D

Календарь++

[!infobar]

// Интеграция планетарных часов в астрологический календарь
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: '<a href="https://www.google.com/search?q=%23eceff4" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#eceff4</a>', muted: '<a href="https://www.google.com/search?q=%236c7086" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#6c7086</a>', accent: '<a href="https://www.google.com/search?q=%2388c0d0" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#88c0d0</a>', border: 'rgba(76, 86, 106, 0.3)' }
   : { bg: 'rgba(245, 247, 250, 0.2)', text: '<a href="https://www.google.com/search?q=%232e3440" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#2e3440</a>', muted: '<a href="https://www.google.com/search?q=%234c566a" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#4c566a</a>', accent: '<a href="https://www.google.com/search?q=%235e81ac" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#5e81ac</a>', 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: <a href="https://www.google.com/search?q=%2351cf66" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#51cf66</a>; 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: <a href="https://www.google.com/search?q=%23ff6b6b" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#ff6b6b</a>; 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: <a href="https://www.google.com/search?q=%2351cf66" target="_blank" rel="noopener noreferrer" class="hashtag-link" onclick="event.stopPropagation()">#51cf66</a>; 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);

Секс не состоялся…..и теперь непонятно когда будет …..
Ждем вечера