# Календарь++
Canonical: https://social-archive.org/densokolvitki/raNO27Ig9p
Author: densokolvitki
Platform: post
## Content
> [!infobar] >```dataviewjs >// Интеграция планетарных часов в астрологический календарь >const CSV_PATH = "2628.csv"; >const LAT = 48.4510; >const LON = 34.9833; > >// === КЛАСС ПЛАНЕТАРНЫХ ЧАСОВ === >class PlanetaryHours { > constructor(lat, lon) { > this.lat = lat; > this.lon = lon; > this.chaldeanOrder = ["Сатурн", "Юпитер", "Марс", "Солнце", "Венера", "Меркурий", "Луна"]; > this.dayRulers = ["Солнце", "Луна", "Марс", "Меркурий", "Юпитер", "Венера", "Сатурн"]; > this.planetSymbols = { "Солнце": "☉", "Луна": "☽", "Марс": "♂", "Меркурий": "☿", "Юпитер": "♃", "Венера": "♀", "Сатурн": "♄" }; > this.tattvas = ["Акаша", "Вайю", "Теджас", "Апас", "Притхиви"]; > } > > calculateSunTimes(date) { > const dayOfYear = Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000); > const gamma = 2 * Math.PI / 365 * (dayOfYear - 1); > const TZ = -(date.getTimezoneOffset() / 60); > const eqtime = 229.18 * (0.000075 + 0.001868 * Math.cos(gamma) - 0.032077 * Math.sin(gamma)); > const decl = 0.006918 - 0.399912 * Math.cos(gamma) + 0.070257 * Math.sin(gamma); > const ha = Math.acos(Math.cos(90.833 * Math.PI / 180) / (Math.cos(this.lat * Math.PI / 180) * Math.cos(decl)) - Math.tan(this.lat * Math.PI / 180) * Math.tan(decl)); > > const sunrise = ((720 - 4 * (this.lon + ha * 180 / Math.PI) - eqtime) / 60) + TZ; > const sunset = ((720 - 4 * (this.lon - ha * 180 / Math.PI) - eqtime) / 60) + TZ; > > return { sunrise, sunset }; > } > > getAllHours(date) { > const sunTimes = this.calculateSunTimes(date); > const dayLength = sunTimes.sunset - sunTimes.sunrise; > const nightLength = 24 - dayLength; > const dayHourLen = dayLength / 12; > const nightHourLen = nightLength / 12; > > const dayOfWeek = date.getDay(); > const dayRuler = this.dayRulers[dayOfWeek]; > const startIdx = this.chaldeanOrder.indexOf(dayRuler); > > const hours = []; > > for (let i = 0; i < 12; i++) { > const planetIdx = (startIdx + i) % 7; > const planet = this.chaldeanOrder[planetIdx]; > const start = sunTimes.sunrise + (i * dayHourLen); > const tattvaLen = dayHourLen / 5; > const tattvas = []; > for (let t = 0; t < 5; t++) { > tattvas.push({ name: this.tattvas[t], num: t + 1, start: start + (t * tattvaLen) }); > } > hours.push({ num: i + 1, planet, symbol: this.planetSymbols[planet], type: "Дневной", start, duration: dayHourLen * 60, tattvas }); > } > > for (let i = 0; i < 12; i++) { > const planetIdx = (startIdx + 12 + i) % 7; > const planet = this.chaldeanOrder[planetIdx]; > const start = sunTimes.sunset + (i * nightHourLen); > const tattvaLen = nightHourLen / 5; > const tattvas = []; > for (let t = 0; t < 5; t++) { > tattvas.push({ name: this.tattvas[t], num: t + 1, start: start + (t * tattvaLen) }); > } > hours.push({ num: i + 1, planet, symbol: this.planetSymbols[planet], type: "Ночной", start, duration: nightHourLen * 60, tattvas }); > } > > return { dayRuler, sunrise: sunTimes.sunrise, sunset: sunTimes.sunset, hours }; > } > > getCurrent(date) { > if (!date) date = new Date(); > const all = this.getAllHours(date); > const time = date.getHours() + date.getMinutes() / 60 + date.getSeconds() / 3600; > > for (const hour of all.hours) { > if (time >= hour.start && time < hour.start + hour.duration / 60) { > let tattva = null; > for (const t of hour.tattvas) { > if (time >= t.start && time < t.start + hour.duration / 60 / 5) { > tattva = t; > break; > } > } > return { ...hour, tattva, dayRuler: all.dayRuler }; > } > } > return { num: 1, planet: "Солнце", symbol: "☉", type: "Дневной", tattva: { name: "Акаша", num: 1 } }; > } > > formatTime(dec) { > const h = Math.floor(dec); > const m = Math.floor((dec - h) * 60); > return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; > } >} > >const phCalc = new PlanetaryHours(LAT, LON); >const currentHour = phCalc.getCurrent(new Date()); > >function getTheme() { > return document.body.classList.contains('theme-dark') ? 'dark' : 'light'; >} > >const theme = getTheme(); >const colors = theme === 'dark' > ? { bg: 'rgba(59, 66, 82, 0.2)', text: '#eceff4', muted: '#6c7086', accent: '#88c0d0', border: 'rgba(76, 86, 106, 0.3)' } > : { bg: 'rgba(245, 247, 250, 0.2)', text: '#2e3440', muted: '#4c566a', accent: '#5e81ac', border: 'rgba(216, 222, 233, 0.3)' }; > >function getMoon() { > const now = new Date(); > let year = now.getFullYear(), month = now.getMonth() + 1, day = now.getDate(); > if (month < 3) { year--; month += 12; } > const jd = (365.25 * year + 30.6 * (month + 1) + day - 694039.09) / 29.5305882; > let phase = Math.round((jd - Math.floor(jd)) * 8); > if (phase >= 8) phase = 0; > const phases = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]; > const zodiacs = ["♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓"]; > const zodiacIdx = Math.floor((((now - new Date(now.getFullYear(), 0, 0)) / 86400000 / 27.322) % 1) * 12); > return { emoji: phases[phase], zodiac: zodiacs[zodiacIdx], phase }; >} > >class AstroCalendar { > constructor() { this.data = []; } > init(csv) { > const lines = csv.trim().split('\n'); > const headers = lines[0].split(';'); > this.data = lines.slice(1).map(line => { > const vals = line.split(';'); > const entry = {}; > headers.forEach((h, i) => entry[h.trim()] = vals[i] ? vals[i].trim() : ''); > return entry; > }); > } > getByDate(d) { return this.data.find(e => e['Дата'] === d) || null; } >} > >let csvContent = ''; >try { csvContent = await app.vault.adapter.read(CSV_PATH); } >catch (e) { dv.paragraph(`❌ ${CSV_PATH} не найден`); return; } > >const cal = new AstroCalendar(); >cal.init(csvContent); > >const moon = getMoon(); >let userNotes = {}; >try { > const saved = localStorage.getItem('astroNotes'); > if (saved) userNotes = JSON.parse(saved); >} catch (e) {} > >let currentDate = new Date(); >const uid = 'ac-' + Date.now(); >let expanded = false; > >const months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']; >const days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; > >function fmt(d) { > return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${d.getFullYear()}`; >} > >const styles = `<style> >.ac-widget { background: ${colors.bg}; backdrop-filter: blur(10px); border: 1px solid ${colors.border}; border-radius: 12px; overflow: hidden; margin: 1em 0; } >.ac-header { background: linear-gradient(135deg, ${colors.accent}66, ${colors.accent}33); backdrop-filter: blur(10px); padding: 12px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } >.ac-header:hover { opacity: 0.9; } >.ac-title { display: flex; align-items: center; gap: 12px; color: ${colors.text}; } >.ac-widgets { display: flex; gap: 8px; align-items: center; } >.ac-planet, .ac-moon { width: 50px; height: 50px; border-radius: 50%; background: ${colors.bg}; backdrop-filter: blur(10px); border: 2px solid ${colors.border}; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; } >.ac-planet::before { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; background: conic-gradient(${colors.accent} 0deg ${(currentHour.num / 12) * 360}deg, transparent ${(currentHour.num / 12) * 360}deg); opacity: 0.3; } >.ac-moon::after { content: ''; position: absolute; top: 0; height: 100%; width: ${Math.abs((moon.phase - 4) / 4) * 100}%; ${moon.phase < 4 ? 'left: 0;' : 'right: 0;'} background: rgba(0,0,0,0.4); border-radius: 50%; } >.ac-emoji { font-size: 20px; z-index: 1; } >.ac-sub { font-size: 11px; color: ${colors.accent}; font-weight: 700; z-index: 1; } >.ac-arrow { color: ${colors.text}; transition: transform 0.3s; } >.ac-arrow.exp { transform: rotate(180deg); } >.ac-body { max-height: 0; overflow: hidden; transition: max-height 0.3s; } >.ac-body.exp { max-height: 2000px; } >.ac-nav { display: flex; justify-content: space-between; padding: 12px; background: ${colors.bg}; backdrop-filter: blur(10px); border-bottom: 1px solid ${colors.border}; } >.ac-btn { background: ${colors.bg}; backdrop-filter: blur(10px); border: 1px solid ${colors.border}; width: 32px; height: 32px; border-radius: 6px; color: ${colors.text}; cursor: pointer; font-weight: bold; } >.ac-btn:hover { opacity: 0.8; } >.ac-month { font-weight: 700; color: ${colors.accent}; min-width: 150px; text-align: center; } >.ac-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; background: ${colors.border}; border: 1px solid ${colors.border}; } >.ac-day-h { background: ${colors.bg}; backdrop-filter: blur(10px); text-align: center; padding: 8px 4px; font-weight: 700; font-size: 11px; color: ${colors.muted}; } >.ac-cell { background: ${colors.bg}; backdrop-filter: blur(10px); min-height: 70px; padding: 6px; cursor: pointer; position: relative; } >.ac-cell:hover { opacity: 0.9; } >.ac-cell.other { opacity: 0.4; } >.ac-cell.today { background: ${colors.accent}33; border: 2px solid ${colors.accent}; } >.ac-num { font-size: 13px; font-weight: 700; color: ${colors.text}; } >.ac-info { font-size: 18px; margin: 4px 0; } >.ac-note-dot { position: absolute; top: 4px; right: 4px; width: 6px; height: 6px; background: #51cf66; border-radius: 50%; } >.ac-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.75); z-index: 9999; display: flex; justify-content: center; align-items: center; } >.ac-modal { background: ${colors.bg}; backdrop-filter: blur(20px); border-radius: 12px; padding: 24px; max-width: 700px; width: 95%; max-height: 90vh; overflow-y: auto; border: 1px solid ${colors.border}; position: relative; } >.ac-close { position: absolute; top: 12px; right: 12px; background: ${colors.bg}; border: 1px solid ${colors.border}; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 20px; } >.ac-close:hover { background: #ff6b6b; color: white; } >.ac-modal-h { font-size: 24px; font-weight: 700; color: ${colors.accent}; margin-bottom: 16px; padding-right: 40px; } >.ac-detail { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid ${colors.border}; font-size: 13px; } >.ac-label { font-weight: 600; color: ${colors.muted}; } >.ac-value { color: ${colors.text}; } >.ac-hours-table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 12px; } >.ac-hours-table th { background: ${colors.bg}; color: ${colors.accent}; padding: 8px 4px; text-align: left; border-bottom: 2px solid ${colors.border}; } >.ac-hours-table td { padding: 6px 4px; border-bottom: 1px solid ${colors.border}; } >.ac-hours-table tr:hover { background: ${colors.bg}; } >.ac-current-hour { background: ${colors.accent}22 !important; font-weight: 700; } >.ac-textarea { width: 100%; min-height: 80px; background: ${colors.bg}; border: 1px solid ${colors.border}; border-radius: 6px; padding: 10px; color: ${colors.text}; } >.ac-save { background: #51cf66; border: none; padding: 10px 18px; border-radius: 6px; color: white; font-weight: 600; cursor: pointer; margin-top: 10px; } >.ac-save:hover { opacity: 0.85; } >.ac-tabs { display: flex; gap: 8px; margin-bottom: 16px; } >.ac-tab { padding: 8px 16px; background: ${colors.bg}; border: 1px solid ${colors.border}; border-radius: 6px; cursor: pointer; font-size: 13px; } >.ac-tab.active { background: ${colors.accent}; color: white; border-color: ${colors.accent}; } >.ac-tab:hover { opacity: 0.8; } ></style>`; > >function create() { > const c = document.getElementById(uid); > const today = new Date(); > > c.innerHTML = ` ><div class="ac-widget"> > <div class="ac-header" id="${uid}-t"> > <div class="ac-title"> > <div style="font-size:24px;">📅</div> > <div> > <div style="font-weight:700;font-size:15px;">Дневник</div> > <div style="font-size:12px;opacity:0.9;">${today.getDate()} ${months[today.getMonth()]}</div> > </div> > </div> > <div class="ac-widgets"> > <div class="ac-planet"> > <div class="ac-emoji">${currentHour.symbol}</div> > <div class="ac-sub">${currentHour.num}</div> > </div> > <div class="ac-moon"> > <div class="ac-emoji">${moon.emoji}</div> > <div class="ac-sub">${moon.zodiac}</div> > </div> > </div> > <div class="ac-arrow" id="${uid}-a">▼</div> > </div> > <div class="ac-body" id="${uid}-b"> > <div class="ac-nav"> > <div style="display:flex;gap:8px;align-items:center;"> > <button class="ac-btn" id="${uid}-p">←</button> > <div class="ac-month" id="${uid}-m"></div> > <button class="ac-btn" id="${uid}-n">→</button> > </div> > <button class="ac-btn" id="${uid}-td" style="width:auto;padding:0 12px;">Сегодня</button> > </div> > <div class="ac-grid" id="${uid}-g"></div> > </div> ></div>`; > > document.getElementById(`${uid}-t`).onclick = toggle; > document.getElementById(`${uid}-p`).onclick = () => { currentDate.setMonth(currentDate.getMonth() - 1); render(); }; > document.getElementById(`${uid}-n`).onclick = () => { currentDate.setMonth(currentDate.getMonth() + 1); render(); }; > document.getElementById(`${uid}-td`).onclick = () => { currentDate = new Date(); render(); }; >} > >function toggle() { > expanded = !expanded; > const b = document.getElementById(`${uid}-b`); > const a = document.getElementById(`${uid}-a`); > if (expanded) { b.classList.add('exp'); a.classList.add('exp'); render(); } > else { b.classList.remove('exp'); a.classList.remove('exp'); } >} > >function render() { > const y = currentDate.getFullYear(); > const m = currentDate.getMonth(); > document.getElementById(`${uid}-m`).textContent = `${months[m]} ${y}`; > > const g = document.getElementById(`${uid}-g`); > g.innerHTML = ''; > days.forEach(d => { const h = document.createElement('div'); h.className = 'ac-day-h'; h.textContent = d; g.appendChild(h); }); > > const first = new Date(y, m, 1); > const last = new Date(y, m + 1, 0); > let dow = first.getDay(); > dow = dow === 0 ? 6 : dow - 1; > > const prevLast = new Date(y, m, 0); > for (let i = dow - 1; i >= 0; i--) { > const day = prevLast.getDate() - i; > g.appendChild(makeCell(day, new Date(y, m - 1, day), true)); > } > > for (let day = 1; day <= last.getDate(); day++) { > g.appendChild(makeCell(day, new Date(y, m, day), false)); > } > > const total = dow + last.getDate(); > const remain = total <= 35 ? 35 - total : 42 - total; > for (let day = 1; day <= remain; day++) { > g.appendChild(makeCell(day, new Date(y, m + 1, day), true)); > } >} > >function makeCell(day, date, other) { > const dateStr = fmt(date); > const info = cal.getByDate(dateStr); > const today = fmt(new Date()); > const isToday = dateStr === today && !other; > > const cell = document.createElement('div'); > cell.className = 'ac-cell'; > if (other) cell.classList.add('other'); > if (isToday) cell.classList.add('today'); > > const num = document.createElement('div'); > num.className = 'ac-num'; > num.textContent = day; > cell.appendChild(num); > > if (info && !other && info['Имя фазы']) { > const phases = { 'Новолуние': '🌑', 'Первая четверть луны': '🌓', 'Полнолуние': '🌕', 'Последняя четверть луны': '🌗' }; > const i = document.createElement('div'); > i.className = 'ac-info'; > i.textContent = phases[info['Имя фазы']] || ''; > cell.appendChild(i); > } > > if (userNotes[dateStr]) { > const dot = document.createElement('div'); > dot.className = 'ac-note-dot'; > cell.appendChild(dot); > } > > if (!other) cell.onclick = () => openModal(dateStr, date); > return cell; >} > >function openModal(dateStr, date) { > const [d, m, y] = dateStr.split('.'); > const info = cal.getByDate(dateStr); > const hours = phCalc.getAllHours(date); > const now = new Date(); > const isToday = dateStr === fmt(now); > > const overlay = document.createElement('div'); > overlay.className = 'ac-modal-overlay'; > > let html = ''; > if (info) { > const items = [ > { l: '☀️ Восход', v: info['Восход солнца'] }, > { l: '🌅 Заход', v: info['Заход солнца'] }, > { l: '☀️ Солнце', v: info['Солнце в знаке'] }, > { l: '🌙 Луна', v: info['Луна в знаке'] }, > { l: '🌙 Лунные сутки', v: info['Лунные сутки'] }, > { l: '🌙 Фаза', v: info['Имя фазы'] } > ]; > items.forEach(i => { if (i.v) html += `<div class="ac-detail"><span class="ac-label">${i.l}</span><span class="ac-value">${i.v}</span></div>`; }); > } > > let hoursHTML = '<table class="ac-hours-table"><thead><tr><th>Время</th><th>Час</th><th>Тип</th><th>Планета</th><th>Таттва</th></tr></thead><tbody>'; > hours.hours.forEach(h => { > const isCurrent = isToday && now.getHours() + now.getMinutes() / 60 >= h.start && now.getHours() + now.getMinutes() / 60 < h.start + h.duration / 60; > const rowClass = isCurrent ? 'ac-current-hour' : ''; > hoursHTML += `<tr class="${rowClass}"> > <td>${phCalc.formatTime(h.start)}</td> > <td>${h.num}</td> > <td>${h.type}</td> > <td>${h.symbol} ${h.planet}</td> > <td>${h.tattvas.map(t => t.name).join(', ')}</td> > </tr>`; > }); > hoursHTML += '</tbody></table>'; > > overlay.innerHTML = ` ><div class="ac-modal"> > <button class="ac-close">×</button> > <div class="ac-modal-h">${parseInt(d)} ${months[parseInt(m) - 1]} ${y}</div> > > <div class="ac-tabs"> > <div class="ac-tab active" id="tab-info">📊 Информация</div> > <div class="ac-tab" id="tab-hours">⏰ Планетарные часы</div> > <div class="ac-tab" id="tab-notes">📝 Заметки</div> > </div> > > <div id="content-info"> > <div style="background:${colors.bg};padding:14px;border-radius:8px;">${html || '<p style="text-align:center;color:' + colors.muted + ';">Нет данных</p>'}</div> > </div> > > <div id="content-hours" style="display:none;"> > <div style="margin-bottom:12px;color:${colors.accent};font-weight:600;">Управитель дня: ${hours.dayRuler} ${phCalc.planetSymbols[hours.dayRuler]}</div> > ${hoursHTML} > </div> > > <div id="content-notes" style="display:none;"> > <textarea class="ac-textarea" id="n-${dateStr}" placeholder="Ваши заметки...">${userNotes[dateStr] || ''}</textarea> > <button class="ac-save" id="s-${dateStr}">💾 Сохранить</button> > </div> ></div>`; > > document.body.appendChild(overlay); > overlay.querySelector('.ac-close').onclick = () => overlay.remove(); > overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); }; > > const tabs = ['info', 'hours', 'notes']; > tabs.forEach(tab => { > document.getElementById(`tab-${tab}`).onclick = () => { > tabs.forEach(t => { > document.getElementById(`tab-${t}`).classList.remove('active'); > document.getElementById(`content-${t}`).style.display = 'none'; > }); > document.getElementById(`tab-${tab}`).classList.add('active'); > document.getElementById(`content-${tab}`).style.display = 'block'; > }; > }); > > document.getElementById(`s-${dateStr}`).onclick = () => { > const note = document.getElementById(`n-${dateStr}`).value.trim(); > if (note) userNotes[dateStr] = note; else delete userNotes[dateStr]; > localStorage.setItem('astroNotes', JSON.stringify(userNotes)); > render(); > overlay.remove(); > }; >} > >dv.container.innerHTML = styles + `<div id="${uid}"></div>`; >setTimeout(create, 100); >``` > > Секс не состоялся…..и теперь непонятно когда будет ….. > Ждем вечера
