Trying to visualize the tables at https://www.timeanddate.com/moon/ to better see where the moon is from day to day.
A Pen by Andreas Borgen on CodePen.
<script src="//unpkg.com/[email protected]"></script> | |
<script src="//unpkg.com/[email protected]"></script> | |
<script src="//unpkg.com/[email protected]"></script> | |
<header> | |
<h2>Moon calendar</h2> | |
<aside>(All data from <a target="_blank" href="https://www.timeanddate.com/moon/norway/hamar?month=9&year=2018">timeanddate.com</a> )</aside> | |
</header> | |
<form> | |
<div class="location"> | |
<label> | |
<span class="caption">Country</span> | |
<input type="text" name="country" value="Norway" /> | |
</label> | |
<label> | |
<span class="caption">City</span> | |
<input type="text" name="city" value="Hamar" /> | |
</label> | |
</div> | |
<div class="month-picker"> | |
<div class="year"> | |
<label> | |
<span class="caption">Year</span> | |
<input type="number" name="year" value="2018" /> | |
</label> | |
</div> | |
<div class="month"> | |
<span class="caption">Month</span> | |
<div class="inputs"> | |
<label><input type="radio" name="month" value="1" /><span>Jan</span></label> | |
<label><input type="radio" name="month" value="2" /><span>Feb</span></label> | |
<label><input type="radio" name="month" value="3" /><span>Mar</span></label> | |
<label><input type="radio" name="month" value="4" /><span>Apr</span></label> | |
<label><input type="radio" name="month" value="5" /><span>May</span></label> | |
<label><input type="radio" name="month" value="6" /><span>Jun</span></label> | |
<label><input type="radio" name="month" value="7" /><span>Jul</span></label> | |
<label><input type="radio" name="month" value="8" /><span>Aug</span></label> | |
<label><input type="radio" name="month" value="9" /><span>Sep</span></label> | |
<label><input type="radio" name="month" value="10"/><span>Oct</span></label> | |
<label><input type="radio" name="month" value="11"/><span>Nov</span></label> | |
<label><input type="radio" name="month" value="12"/><span>Dec</span></label> | |
</div> | |
</div> | |
</div> | |
<div> | |
<button type="submit">Update</button> | |
</div> | |
</form> | |
<div id="container"></div> | |
<div id="shortcuts"> | |
<a id="start" href="#">First day of month</a> | |
<a id="now" href="#" style="display:none;">Today</a> | |
<a id="end" href="#">Last day of month</a> | |
</div> | |
<div id="loader"></div> |
Trying to visualize the tables at https://www.timeanddate.com/moon/ to better see where the moon is from day to day.
A Pen by Andreas Borgen on CodePen.
console.clear(); | |
/** | |
* localForage + lz-string | |
* https://github.com/localForage/localForage | |
* https://github.com/pieroxy/lz-string | |
*/ | |
class CompressedForage { | |
constructor(key) { | |
this._key = key; | |
this.key = 'ABO_CompressedForage_' + key; | |
} | |
_onError(err) { | |
console.error(err); | |
} | |
_containerWork(work, callback) { | |
const mainKey = this.key, | |
errHandler = this._onError; | |
localforage.getItem(mainKey) | |
.then(function(cont) { | |
if(!cont) { cont = {}; } | |
const result = work(cont); | |
localforage.setItem(mainKey, cont) | |
.then(function(cont) { | |
if(callback) { callback(result); } | |
}) | |
.catch(errHandler); | |
}) | |
.catch(errHandler); | |
} | |
_normKey(key) { | |
//See points 2 and 3 here (avoid overriding standard functions and properties on Object): | |
//http://2ality.com/2012/01/objects-as-maps.html | |
return '_@' + key; | |
} | |
setItem(key, data, callback) { | |
const k = this._normKey(key); | |
this._containerWork(cont => { | |
const buffer = data ? LZString.compressToUint8Array(data) : data; | |
cont[k] = buffer; | |
}, callback); | |
} | |
getKeys(callback) { | |
this._containerWork(cont => { | |
const keys = Object.keys(cont).map(k => { | |
//Keep this code synced with _normKey().. | |
return k.substring(2); | |
}); | |
return keys; | |
}, callback); | |
} | |
getItem(key, callback) { | |
const k = this._normKey(key); | |
this._containerWork(cont => { | |
const buffer = cont[k], | |
data = buffer ? LZString.decompressFromUint8Array(buffer) : buffer; | |
return data; | |
}, callback); | |
} | |
removeItem(key, callback) { | |
const k = this._normKey(key); | |
this._containerWork(cont => { | |
//https://stackoverflow.com/questions/208105/how-do-i-remove-a-property-from-a-javascript-object | |
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete | |
delete cont[k]; | |
}, callback); | |
} | |
clear(callback) { | |
localforage.removeItem(this.key) | |
.then(callback) | |
.catch(this._onError); | |
} | |
} | |
function createTimeline(table) { | |
const ud = ABOUtils.DOM, | |
[$, $$] = ud.selectors(); | |
let events = []; | |
function parseEvent(type, date, time, deg) { | |
if(!time) { return null; } | |
function extractNumbers(str) { | |
return str.match(/(\d+)/g).map(Number); | |
} | |
function parseTime(time) { | |
time = time.trim(); | |
let [h, m] = extractNumbers(time); | |
//12:13 am -> 00:13 | |
// 1:16 am -> 01:16 | |
//.. | |
//12:42 pm -> 12:42 | |
// 1:52 pm -> 13:52 | |
if(time.indexOf('am') > 0) { | |
if(h === 12) { h = 0; } | |
} | |
else if(time.indexOf('pm') > 0) { | |
if(h !== 12) { h += 12; } | |
} | |
return [h, m]; | |
} | |
//Example: "↑ (296°)", "13:14" | |
const degr = extractNumbers(deg)[0], | |
[h, m] = parseTime(time), | |
dayFraction = (h + (m/60))/24, | |
monthIndex = date-1 + dayFraction; | |
const event = createEvent(type, monthIndex, degr); | |
event.raw = { date, time, deg }; | |
return event; | |
} | |
function createEvent(type, time, degr) { | |
return { | |
type, | |
time, | |
degr, | |
}; | |
} | |
$$('tr[data-day]', table).forEach(tr => { | |
const date = Number(tr.dataset.day), | |
tds = $$('td', tr); | |
//Account for empty padder cells: | |
const cells = []; | |
tds.forEach(td => { | |
let span = td.colSpan; | |
if(span === 1) { | |
cells.push(td.textContent.trim()); | |
} | |
else { | |
while((span--) > 0) { cells.push(''); } | |
} | |
}); | |
let up1 = parseEvent('up', date, cells[0], cells[1]), | |
down = parseEvent('down', date, cells[2], cells[3]), | |
up2 = parseEvent('up', date, cells[4], cells[5]), | |
max = parseEvent('max', date, cells[6], cells[7]); | |
if(max) { | |
max.illum = parseFloat(cells[9]); | |
} | |
events.push(up1, down, up2, max); | |
}); | |
//Order the events into a moon's rise, max and set: | |
events = events.filter(x => x).sort((a, b) => a.time - b.time); | |
//Fake first moonrise and last moonset if we have a max: | |
const e1 = events[0], | |
e2 = events[1], | |
e99 = events[events.length - 2], | |
e100 = events[events.length - 1]; | |
if(e1.type === 'max') { | |
const up0 = createEvent('up', e1.time - (e2.time - e1.time)); | |
events.unshift(up0); | |
} | |
if(e100.type === 'max') { | |
const down101 = createEvent('down', e100.time + (e100.time - e99.time)); | |
} | |
while(events[0].type !== 'up') { events.shift(); } | |
//console.log('Events: ', events); | |
//Group the events into a moon's rise, max and set: | |
const moons = []; | |
for(let i = 2; i < events.length; i += 3) { | |
if(events[i].type !== 'down') { throw 'Order WTF?! - ' + i; } | |
moons.push({ | |
up: events[i-2], | |
max: events[i-1], | |
down: events[i], | |
}); | |
} | |
console.log('Moons:', moons); | |
/* Render the "calendar" */ | |
const chart = ud.createElement('.moon-chart'), | |
base = ud.createElement('.day-base', chart); | |
function toBasePct(x) { | |
return (x * 100).toFixed(1) + '%'; | |
} | |
function toBase(x) { | |
//console.log(x, base.clientWidth); | |
return Math.round(x * base.clientWidth); | |
} | |
/* Not quite reliable.. | |
function moonPos(m) { | |
function isEast (degr) { return (degr < 180); } | |
function isSouth(degr) { return (degr > 90) && (degr < 270); } | |
function posInfo(degr) { | |
if(!degr) { return null; } | |
return { | |
degr: degr, | |
isEast: isEast(degr), | |
isSouth: isSouth(degr), | |
}; | |
} | |
//The moon rotates very slowly around the earth (.56 degrees/hr), | |
//so its perceived motion across the sky is caused by earth's rotation around itself (15 degrees/hr). | |
//We see it moving from east to west. Far north, it is always to the south, and vice versa. | |
//https://www.quora.com/Does-the-moon-orbit-the-earth-at-the-equator-Does-the-orbit-vary-much | |
//https://cseligman.com/text/sky/moonmotion.htm | |
const p1 = posInfo(m.up.degr), | |
p2 = posInfo(m.down.degr); | |
if(!p1 || !p2) { return '?'; } | |
let makesSense = true; | |
if(p2.isEast || !p1.isEast) { | |
makesSense = false; | |
} | |
if(p1.isSouth !== p2.isSouth) { | |
makesSense = (m.max.degr > 80); | |
} | |
if(!makesSense) { throw 'Pos WTF?!'; } | |
const hoursUp = (m.down.time - m.up.time) * 24, | |
toSouth = (hoursUp < 12) ? p1.isSouth : !p1.isSouth; | |
const degsTraveled = toSouth ? (p2.degr - p1.degr) : (p1.degr + 360-p2.degr), | |
speed = degsTraveled/hoursUp, | |
centerDegr = toSouth ? (p2.degr + p1.degr)/2 : p1.degr - (p1.degr + 360-p2.degr)/2; | |
console.log(`pos ${m.max.raw.date} ${toSouth ? 'S' : 'N'} ${hoursUp.toFixed(1)}h\t(${centerDegr}°\t${speed.toFixed(1)}°/h)`); | |
return centerDegr; | |
} | |
*/ | |
moons.forEach((m, i) => { | |
const illum1 = i ? moons[i-1].max.illum : m.max.illum, | |
illum2 = i ? m.max.illum : moons[i+1].max.illum; | |
m.wane = illum1 > illum2; | |
const path = ud.createElement('svg.moon-path', base, { | |
style: `left:${toBasePct(m.up.time)};`, //width:${toBasePct(m.down.time - m.up.time)}; height:${toBasePct(2 * m.max.degr/90)};` | |
width: toBasePct(m.down.time - m.up.time), | |
height: toBasePct(m.max.degr/90), | |
viewBox: '0,0 100,50', | |
preserveAspectRatio: 'none', | |
}); | |
ud.createElement('path', path, { | |
d: 'M0,50 Q50,-50 100,50', | |
}); | |
ud.createElement('.east-west-marker', base, { | |
style: `left:${toBasePct(m.max.time)}; height:${toBasePct(m.max.degr/90)};` | |
}); | |
const moon = ud.createElement('.moon', base, { | |
title: `${m.max.raw.time} ${m.max.illum}%`, //@${moonPos(m)}`, | |
style: `left:${toBasePct(m.max.time)}; bottom:${toBasePct(m.max.degr/90)};` | |
}); | |
if(m.wane) { moon.classList.add('wane'); } | |
const pct = m.max.illum/100, | |
small = (pct < .5), | |
half = small ? (1 - 2*pct) : 2 * (pct-.5); | |
moon.innerHTML = `<svg width="10" height="10" viewBox="0,0 2,2"> | |
<circle cx="1" cy="1" r="1" fill="currentColor" /> | |
<path d="M1,0 a1,1 0 1,0 0,2 a${half},1 0 1,${small ? 0 : 1} 0,-2" /> | |
</svg>`; | |
}); | |
for(let d = 1; d <= e100.raw.date; d++) { | |
const day = ud.createElement('.day', chart, { | |
'data-date': d, | |
}), | |
hours = ud.createElement('.hours', day); | |
['03', '06', '09', '12', '15', '18', '21'].forEach(h => ud.createElement('span', hours, { 'data-hour': h })); | |
} | |
return chart; | |
} | |
(function() { | |
const ud = ABOUtils.DOM, | |
[$, $$] = ud.selectors(); | |
const form = $('form'), | |
container = $('#container'), | |
shortcuts = {}, | |
loader = $('#loader'); | |
$$('#shortcuts a').forEach(a => shortcuts[a.id] = a); | |
//Stored (html) pages from timeanddate.com | |
const htmlStore = new CompressedForage('moon-data'); | |
function onError(err) { | |
alert(err); | |
toggleLoader(false); | |
} | |
function toggleLoader(show) { | |
//loader.style.display = show ? 'flex' : 'none'; | |
show ? loader.classList.add('show') : loader.classList.remove('show'); | |
} | |
function formSerialize(form) { | |
//https://stackoverflow.com/a/44033425/1869660 | |
const data = new FormData(form); | |
return new URLSearchParams(data).toString(); | |
} | |
function formDeserialize(form, data) { | |
const entries = (new URLSearchParams(data)).entries(); | |
let entry; | |
while(!(entry = entries.next()).done) { | |
const [key, val] = entry.value, | |
//http://javascript-coder.com/javascript-form/javascript-form-value.phtml | |
input = form.elements[key]; | |
switch(input.type) { | |
case 'checkbox': input.checked = !!val; break; | |
default: input.value = val; break; | |
} | |
} | |
} | |
function currDate() { | |
const date = new Date(), | |
year = date.getFullYear(), | |
month = date.getMonth() + 1, | |
day = date.getDate(); | |
return { | |
year, | |
month, | |
day, | |
}; | |
} | |
function htmlKey(year, month, url) { | |
month = ('0' + month).slice(-2); | |
return `${year}-${month}-${url}`; | |
} | |
function init(callback) { | |
//Form values (stored + default): | |
const stored = localStorage.ABO_moonPhases; | |
if(stored) { | |
console.log('Stored:', stored); | |
formDeserialize(form, stored); | |
} | |
const now = currDate(); | |
form.elements.year.value = now.year; | |
form.elements.month.value = now.month; | |
function updateChart() { | |
localStorage.ABO_moonPhases = formSerialize(form); | |
doIt(); | |
} | |
ud.addEvent(form, 'submit', e => { | |
e.preventDefault(); | |
updateChart(); | |
}); | |
//For convenience, also update the chart when we click on a new month: | |
ud.addEvent(form, 'change', e => { | |
if(e.target.type === 'radio') { | |
updateChart(); | |
} | |
}); | |
//Scrolling the chart: | |
ud.addEvent($('#shortcuts'), 'click', e => { | |
e.preventDefault(); | |
const chart = container.firstElementChild; | |
switch(e.target.id) { | |
case 'start': chart.scrollLeft = 0; break; | |
case 'end': chart.scrollLeft = chart.scrollWidth; break; | |
case 'now': chart.scrollLeft = $(`[data-date="${now.day}"]`, chart).offsetLeft; break; | |
} | |
}) | |
//Delete stored pages that are more than a month old: | |
htmlStore._onError = onError; | |
function deleteHtml(keys, callback) { | |
if(keys && keys.length) { | |
const next = keys.shift(); | |
console.log('Deleting ' + next); | |
htmlStore.removeItem(next, () => deleteHtml(keys, callback)); | |
} | |
else { | |
callback(); | |
} | |
} | |
const prevMonthKey = htmlKey(now.year, now.month-1, ''), | |
storedKeys = htmlStore.getKeys(keys => { | |
const toDelete = keys.filter(k => k < prevMonthKey); | |
deleteHtml(toDelete, callback); | |
}); | |
} | |
function doIt() { | |
toggleLoader(true); | |
function getParam(key, toLower) { | |
let val = form.elements[key].value; | |
if(toLower) { val = val.toLowerCase(); } | |
return val; | |
} | |
function parseHtml(html) { | |
//console.log('html\n' + html); | |
let table = html.substring(html.indexOf('<table id=tb-7dmn'), | |
html.indexOf('</table>')) + '</table>'; | |
//console.log('table\n' + table); | |
const parser = ud.createElement('div'); | |
parser.innerHTML = table; | |
const tableElm = parser.firstElementChild; | |
container.innerHTML = ''; | |
container.appendChild(createTimeline(tableElm)); | |
const nowLink = shortcuts.now; | |
if(isNow) { | |
nowLink.textContent = now.day; | |
nowLink.style.display = 'inline-block'; | |
nowLink.click(); | |
} | |
else { | |
nowLink.style.display = 'none'; | |
} | |
toggleLoader(false); | |
} | |
//https://www.timeanddate.com/moon/norway/hamar?month=9&year=2018 | |
const y = Number(getParam('year')), | |
m = Number(getParam('month')), | |
now = currDate(), | |
isNow = (y === now.year) && (m === now.month); | |
//We store the moon data (scraped html pages) locally, | |
//as the moon's path and phases are unlikely to change: | |
const url = `https://www.timeanddate.com/moon/${getParam('country', true)}/${getParam('city', true)}?month=${m}&year=${y}`, | |
storeKey = htmlKey(y, m, url); | |
htmlStore.getItem(storeKey, html => { | |
if(html) { | |
console.log('Found stored ' + url); | |
parseHtml(html); | |
} | |
else { | |
//*/ | |
console.log('Fetching ' + url); | |
fetch('https://cors-anywhere.herokuapp.com/' + url) | |
.then(r => r.text()) | |
.then(html => { | |
htmlStore.setItem(storeKey, html, () => parseHtml(html)); | |
}) | |
.catch(onError); | |
//*/ | |
} | |
}); | |
} | |
init(doIt); | |
})(); |
body { | |
font-family: Georgia, sans-serif; | |
header h2 { | |
margin-bottom: 0; | |
} | |
button, input { | |
font: inherit; | |
box-sizing: border-box; | |
} | |
} | |
#loader { | |
position: absolute; | |
top:0;left:0;bottom:0;right:0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
background: rgba(white, .5); | |
opacity: 0; | |
pointer-events: none; | |
transition: opacity .5s; | |
&.show { | |
opacity: 1; | |
pointer-events: auto; | |
} | |
&::before { | |
content: 'Loading...'; | |
display: inline-block; | |
font-size: 1.5em; | |
width: 7em; | |
height: 7em; | |
line-height: 6; | |
text-align: center; | |
border-radius: 100%; | |
background: palegreen; | |
//color: white; | |
} | |
} | |
form { | |
margin: 1em 0; | |
> div { | |
} | |
label { | |
cursor: pointer; | |
white-space: nowrap; | |
} | |
.caption { | |
padding-right: .5em; | |
} | |
.location, .month-picker { | |
display: inline-block; | |
} | |
.location { | |
//display: table; | |
margin: 0 .5em .5em 0; | |
label { | |
display: table-row; | |
> * { display: table-cell; } | |
} | |
input { | |
width: 100%; | |
min-width: 5em; | |
} | |
} | |
@supports (display: grid) { | |
& { | |
display: grid; | |
grid-template-areas: "loc month" | |
". month"; | |
//grid-template-columns: minmax(1fr, 15em) auto; | |
grid-template-rows: 1fr auto; | |
grid-column-gap: 1em; | |
justify-content: start; | |
justify-items: stretch; | |
} | |
.location { | |
grid-area: loc; | |
margin: 0; | |
} | |
.month-picker { | |
grid-area: month; | |
} | |
button[type="submit"] { | |
width: 100%; | |
} | |
} | |
} | |
.month-picker { | |
@mixin hide() { | |
position: absolute; | |
z-index: -1; | |
opacity: 0; | |
} | |
display: inline-flex; | |
flex-flow: column nowrap; | |
//align-items: center; | |
border: 1px solid gainsboro; | |
.year { | |
//background: gainsboro; | |
padding: .2em .4em; | |
input { | |
width: 4em; | |
} | |
} | |
.month { | |
.caption { | |
@include hide(); | |
} | |
.inputs { | |
display: flex; | |
flex-flow: row wrap; | |
width: 9em; | |
label { | |
flex: 1 1 30%; | |
margin: 1px; | |
input { | |
@include hide(); | |
} | |
span { | |
display: inline-block; | |
width: 100%; | |
padding: .5em 0; | |
text-align: center; | |
background: aliceblue; | |
border: 1px solid lightskyblue; | |
box-sizing: border-box; | |
} | |
span:hover, | |
input:checked + span { | |
background: gold; | |
border-color: orange; | |
} | |
} | |
} | |
} | |
} | |
#container { | |
margin: 1em 0; | |
} | |
#shortcuts { | |
a { | |
display: inline-block; | |
width: 2.5em; | |
height: 2.5em; | |
white-space: nowrap; | |
text-indent: 100%; | |
overflow: hidden; | |
background: url("data:image/svg+xml,%3Csvg style='color:tomato;fill:black' xmlns='http://www.w3.org/2000/svg' width='1200' height='400' viewBox='0 0 555 185'%3E %3Cdefs%3E %3Cpath id='cal' d='M22 143h29v-29H22v29zm35 0h32v-29H57v29zm-35-36h29V75H22v32zm35 0h32V75H57v32zM22 69h29V40H22v29zm74 74h32v-29H96v29zM57 69h32V40H57v29zm77 74h29v-29h-29v29zm-38-36h32V75H96v32zM60 21V-8l-1-2-2-1h-6l-2 1-1 2v29l1 2 2 1h6l2-1 1-2zm74 86h29V75h-29v32zM96 69h32V40H96v29zm38 0h29V40h-29v29zm3-48V-8l-1-2-2-1h-7l-2 1-1 2v29l1 2 2 1h7l2-1 1-2zm38-6v128q1 5-3 9t-9 3H22q-5 0-9-3t-4-9V15q0-6 4-9t9-4h13V-8q0-6 4-11t12-5h6q7 0 11 5t5 11V2h39V-8q0-6 4-11t12-5h6q7 0 11 5t5 11V2h13q6 0 9 4t3 9z'/%3E %3C/defs%3E %3Csvg x='0' width='185' viewBox='0 -26 185 185'%3E %3Ccircle id='start' cx='36' cy='54' r='30' fill='currentColor' stroke='none' /%3E %3Cuse href='%23cal' /%3E %3C/svg%3E %3Csvg x='185' width='185' viewBox='0 -26 185 185'%3E %3Ccircle id='now' cx='112' cy='91' r='30' fill='currentColor' stroke='none' /%3E %3Cuse href='%23cal' /%3E %3C/svg%3E %3Csvg x='370' width='185' viewBox='0 -26 185 185'%3E %3Ccircle id='end' cx='149' cy='128' r='30' fill='currentColor' stroke='none' /%3E %3Cuse href='%23cal' /%3E %3C/svg%3E %3C/svg%3E") center/300% no-repeat; | |
&#start { | |
background-position: 0; | |
} | |
&#now { | |
background-position: 50%; | |
} | |
&#end { | |
background-position: 100%; | |
} | |
} | |
} | |
$day-width: 200px; | |
$day-height: 100px; | |
.moon-chart { | |
position: relative; | |
display: flex; | |
align-items: flex-end; | |
overflow-x: auto; | |
overflow-y: hidden; | |
$altitude: royalblue; | |
background: dodgerblue; | |
background-image: | |
repeating-linear-gradient(90deg, white 0, white 1px, transparent 0, transparent $day-width), | |
repeating-linear-gradient(180deg, transparent 0, transparent $day-height*.33, $altitude 0, $altitude $day-height*.34); | |
background-attachment: local; | |
.day-base { | |
position: absolute; | |
bottom: 0; | |
display: block; | |
width: $day-width; | |
height: $day-height; | |
.moon-path { | |
position: absolute; | |
bottom: 0; | |
path { | |
fill: none; | |
stroke: white; | |
stroke-width: 2; | |
stroke-dasharray: 6; | |
} | |
} | |
.east-west-marker { | |
position: absolute; | |
bottom: 0; | |
//52% instead of 50%: | |
//Because of the moon's own orbit speed, it takes a little longer than 24hrs to circle around to the same point on the horizon: | |
//https://cseligman.com/text/sky/moonmotion.htm | |
width: 52%; | |
box-sizing: border-box; | |
border: 1px dashed yellow; | |
border-top: none; | |
transform: translateX(-50%); | |
&::before, &::after { | |
display: block; | |
position: absolute; | |
top: -1em; | |
color: white; | |
} | |
&::before { | |
content: 'E'; | |
left: -.3em; | |
} | |
&::after { | |
content: 'W'; | |
right: -.4em; | |
} | |
} | |
.moon { | |
position: absolute; | |
display: block; | |
width: 1.5em; | |
height: 1.5em; | |
margin: -.75em; | |
//background: rgba(white, .8); | |
//border-radius: 100%; | |
svg { | |
width: 100%; | |
height: 100%; | |
color: white; | |
fill: royalblue; | |
} | |
&.wane { | |
transform: rotate(180deg); | |
} | |
} | |
} | |
.day { | |
flex: 0 0 auto; | |
position: relative; | |
display: block; | |
width: $day-width; | |
height: $day-height; | |
$dark: rgba(black, 0.6); | |
background-image: | |
//url("data:image/svg+xml,%3Csvg fill='none' stroke='white' stroke-width='.2' xmlns='http://www.w3.org/2000/svg' width='120' height='10' viewBox='0,0, 24,2'%3E %3Cpath d='M0,2v-2 M12,2v-2 M24,2v-2' /%3E %3Cpath d='M6,2v-1.5 M18,2v-1.5'/%3E %3Cpath d='M3,2v-1 M9,2v-1 M15,2v-1 M21,2v-1'/%3E %3C/svg%3E"), | |
linear-gradient(90deg, $dark 0, $dark $day-width*.2, rgba(0,0,0,0) $day-width*.3, rgba(0,0,0,0) $day-width*.7, $dark $day-width*.8, $dark $day-width), | |
linear-gradient(180deg, transparent $day-height*.95, green 0); | |
//background-repeat: no-repeat; | |
//background-size: 100%, auto, auto; | |
//background-position: center 100%; | |
pointer-events: none; | |
box-sizing: border-box; | |
border-right: 1px dashed white; | |
&::after { | |
content: attr(data-date); | |
display: block; | |
width: 1.5em; | |
height: 1.5em; | |
margin: auto; | |
text-align: center; | |
line-height: 1.5; | |
background: #ff9; | |
border-radius: 100%; | |
} | |
.hours { | |
position: absolute; | |
bottom:0; left:0; width: 100%; | |
display: flex; | |
justify-content: space-evenly; | |
span { | |
position: relative; | |
display: inline-block; | |
//width: 2px; | |
height: 1.4em; | |
font-family: sans-serif; | |
font-size: .7em; | |
color: white; | |
&::before { | |
content: ''; | |
display: block; | |
position: absolute; | |
width: 2px; | |
height: 2px; | |
transform: translateX(-50%); | |
background: currentColor; | |
} | |
&::after { | |
content: attr(data-hour); | |
position: absolute; | |
bottom:0; | |
transform: translateX(-50%); | |
} | |
} | |
} | |
} | |
} //.moon-chart |