feat: initial commit
This commit is contained in:
commit
e214116e40
253 changed files with 17406 additions and 0 deletions
BIN
src/assets/fonts/lb.woff2
Normal file
BIN
src/assets/fonts/lb.woff2
Normal file
Binary file not shown.
BIN
src/assets/fonts/ll.woff2
Normal file
BIN
src/assets/fonts/ll.woff2
Normal file
Binary file not shown.
BIN
src/assets/fonts/ml.woff2
Normal file
BIN
src/assets/fonts/ml.woff2
Normal file
Binary file not shown.
BIN
src/assets/fonts/sg.woff2
Normal file
BIN
src/assets/fonts/sg.woff2
Normal file
Binary file not shown.
BIN
src/assets/icons/feed.png
Normal file
BIN
src/assets/icons/feed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
41
src/assets/scripts/components/now-playing.js
Normal file
41
src/assets/scripts/components/now-playing.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
class NowPlaying extends HTMLElement {
|
||||
static tagName = "now-playing";
|
||||
|
||||
static register(tagName = this.tagName, registry = globalThis.customElements) {
|
||||
registry.define(tagName, this);
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.contentElement = this.querySelector(".content");
|
||||
if (!this.contentElement) return;
|
||||
|
||||
const cache = localStorage.getItem("now-playing-cache");
|
||||
if (cache) this.updateHTML(JSON.parse(cache));
|
||||
|
||||
await this.fetchAndUpdate();
|
||||
}
|
||||
|
||||
async fetchAndUpdate() {
|
||||
try {
|
||||
const data = await this.fetchData();
|
||||
const newHTML = data?.content;
|
||||
|
||||
if (newHTML && newHTML !== this.contentElement.innerHTML) {
|
||||
this.updateHTML(newHTML);
|
||||
localStorage.setItem("now-playing-cache", JSON.stringify(newHTML));
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
updateHTML(value) {
|
||||
this.contentElement.innerHTML = value;
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
return fetch(`/api/playing.php?nocache=${Date.now()}`)
|
||||
.then(response => response.json())
|
||||
.catch(() => ({}));
|
||||
}
|
||||
}
|
||||
|
||||
NowPlaying.register();
|
48
src/assets/scripts/components/select-pagination.js
Normal file
48
src/assets/scripts/components/select-pagination.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
class SelectPagination extends HTMLElement {
|
||||
static register(tagName = 'select-pagination') {
|
||||
if ("customElements" in window) customElements.define(tagName, this)
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['data-base-index']
|
||||
}
|
||||
|
||||
get baseIndex() {
|
||||
return this.getAttribute('data-base-index') || 0
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) return
|
||||
|
||||
this.attachShadow({ mode: 'open' }).appendChild(document.createElement('slot'))
|
||||
|
||||
const uriSegments = window.location.pathname.split('/').filter(Boolean)
|
||||
let pageNumber = this.extractPageNumber(uriSegments) || 0
|
||||
|
||||
this.control = this.querySelector('select')
|
||||
this.control.value = pageNumber
|
||||
this.control.addEventListener('change', (event) => {
|
||||
pageNumber = parseInt(event.target.value)
|
||||
const updatedUrlSegments = this.updateUrlSegments(uriSegments, pageNumber)
|
||||
window.location.href = `${window.location.origin}/${updatedUrlSegments.join('/')}`
|
||||
})
|
||||
}
|
||||
|
||||
extractPageNumber(segments) {
|
||||
const lastSegment = segments[segments.length - 1]
|
||||
return !isNaN(lastSegment) ? parseInt(lastSegment) : null
|
||||
}
|
||||
|
||||
updateUrlSegments(segments, pageNumber) {
|
||||
if (!isNaN(segments[segments.length - 1])) {
|
||||
segments[segments.length - 1] = pageNumber.toString()
|
||||
} else {
|
||||
segments.push(pageNumber.toString())
|
||||
}
|
||||
|
||||
if (pageNumber === parseInt(this.baseIndex)) segments.pop()
|
||||
return segments
|
||||
}
|
||||
}
|
||||
|
||||
SelectPagination.register()
|
72
src/assets/scripts/index.js
Normal file
72
src/assets/scripts/index.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
window.addEventListener("load", () => {
|
||||
// dialog controls
|
||||
(() => {
|
||||
if (document.querySelectorAll(".modal-open").length) {
|
||||
document.querySelectorAll(".modal-open").forEach((button) => {
|
||||
const modalId = button.getAttribute("data-modal-trigger");
|
||||
const dialog = document.getElementById(`dialog-${modalId}`);
|
||||
|
||||
if (!dialog) return;
|
||||
|
||||
const closeButton = dialog.querySelector(".modal-close");
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
dialog.showModal();
|
||||
dialog.classList.remove("closing");
|
||||
});
|
||||
|
||||
if (closeButton)
|
||||
closeButton.addEventListener("click", () => {
|
||||
dialog.classList.add("closing");
|
||||
setTimeout(() => dialog.close(), 200);
|
||||
});
|
||||
|
||||
dialog.addEventListener("click", (event) => {
|
||||
const rect = dialog.getBoundingClientRect();
|
||||
|
||||
if (
|
||||
event.clientX < rect.left ||
|
||||
event.clientX > rect.right ||
|
||||
event.clientY < rect.top ||
|
||||
event.clientY > rect.bottom
|
||||
) {
|
||||
dialog.classList.add("closing");
|
||||
setTimeout(() => dialog.close(), 200);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
dialog.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
dialog.classList.add("closing");
|
||||
setTimeout(() => dialog.close(), 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// text toggle for media pages
|
||||
(() => {
|
||||
const button = document.querySelector("[data-toggle-button]");
|
||||
const content = document.querySelector("[data-toggle-content]");
|
||||
const text = document.querySelectorAll("[data-toggle-content] p");
|
||||
const minHeight = 500; // this needs to match the height set on [data-toggle-content].text-toggle-hidden in text-toggle.css
|
||||
const interiorHeight = Array.from(text).reduce(
|
||||
(acc, node) => acc + node.scrollHeight,
|
||||
0,
|
||||
);
|
||||
|
||||
if (!button || !content || !text) return;
|
||||
|
||||
if (interiorHeight < minHeight) {
|
||||
content.classList.remove("text-toggle-hidden");
|
||||
button.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
const isHidden = content.classList.toggle("text-toggle-hidden");
|
||||
button.textContent = isHidden ? "Show more" : "Show less";
|
||||
});
|
||||
})();
|
||||
});
|
31
src/assets/styles/base/fonts.css
Normal file
31
src/assets/styles/base/fonts.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
@font-face {
|
||||
font-family: "MonoLisa";
|
||||
src: url("/assets/fonts/ml.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lexend';
|
||||
src: url('/assets/fonts/ll.woff2') format('woff2');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lexend';
|
||||
src: url('/assets/fonts/lb.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Space Grotesk";
|
||||
src: url("/assets/fonts/sg.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
515
src/assets/styles/base/index.css
Normal file
515
src/assets/styles/base/index.css
Normal file
|
@ -0,0 +1,515 @@
|
|||
html,
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-weight: var(--font-weight-light);
|
||||
color: var(--text-color);
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-color: var(--accent-color) var(--gray-light);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: var(--sizing-md);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--gray-light);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-color);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-color-hover);
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: var(--color-lightest);
|
||||
background: var(--accent-color);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
|
||||
mark {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--text-color-inverted);
|
||||
background-color: var(--section-color, var(--accent-color));
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
& > * {
|
||||
color: var(--text-color-inverted);
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--gray-dark);
|
||||
padding-left: var(--spacing-lg);
|
||||
border-left: var(--sizing-xs) solid var(--gray-dark);
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
|
||||
:is(h1, h2, h3) svg {
|
||||
stroke-width: var(--stroke-width-bold);
|
||||
}
|
||||
|
||||
strong,
|
||||
blockquote {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
em,
|
||||
blockquote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: var(--sizing-svg);
|
||||
height: var(--sizing-svg);
|
||||
stroke: var(--icon-color);
|
||||
stroke-width: var(--stroke-width-default);
|
||||
}
|
||||
|
||||
/* images */
|
||||
img {
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
&.image-banner {
|
||||
border: var(--border-gray);
|
||||
height: auto;
|
||||
width: var(--sizing-full);
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
}
|
||||
|
||||
/* lists */
|
||||
ul,
|
||||
ol {
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
|
||||
&:not(.standalone) {
|
||||
padding-left: var(--spacing-base);
|
||||
}
|
||||
|
||||
li:not(:last-child) {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
}
|
||||
|
||||
/* brand + section colors */
|
||||
.article,
|
||||
.books,
|
||||
.brand-github,
|
||||
.brand-mastodon,
|
||||
.brand-npm,
|
||||
.calendar,
|
||||
.coffee,
|
||||
.concerts,
|
||||
.country,
|
||||
.device-tv-old,
|
||||
.device-watch,
|
||||
.error,
|
||||
.favorite,
|
||||
.github,
|
||||
.headphones,
|
||||
.heart-handshake,
|
||||
.info-circle,
|
||||
.link,
|
||||
.mail,
|
||||
.mail-plus,
|
||||
.mastodon,
|
||||
.movies,
|
||||
.music,
|
||||
.npm,
|
||||
.old-post,
|
||||
.rss,
|
||||
.search,
|
||||
.tattoo,
|
||||
.tv,
|
||||
.warning {
|
||||
&.article {
|
||||
--section-color: var(--article);
|
||||
}
|
||||
&.books {
|
||||
--section-color: var(--books);
|
||||
}
|
||||
&.brand-github,
|
||||
&.github {
|
||||
--section-color: var(--brand-github);
|
||||
}
|
||||
&.brand-mastodon,
|
||||
&.mastodon {
|
||||
--section-color: var(--brand-mastodon);
|
||||
}
|
||||
&.brand-npm {
|
||||
--section-color: var(--brand-npm);
|
||||
}
|
||||
&.calendar {
|
||||
--section-color: var(--calendar);
|
||||
}
|
||||
&.coffee {
|
||||
--section-color: var(--brand-buy-me-a-coffee);
|
||||
}
|
||||
&.concerts {
|
||||
--section-color: var(--concerts);
|
||||
}
|
||||
&.country {
|
||||
--section-color: var(--country);
|
||||
}
|
||||
&.device-tv-old {
|
||||
--section-color: var(--tv);
|
||||
}
|
||||
&.device-watch {
|
||||
--section-color: var(--now);
|
||||
}
|
||||
&.error {
|
||||
--section-color: var(--error);
|
||||
}
|
||||
&.favorite {
|
||||
--section-color: var(--favorite);
|
||||
}
|
||||
&.headphones {
|
||||
--section-color: var(--music);
|
||||
}
|
||||
&.heart-handshake {
|
||||
--section-color: var(--webrings);
|
||||
}
|
||||
&.info-circle {
|
||||
--section-color: var(--about);
|
||||
}
|
||||
&.link {
|
||||
--section-color: var(--link);
|
||||
}
|
||||
&.mail {
|
||||
--section-color: var(--brand-proton);
|
||||
}
|
||||
&.mail-plus {
|
||||
--section-color: var(--newsletter);
|
||||
}
|
||||
&.movies,
|
||||
&.tv {
|
||||
--section-color: var(--tv);
|
||||
}
|
||||
&.music {
|
||||
--section-color: var(--music);
|
||||
}
|
||||
&.npm {
|
||||
--section-color: var(--brand-npm);
|
||||
}
|
||||
&.old-post {
|
||||
--section-color: var(--gray-dark);
|
||||
}
|
||||
&.rss {
|
||||
--section-color: var(--brand-rss);
|
||||
}
|
||||
&.search {
|
||||
--section-color: var(--search);
|
||||
}
|
||||
&.tattoo {
|
||||
--section-color: var(--tattoo);
|
||||
}
|
||||
&.warning {
|
||||
--section-color: var(--warning);
|
||||
}
|
||||
|
||||
--icon-color: var(--section-color);
|
||||
--link-color: var(--section-color);
|
||||
--banner-border-color: var(--section-color);
|
||||
|
||||
color: var(--section-color);
|
||||
}
|
||||
|
||||
/* links */
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-underline-offset: var(--underline-offset-default);
|
||||
transition: color var(--transition-duration-default) var(--transition-ease-in-out),
|
||||
text-underline-offset var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
|
||||
img {
|
||||
border: var(--border-default);
|
||||
filter: var(--filter-image-default);
|
||||
transition: filter var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: var(--transform-icon-default);
|
||||
}
|
||||
|
||||
&.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-base);
|
||||
|
||||
&:is(:hover, :focus, :active) svg {
|
||||
transform: var(--transform-icon-default);
|
||||
}
|
||||
}
|
||||
|
||||
--icon-color: var(--accent-color);
|
||||
|
||||
&:is(:hover, :focus, :active) {
|
||||
color: var(--link-color-hover);
|
||||
text-underline-offset: var(--underline-offset-hover);
|
||||
|
||||
img {
|
||||
filter: var(--filter-image-light);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
filter: var(--filter-image-dark);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: transform var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
transform: var(--transform-icon-tilt);
|
||||
}
|
||||
|
||||
--icon-color: var(--accent-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, a, p, span, th, td, article aside):has(svg):not(.back-link) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* headers */
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-md);
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
|
||||
a:is(:hover, :focus, :active) svg {
|
||||
transform: var(--transform-icon-default);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-xl);
|
||||
|
||||
&.page-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
h1 {
|
||||
font-size: var(--font-size-3xl);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
}
|
||||
|
||||
/* dividers */
|
||||
hr {
|
||||
color: var(--gray-light);
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
|
||||
/* articles */
|
||||
time {
|
||||
color: var(--gray-dark);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
article {
|
||||
margin-bottom: var(--spacing-base);
|
||||
|
||||
&:not([class], :last-of-type) {
|
||||
border-bottom: var(--border-gray);
|
||||
}
|
||||
|
||||
&.intro p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
aside {
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
button {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
svg {
|
||||
--icon-color: var(--gray-dark);
|
||||
--sizing-svg: var(--sizing-svg-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.footnotes ol p,
|
||||
.footnotes-list p {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
/* tables */
|
||||
table {
|
||||
display: block;
|
||||
border: var(--border-gray);
|
||||
border-radius: var(--border-radius-slight);
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
caption-side: bottom;
|
||||
overscroll-behavior: none;
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
:is(th, td):not(:first-child, :last-child) {
|
||||
border-right: var(--border-gray);
|
||||
}
|
||||
|
||||
th,
|
||||
tr:not(:last-child) {
|
||||
border-bottom: var(--border-gray);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: var(--spacing-sm);
|
||||
word-break: break-word;
|
||||
|
||||
&:first-child {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
max-width: calc(var(--sizing-3xl) * 5.5);
|
||||
border-inline-end: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: 0;
|
||||
width: 1px;
|
||||
height: var(--sizing-full);
|
||||
background: var(--gray-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: var(--font-weight-bold);
|
||||
background-color: var(--gray-lighter);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
min-width: calc(var(--spacing-3xl) * 2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&:first-child {
|
||||
background: var(--background-color);
|
||||
width: var(--sizing-full);
|
||||
}
|
||||
}
|
||||
|
||||
td:first-of-type,
|
||||
:where(thead, tfoot) th:nth-child(2) {
|
||||
border-inline-start: none;
|
||||
}
|
||||
|
||||
/* header */
|
||||
.main-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-base);
|
||||
width: var(--sizing-full);
|
||||
padding-top: var(--spacing-3xl);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* nav */
|
||||
.active,
|
||||
.active svg {
|
||||
--icon-color: var(--accent-color-active);
|
||||
|
||||
cursor: not-allowed;
|
||||
color: var(--accent-color-active);
|
||||
}
|
||||
|
||||
/* layout */
|
||||
.default-wrapper {
|
||||
padding-top: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 1 0%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
main,
|
||||
footer {
|
||||
width: calc(var(--sizing-full) * .8);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: var(--sizing-3xl) auto 0;
|
||||
|
||||
.updated {
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
128
src/assets/styles/base/reset.css
Normal file
128
src/assets/styles/base/reset.css
Normal file
|
@ -0,0 +1,128 @@
|
|||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:where([hidden]:not([hidden='until-found'])) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:where(html) {
|
||||
font-size: var(--sizing-full);
|
||||
-webkit-text-size-adjust: none;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
:where(html:has(dialog:modal[open])) {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:where(html:focus-within) {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
:where(body) {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-base);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: var(--sizing-full);
|
||||
}
|
||||
|
||||
:where(button) {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
:where(input, button, textarea, select) {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
:where(textarea) {
|
||||
resize: vertical;
|
||||
resize: block;
|
||||
}
|
||||
|
||||
:where(button, label, select, summary, [role='button'], [role='option']) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:where(:disabled) {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:where(label:has(> input:disabled), label:has(+ input:disabled)) {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:where(a) {
|
||||
color: inherit;
|
||||
text-underline-offset: var(--spacing-xs);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: number;
|
||||
}
|
||||
|
||||
:where(ul, ol) {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
:where(img, svg, video, canvas, audio, iframe, embed, object) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:where(p, h1, h2, h3) {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
:where(hr) {
|
||||
border: none;
|
||||
border-block-start: 1px solid;
|
||||
border-block-start-color: currentColor;
|
||||
color: inherit;
|
||||
block-size: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:where(dialog, [popover]) {
|
||||
border: none;
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
:where(dialog:not([open], [popover]), [popover]:not(:popover-open)) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
:where(:focus-visible) {
|
||||
outline: var(--border-default);
|
||||
outline-offset: 1px;
|
||||
border-radius: var(--border-radius-slight);
|
||||
box-shadow: 0 0 0 1px var(--accent-color);
|
||||
}
|
||||
|
||||
:where(:focus-visible, :target) {
|
||||
scroll-margin-block: 8vh;
|
||||
}
|
||||
|
||||
:where(.visually-hidden:not(:focus-within, :active)) {
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
width: 1px !important;
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
white-space: nowrap !important;
|
||||
border: 0 !important;
|
||||
user-select: none !important;
|
||||
}
|
182
src/assets/styles/base/vars.css
Normal file
182
src/assets/styles/base/vars.css
Normal file
|
@ -0,0 +1,182 @@
|
|||
:root {
|
||||
/* colors */
|
||||
--blue-100: #a2c4ff;
|
||||
--blue-200: #6b9eff;
|
||||
--blue-300: #3364ff;
|
||||
--blue-400: #1e42c7;
|
||||
|
||||
--gray-100: #f9fafb;
|
||||
--gray-200: #eceef1;
|
||||
--gray-300: #dfe3e8;
|
||||
--gray-400: #959eae;
|
||||
--gray-500: #7f899b;
|
||||
--gray-600: #626d7f;
|
||||
--gray-700: #545e71;
|
||||
--gray-800: #4a5365;
|
||||
--gray-900: #14161a;
|
||||
|
||||
--gray-lighter: light-dark(var(--gray-200), var(--gray-700));
|
||||
--gray-light: light-dark(var(--gray-300), var(--gray-600));
|
||||
--gray-medium: var(--gray-400);
|
||||
--gray-dark: light-dark(var(--gray-800), var(--gray-300));
|
||||
|
||||
/* base theme */
|
||||
--color-lightest: var(--gray-100);
|
||||
--color-darkest: var(--gray-900);
|
||||
|
||||
--accent-color: light-dark(var(--blue-300), var(--blue-200));
|
||||
--accent-color-hover: light-dark(var(--blue-400), var(--blue-100));
|
||||
--accent-color-active: light-dark(var(--blue-400), var(--blue-100));
|
||||
|
||||
--text-color: light-dark(var(--color-darkest), var(--color-lightest));
|
||||
--text-color-inverted: light-dark(var(--color-lightest), var(--color-darkest));
|
||||
--link-color: var(--accent-color);
|
||||
--link-color-hover: var(--accent-color-hover);
|
||||
--icon-color: var(--text-color);
|
||||
--disabled-color: var(--gray-medium);
|
||||
--code-color: light-dark(#6a3e9a, #d7a8ff);
|
||||
|
||||
--background-color: light-dark(var(--color-lightest), var(--color-darkest));
|
||||
--background-color-inverted: light-dark(var(--color-darkest), var(--color-lightest));
|
||||
|
||||
--brand-buy-me-a-coffee: light-dark(#9500ff, #ffdd00);
|
||||
--brand-github: light-dark(#333, #f5f5f5);
|
||||
--brand-mastodon: light-dark(#563acc, #858afa);
|
||||
--brand-npm: #cb3837;
|
||||
--brand-proton: light-dark(#6d4af6, #c4b7ff);
|
||||
--brand-rss: light-dark(#c24f19, #f26522);
|
||||
|
||||
--article: light-dark(#007272, #00ffff);
|
||||
--about: light-dark(#e4513a, #ff967d);
|
||||
--books: light-dark(#8b4513, #5fa050);
|
||||
--calendar: light-dark(#2c5c2c, #7ed97e);
|
||||
--concerts: light-dark(#b3365c, #ff82aa);
|
||||
--country: light-dark(#146a67, #80dcdc);
|
||||
--error: light-dark(#b81f1f, #ff8b8b);
|
||||
--favorite: light-dark(#b03c72, #ff9ccd);
|
||||
--link: light-dark(#7b5cba, #e2b8ff);
|
||||
--music: light-dark(#3d7099, #76b8cc);
|
||||
--newsletter: light-dark(#37b0b0, #91fffa);
|
||||
--now: light-dark(#cc1076, #ff82d5);
|
||||
--search: light-dark(#6b5e3a, #c0b594);
|
||||
--tattoo: light-dark(#951b1b, #ff7373);
|
||||
--tv: light-dark(#cc3600, #d65f2b);
|
||||
--warning: light-dark(#cc6f00, #ffbf66);
|
||||
--webrings: light-dark(#b054b0, #ffb3ff);
|
||||
|
||||
/* borders */
|
||||
--border-default: 1px solid var(--accent-color);
|
||||
--border-default-hover: 1px solid var(--accent-color-hover);
|
||||
--border-gray: 1px solid var(--gray-light);
|
||||
|
||||
/* fonts */
|
||||
--font-body: "Lexend", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-heading: "Space Grotesk", "Arial Black", "Arial Bold", Gadget, sans-serif;
|
||||
--font-code: "MonoLisa", SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
|
||||
/* text */
|
||||
--font-size-xs: 0.7rem;
|
||||
--font-size-sm: 0.85rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.15rem;
|
||||
--font-size-xl: 1.3rem;
|
||||
--font-size-2xl: 1.45rem;
|
||||
--font-size-3xl: 1.6rem;
|
||||
|
||||
--font-weight-light: 300;
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
--line-height-sm: 1;
|
||||
--line-height-md: 1.5;
|
||||
--line-height-base: 2;
|
||||
|
||||
--underline-offset-default: 3px;
|
||||
--underline-offset-hover: 4px;
|
||||
|
||||
/* sizing */
|
||||
--sizing-xs: 0.25rem;
|
||||
--sizing-sm: 0.5rem;
|
||||
--sizing-md: 0.75rem;
|
||||
--sizing-lg: 1rem;
|
||||
--sizing-base: 1.5rem;
|
||||
--sizing-xl: 1.75rem;
|
||||
--sizing-2xl: 2rem;
|
||||
--sizing-3xl: 2.25rem;
|
||||
--sizing-full: 100%;
|
||||
|
||||
--sizing-svg-sm: 18px;
|
||||
--sizing-svg: 24px;
|
||||
|
||||
/* spacing */
|
||||
--spacing-xs: var(--sizing-xs);
|
||||
--spacing-sm: var(--sizing-sm);
|
||||
--spacing-md: var(--sizing-md);
|
||||
--spacing-lg: var(--sizing-lg);
|
||||
--spacing-base: var(--sizing-base);
|
||||
--spacing-xl: var(--sizing-xl);
|
||||
--spacing-2xl: var(--sizing-2xl);
|
||||
--spacing-3xl: var(--sizing-3xl);
|
||||
|
||||
--margin-vertical-base-horizontal-zero: var(--spacing-base) 0;
|
||||
|
||||
/* radii */
|
||||
--border-radius-slight: var(--sizing-xs);
|
||||
--border-radius-full: 9999px;
|
||||
|
||||
/* aspect ratios */
|
||||
--aspect-ratio-square: 1/1;
|
||||
--aspect-ratio-vertical: 2/3;
|
||||
--aspect-ratio-banner: 3/2;
|
||||
--aspect-ratio-video: 16/9;
|
||||
|
||||
--aspect-ratio: var(--aspect-ratio-square);
|
||||
|
||||
/* grid columns */
|
||||
--grid-columns-one: repeat(1, minmax(0, 1fr));
|
||||
--grid-columns-two: repeat(2, minmax(0, 1fr));
|
||||
--grid-columns-three: repeat(3, minmax(0, 1fr));
|
||||
--grid-columns-four: repeat(4, minmax(0, 1fr));
|
||||
--grid-columns-six: repeat(6, minmax(0, 1fr));
|
||||
|
||||
--grid-square: var(--grid-columns-two);
|
||||
--grid-vertical: var(--grid-columns-three);
|
||||
|
||||
--grid-shape: var(--grid-square);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
--grid-square: var(--grid-columns-four);
|
||||
--grid-vertical: var(--grid-columns-six);
|
||||
}
|
||||
|
||||
/* transitions */
|
||||
--transition-ease-in-out: cubic-bezier(.4, 0, .2, 1);
|
||||
--transition-duration-default: 250ms;
|
||||
|
||||
/* transforms */
|
||||
--transform-icon-default: rotate(0);
|
||||
--transform-icon-tilt: rotate(7.5deg);
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
--transform-icon-tilt: var(--transform-icon-default);
|
||||
--underline-offset-hover: var(--underline-offset-default);
|
||||
}
|
||||
|
||||
/* filters */
|
||||
--filter-image-default: contrast(1) saturate(1) brightness(1);
|
||||
--filter-image-light: contrast(1.2) saturate(1.2) brightness(0.9);
|
||||
--filter-image-dark: contrast(1.1) saturate(1.1) brightness(1.1);
|
||||
|
||||
/* svgs */
|
||||
--stroke-width-default: 1.3;
|
||||
--stroke-width-bold: 2;
|
||||
|
||||
/* shadows */
|
||||
--box-shadow-text-toggle: inset 0 -120px 60px -60px var(--background-color);
|
||||
|
||||
/* modals */
|
||||
--modal-overlay-background: light-dark(#ffffffbf, #000000bf);
|
||||
|
||||
/* input accent color */
|
||||
accent-color: var(--accent-color);
|
||||
}
|
22
src/assets/styles/components/banners.css
Normal file
22
src/assets/styles/components/banners.css
Normal file
|
@ -0,0 +1,22 @@
|
|||
.banner {
|
||||
padding: var(--spacing-md);
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
border: 1px solid;
|
||||
border-color: var(--banner-border-color);
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
svg {
|
||||
--sizing-svg: var(--sizing-svg-sm);
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
margin: 0;
|
||||
|
||||
svg {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
26
src/assets/styles/components/buttons.css
Normal file
26
src/assets/styles/components/buttons.css
Normal file
|
@ -0,0 +1,26 @@
|
|||
@import url("./tab-buttons.css");
|
||||
@import url("./text-toggle.css");
|
||||
|
||||
button:not([data-modal-button]),
|
||||
.button {
|
||||
appearance: none;
|
||||
border: 2px solid var(--accent-color);
|
||||
border-radius: var(--border-radius-full);
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--line-height-base);
|
||||
white-space: nowrap;
|
||||
color: var(--text-color-inverted);
|
||||
background-color: var(--accent-color);
|
||||
transition: color var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
|
||||
&:not(.active):is(:hover, :active, :focus, :focus-within) {
|
||||
background-color: var(--accent-color-hover);
|
||||
border: 2px solid var(--accent-color-hover);
|
||||
transition:
|
||||
background-color var(--transition-duration-default) var(--transition-ease-in-out),
|
||||
border var(--transition-duration-default) var(--transition-ease-in-out),
|
||||
color var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
}
|
||||
}
|
79
src/assets/styles/components/forms.css
Normal file
79
src/assets/styles/components/forms.css
Normal file
|
@ -0,0 +1,79 @@
|
|||
::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
|
||||
textarea {
|
||||
width: var(--sizing-full);
|
||||
}
|
||||
|
||||
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
|
||||
textarea,
|
||||
select {
|
||||
color: var(--text-color);
|
||||
border-radius: var(--border-radius-slight);
|
||||
background-color: var(--background-color);
|
||||
padding: var(--spacing-sm);
|
||||
border: var(--border-gray);
|
||||
}
|
||||
|
||||
form,
|
||||
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]),
|
||||
textarea {
|
||||
margin-bottom: var(--spacing-base);
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.search__form {
|
||||
margin-top: 0;
|
||||
|
||||
.search__form--input::-webkit-search-cancel-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
button svg,
|
||||
label svg {
|
||||
stroke: var(--section-color, var(--accent-color));
|
||||
cursor: pointer;
|
||||
|
||||
&:is(:hover, :focus, :active) {
|
||||
stroke: var(--accent-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.search__form--type {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
margin-top: var(--spacing-md);
|
||||
border: none;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
.search__results {
|
||||
margin: 0 0 var(--spacing-base);
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
|
||||
li {
|
||||
margin: var(--spacing-sm) 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--spacing-base);
|
||||
border-bottom: var(--border-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search__load-more {
|
||||
margin-bottom: var(--spacing-base);
|
||||
}
|
69
src/assets/styles/components/media-grid.css
Normal file
69
src/assets/styles/components/media-grid.css
Normal file
|
@ -0,0 +1,69 @@
|
|||
.media-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-sm);
|
||||
grid-template-columns: var(--grid-shape);
|
||||
|
||||
a {
|
||||
aspect-ratio: var(--aspect-ratio);
|
||||
}
|
||||
|
||||
& + .media-grid {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
--grid-shape: var(--grid-vertical);
|
||||
|
||||
a {
|
||||
--aspect-ratio: var(--aspect-ratio-vertical);
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: var(--sizing-full);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.media-grid-item {
|
||||
position: relative;
|
||||
border-radius: var(--border-radius-slight);
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: var(--spacing-sm);
|
||||
bottom: var(--spacing-sm);
|
||||
max-width: calc(var(--sizing-full) - calc(var(--spacing-sm) * 2));
|
||||
color: var(--text-color-inverted);
|
||||
background-color: var(--section-color, var(--accent-color));
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
& > * {
|
||||
color: var(--text-color-inverted);
|
||||
}
|
||||
|
||||
.header,
|
||||
.subheader {
|
||||
line-height: var(--line-height-md);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 6;
|
||||
line-clamp: 6;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.subheader {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
}
|
||||
}
|
92
src/assets/styles/components/modal.css
Normal file
92
src/assets/styles/components/modal.css
Normal file
|
@ -0,0 +1,92 @@
|
|||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: scale(0.95); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; transform: scale(1); }
|
||||
to { opacity: 0; transform: scale(0.95); }
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
background: var(--modal-overlay-background);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.modal-wrapper,
|
||||
.modal-body {
|
||||
inset: 0;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.modal-wrapper,
|
||||
.modal-body,
|
||||
dialog {
|
||||
width: var(--sizing-full);
|
||||
height: var(--sizing-full);
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
display: none;
|
||||
|
||||
&:checked ~ .modal-wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:not(:checked) ~ .modal-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-open,
|
||||
.modal-close {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
color: var(--section-color, var(--accent-color));
|
||||
transform: var(--transform-icon-default);
|
||||
transition: color var(--transition-duration-default) var(--transition-ease-in-out),
|
||||
transform var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
|
||||
|
||||
&:is(:hover, :focus, :active) {
|
||||
color: var(--link-color-hover);
|
||||
transform: var(--transform-icon-tilt);
|
||||
}
|
||||
}
|
||||
|
||||
dialog,
|
||||
.modal-body {
|
||||
background: var(--background-color);
|
||||
padding: var(--spacing-lg) var(--spacing-base);
|
||||
overflow-y: auto;
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
&.closing {
|
||||
animation: fadeOut var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
max-width: calc(var(--sizing-full) * .75);
|
||||
max-height: calc(var(--sizing-full) * .75);
|
||||
inset: calc(var(--sizing-full) * .125);
|
||||
border: var(--border-gray);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: var(--sizing-full);
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
animation: fadeIn var(--transition-duration-default) var(--transition-ease-in-out);
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: var(--modal-overlay-background);
|
||||
}
|
101
src/assets/styles/components/music-chart.css
Normal file
101
src/assets/styles/components/music-chart.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
.music-chart {
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
|
||||
ol {
|
||||
padding-left: 0;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
list-style-position: outside;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-item {
|
||||
display: grid;
|
||||
grid-template-columns: var(--grid-columns-one);
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
grid-template-columns: var(--grid-columns-two);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
progress {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: calc(var(--sizing-3xl) * 1.5);
|
||||
height: calc(var(--sizing-3xl) * 1.5);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
width: calc(var(--sizing-3xl) * 2);
|
||||
height: calc(var(--sizing-3xl) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.chart-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.chart-item-progress {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
||||
progress {
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: calc(var(--sizing-full) * .8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
padding-right: var(--spacing-base);
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.title,
|
||||
.subheader,
|
||||
time {
|
||||
line-height: var(--line-height-md);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.subheader,
|
||||
time {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
time {
|
||||
margin-top: var(--spacing-sm);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
src/assets/styles/components/nav/footer.css
Normal file
31
src/assets/styles/components/nav/footer.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
nav {
|
||||
&.social,
|
||||
&.sub-pages {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.social {
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
width: var(--sizing-full);
|
||||
|
||||
.icon > span,
|
||||
.active > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.sub-pages {
|
||||
font-size: var(--font-size-sm);
|
||||
padding-bottom: var(--spacing-3xl);
|
||||
gap: var(--sizing-sm);
|
||||
}
|
||||
}
|
25
src/assets/styles/components/nav/primary.css
Normal file
25
src/assets/styles/components/nav/primary.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.nav-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
gap: var(--spacing-md);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
& > li {
|
||||
margin-bottom: 0;
|
||||
|
||||
:is(.icon, .active) svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:is(.icon, .active) span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
19
src/assets/styles/components/paginator.css
Normal file
19
src/assets/styles/components/paginator.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: var(--spacing-base);
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a svg {
|
||||
transform: var(--transform-icon-default);
|
||||
}
|
||||
|
||||
span svg {
|
||||
cursor: not-allowed;
|
||||
stroke: var(--disabled-color);
|
||||
}
|
||||
}
|
23
src/assets/styles/components/progress-bar.css
Normal file
23
src/assets/styles/components/progress-bar.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
progress {
|
||||
appearance: none;
|
||||
height: var(--sizing-lg);
|
||||
width: var(--sizing-full);
|
||||
background-color: var(--gray-light);
|
||||
border-radius: var(--border-radius-full);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: var(--gray-light);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: var(--accent-color);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
background-color: var(--accent-color);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
34
src/assets/styles/components/tab-buttons.css
Normal file
34
src/assets/styles/components/tab-buttons.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
#tracks-recent,
|
||||
#tracks-chart,
|
||||
.tracks-recent,
|
||||
.tracks-chart {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tracks-recent:checked ~ .tracks-recent,
|
||||
#tracks-chart:checked ~ .tracks-chart {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[id="tracks-recent"] ~ .tracks-recent,
|
||||
input[id="tracks-chart"] ~ .tracks-chart {
|
||||
margin-top: var(--spacing-base);
|
||||
}
|
||||
|
||||
#tracks-recent:checked ~ [for="tracks-recent"],
|
||||
#tracks-chart:checked ~ [for="tracks-chart"] {
|
||||
cursor: not-allowed;
|
||||
border-color: var(--accent-color);
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
#tracks-recent:not(:checked) ~ [for="tracks-recent"],
|
||||
#tracks-chart:not(:checked) ~ [for="tracks-chart"] {
|
||||
color: var(--accent-color);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#tracks-recent:not(:checked) ~ [for="tracks-recent"]:is(:hover, :active),
|
||||
#tracks-chart:not(:checked) ~ [for="tracks-chart"]:is(:hover, :active) {
|
||||
color: var(--accent-color-hover);
|
||||
}
|
21
src/assets/styles/components/text-toggle.css
Normal file
21
src/assets/styles/components/text-toggle.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
[data-toggle-content].text-toggle-hidden {
|
||||
position: relative;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
|
||||
p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
content: "";
|
||||
box-shadow: var(--box-shadow-text-toggle);
|
||||
width: var(--sizing-full);
|
||||
height: calc(var(--sizing-full) * .2);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
13
src/assets/styles/components/youtube-player.css
Normal file
13
src/assets/styles/components/youtube-player.css
Normal file
|
@ -0,0 +1,13 @@
|
|||
youtube-video {
|
||||
aspect-ratio: var(--aspect-ratio-video);
|
||||
width: var(--sizing-full);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
border: var(--border-default);
|
||||
border-radius: var(--border-radius-slight);
|
||||
|
||||
&:hover {
|
||||
border: var(--border-default-hover);
|
||||
}
|
||||
}
|
37
src/assets/styles/index.css
Normal file
37
src/assets/styles/index.css
Normal file
|
@ -0,0 +1,37 @@
|
|||
@layer reset, defaults, base, page, components, plugins;
|
||||
|
||||
/* style resets */
|
||||
@import url("./base/reset.css") layer(reset);
|
||||
|
||||
/* core defaults */
|
||||
@import url("./base/fonts.css") layer(defaults);
|
||||
@import url("./base/vars.css") layer(defaults);
|
||||
|
||||
/* base styles */
|
||||
@import url("./base/index.css") layer(base);
|
||||
|
||||
/* page styles */
|
||||
@import url("./pages/about.css") layer(page);
|
||||
@import url("./pages/books.css") layer(page);
|
||||
@import url("./pages/contact.css") layer(page);
|
||||
@import url("./pages/links.css") layer(page);
|
||||
@import url("./pages/media.css") layer(page);
|
||||
@import url("./pages/music.css") layer(page);
|
||||
@import url("./pages/watching.css") layer(page);
|
||||
@import url("./pages/webrings.css") layer(page);
|
||||
|
||||
/* plugins */
|
||||
@import url("./plugins/prism.css") layer(plugins);
|
||||
|
||||
/* component styles */
|
||||
@import url("./components/banners.css") layer(components);
|
||||
@import url("./components/buttons.css") layer(components);
|
||||
@import url("./components/forms.css") layer(components);
|
||||
@import url("./components/media-grid.css") layer(components);
|
||||
@import url("./components/nav/primary.css") layer(components);
|
||||
@import url("./components/nav/footer.css") layer(components);
|
||||
@import url("./components/modal.css") layer(components);
|
||||
@import url("./components/music-chart.css") layer(components);
|
||||
@import url("./components/paginator.css") layer(components);
|
||||
@import url("./components/progress-bar.css") layer(components);
|
||||
@import url("./components/youtube-player.css") layer(components);
|
14
src/assets/styles/noscript.css
Normal file
14
src/assets/styles/noscript.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* generic helper to hide client side content */
|
||||
.client-side {
|
||||
display:none
|
||||
}
|
||||
|
||||
/* unset text toggle implementation on artist + genre pages */
|
||||
[data-toggle-content].text-toggle-hidden {
|
||||
height: unset !important;
|
||||
overflow: unset !important;
|
||||
margin-bottom: unset !important;
|
||||
}
|
||||
[data-toggle-content].text-toggle-hidden::after {
|
||||
display: none !important;
|
||||
}
|
21
src/assets/styles/pages/about.css
Normal file
21
src/assets/styles/pages/about.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
.avatar-wrapper {
|
||||
--avatar-size: 16rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: var(--sizing-full);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
--avatar-size: 24rem;
|
||||
}
|
||||
|
||||
img {
|
||||
width: var(--avatar-size);
|
||||
height: var(--avatar-size);
|
||||
}
|
||||
}
|
||||
|
||||
.about-title {
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
text-align: center;
|
||||
}
|
84
src/assets/styles/pages/books.css
Normal file
84
src/assets/styles/pages/books.css
Normal file
|
@ -0,0 +1,84 @@
|
|||
:is(.book-entry, .book-focus) img {
|
||||
height: auto;
|
||||
aspect-ratio: var(--aspect-ratio-vertical);
|
||||
}
|
||||
|
||||
.book-years a:first-of-type {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.book-entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
|
||||
&:not(:last-of-type) {
|
||||
padding-bottom: var(--spacing-base);
|
||||
border-bottom: var(--border-gray);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-base);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: calc(var(--sizing-3xl) * 4);
|
||||
}
|
||||
|
||||
.media-meta {
|
||||
margin-top: var(--sizing-base);
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
margin-top: 0;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.description p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
progress {
|
||||
margin-bottom: 0;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.book-focus .book-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-base);
|
||||
margin-bottom: var(--spacing-base);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.media-meta {
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.book-entry,
|
||||
.book-focus .book-display {
|
||||
progress {
|
||||
max-width: 85%;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
36
src/assets/styles/pages/contact.css
Normal file
36
src/assets/styles/pages/contact.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
.contact-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: var(--grid-columns-one);
|
||||
gap: var(--spacing-base);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
grid-template-columns: var(--grid-columns-two);
|
||||
}
|
||||
|
||||
.hp,
|
||||
label > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: calc(var(--sizing-3xl) * 5);
|
||||
}
|
||||
|
||||
.column.description {
|
||||
p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-success-wrapper {
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
19
src/assets/styles/pages/links.css
Normal file
19
src/assets/styles/pages/links.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.link-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-sm);
|
||||
grid-template-columns: var(--grid-columns-one);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
grid-template-columns: var(--grid-columns-two);
|
||||
}
|
||||
|
||||
.link-box {
|
||||
border: var(--border-gray);
|
||||
border-radius: var(--border-radius-slight);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
34
src/assets/styles/pages/media.css
Normal file
34
src/assets/styles/pages/media.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.media-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
width: var(--sizing-full);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub-meta {
|
||||
font-size: var(--font-size-sm);
|
||||
display: inline;
|
||||
|
||||
svg {
|
||||
--sizing-svg: var(--sizing-svg-sm);
|
||||
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-media {
|
||||
border: var(--border-gray);
|
||||
}
|
||||
|
||||
.associated-media {
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
}
|
||||
|
||||
.concerts {
|
||||
margin-top: var(--spacing-base);
|
||||
}
|
56
src/assets/styles/pages/music.css
Normal file
56
src/assets/styles/pages/music.css
Normal file
|
@ -0,0 +1,56 @@
|
|||
.artist-focus {
|
||||
.image-media {
|
||||
aspect-ratio: var(--aspect-ratio-square);
|
||||
width: var(--sizing-full);
|
||||
height: auto;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-width: calc(var(--sizing-3xl) * 6.75);
|
||||
}
|
||||
}
|
||||
|
||||
.artist-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-base);
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.media-meta {
|
||||
margin-top: var(--spacing-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table + p {
|
||||
font-size: var(--font-size-sm);
|
||||
margin: var(--spacing-base) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.music-ranking {
|
||||
img {
|
||||
border: var(--border-gray);
|
||||
}
|
||||
|
||||
td:first-child div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
text-wrap: auto;
|
||||
text-align: center;
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-md);
|
||||
text-align: unset;
|
||||
}
|
||||
}
|
||||
}
|
28
src/assets/styles/pages/watching.css
Normal file
28
src/assets/styles/pages/watching.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
.watching.hero {
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
aspect-ratio: var(--aspect-ratio-banner);
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
color: var(--color-lightest);
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: var(--spacing-sm);
|
||||
bottom: var(--spacing-sm);
|
||||
|
||||
.header {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.subheader {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.header,
|
||||
.subheader {
|
||||
line-height: var(--line-height-md);
|
||||
}
|
||||
}
|
||||
}
|
20
src/assets/styles/pages/webrings.css
Normal file
20
src/assets/styles/pages/webrings.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
.webring-wrapper,
|
||||
.webring-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.webring-wrapper {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
margin: var(--margin-vertical-base-horizontal-zero);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.webring-navigation {
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
}
|
114
src/assets/styles/plugins/prism.css
Normal file
114
src/assets/styles/plugins/prism.css
Normal file
|
@ -0,0 +1,114 @@
|
|||
code,
|
||||
pre {
|
||||
color: var(--blue-100);
|
||||
background: none;
|
||||
border-radius: var(--border-radius-slight);
|
||||
font-family: var(--font-code);
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-size: var(--font-size-sm);
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: var(--line-height-md);
|
||||
tab-size: 2;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: var(--spacing-lg);
|
||||
margin: var(--sizing-xl) 0;
|
||||
overflow: auto;
|
||||
background: var(--gray-900);
|
||||
border: var(--border-gray);
|
||||
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:not(pre) > code {
|
||||
color: var(--code-color);
|
||||
background: light-dark(var(--gray-200), var(--gray-900));
|
||||
border: var(--border-gray);
|
||||
padding: var(--spacing-xs);
|
||||
white-space: normal
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #99ccff;
|
||||
}
|
||||
|
||||
.token {
|
||||
color: var(--blue-200);
|
||||
|
||||
&.comment,
|
||||
&.prolog,
|
||||
&.doctype,
|
||||
&.cdata {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
&.punctuation {
|
||||
color: var(--gray-300);
|
||||
}
|
||||
|
||||
&.boolean,
|
||||
&.number {
|
||||
color: var(--blue-300);
|
||||
}
|
||||
|
||||
&.selector,
|
||||
&.attr-name,
|
||||
&.string,
|
||||
&.char,
|
||||
&.builtin,
|
||||
&.inserted {
|
||||
color: #6fff6f;
|
||||
}
|
||||
|
||||
&.operator,
|
||||
&.entity,
|
||||
&.url,
|
||||
&.variable {
|
||||
color: #99ccff;
|
||||
}
|
||||
|
||||
&.atrule,
|
||||
&.attr-value,
|
||||
&.function,
|
||||
&.class-name {
|
||||
color: #ff8f66;
|
||||
}
|
||||
|
||||
&.keyword {
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
&.regex,
|
||||
&.important {
|
||||
color: #ff7373;
|
||||
}
|
||||
|
||||
&.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
&.important,
|
||||
&.bold {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
3
src/assets/styles/styles.json
Normal file
3
src/assets/styles/styles.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"eleventyExcludeFromCollections": true
|
||||
}
|
60
src/data/albumReleases.js
Normal file
60
src/data/albumReleases.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAlbumReleases = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_album_releases`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const pacificNow = new Date().toLocaleString("en-US", {
|
||||
timeZone: "America/Los_Angeles",
|
||||
});
|
||||
const pacificDate = new Date(pacificNow);
|
||||
pacificDate.setHours(0, 0, 0, 0);
|
||||
const todayTimestamp = pacificDate.getTime() / 1000;
|
||||
|
||||
const all = data
|
||||
.map((album) => {
|
||||
const releaseDate = new Date(album.release_timestamp * 1000);
|
||||
|
||||
return {
|
||||
...album,
|
||||
description: album.artist?.description || "No description",
|
||||
date: releaseDate.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
}),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.release_timestamp - b.release_timestamp);
|
||||
|
||||
const upcoming = all.filter(
|
||||
(album) =>
|
||||
album.release_timestamp > todayTimestamp &&
|
||||
album.total_plays === 0,
|
||||
);
|
||||
|
||||
return { all, upcoming };
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing album releases:", error);
|
||||
return { all: [], upcoming: [] };
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchAlbumReleases();
|
||||
}
|
27
src/data/allActivity.js
Normal file
27
src/data/allActivity.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
export default async function fetchAllActivity() {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_all_activity`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data?.[0] || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching activity:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
26
src/data/blogroll.js
Normal file
26
src/data/blogroll.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchBlogroll = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_blogroll`, {
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing the blogroll:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchBlogroll();
|
||||
}
|
57
src/data/books.js
Normal file
57
src/data/books.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllBooks = async () => {
|
||||
try {
|
||||
return await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_books?order=date_finished.desc`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching books:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const sortBooksByYear = (books) => {
|
||||
const years = {};
|
||||
books.forEach((book) => {
|
||||
const year = book.year;
|
||||
if (!years[year]) {
|
||||
years[year] = { value: year, data: [book] };
|
||||
} else {
|
||||
years[year].data.push(book);
|
||||
}
|
||||
});
|
||||
return Object.values(years).filter((year) => year.value);
|
||||
};
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
export default async function () {
|
||||
const books = await fetchAllBooks();
|
||||
const sortedByYear = sortBooksByYear(books);
|
||||
const booksForCurrentYear =
|
||||
sortedByYear
|
||||
.find((yearGroup) => yearGroup.value === currentYear)
|
||||
?.data.filter((book) => book.status === "finished") || [];
|
||||
|
||||
return {
|
||||
all: books,
|
||||
years: sortedByYear,
|
||||
currentYear: booksForCurrentYear,
|
||||
feed: books.filter((book) => book.feed),
|
||||
daysRead: books[0]?.days_read
|
||||
};
|
||||
}
|
38
src/data/concerts.js
Normal file
38
src/data/concerts.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllConcerts = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_concerts`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching concerts:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const processConcerts = (concerts) =>
|
||||
concerts.map((concert) => ({
|
||||
...concert,
|
||||
artist: concert.artist || { name: concert.artist_name_string, url: null },
|
||||
}));
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
const concerts = await fetchAllConcerts();
|
||||
return processConcerts(concerts);
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing concerts data:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
56
src/data/feeds.js
Normal file
56
src/data/feeds.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchFeeds = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_feeds?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching feed metadata:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchFeedData = async (feedKey) => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/rpc/get_feed_data`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({ feed_key: feedKey }),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching feed data for ${feedKey}:`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
const feeds = await fetchFeeds();
|
||||
const feedsWithData = [];
|
||||
|
||||
for (const feed of feeds) {
|
||||
feedsWithData.push({
|
||||
...feed,
|
||||
entries: await fetchFeedData(feed.data),
|
||||
});
|
||||
}
|
||||
|
||||
return feedsWithData;
|
||||
};
|
31
src/data/globals.js
Normal file
31
src/data/globals.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchGlobals = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_globals?select=*`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return data[0];
|
||||
} catch (error) {
|
||||
console.error("Error fetching globals:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchGlobals();
|
||||
}
|
26
src/data/headers.js
Normal file
26
src/data/headers.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchHeaders = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_headers?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching header data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchHeaders();
|
||||
}
|
31
src/data/links.js
Normal file
31
src/data/links.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllLinks = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_links?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching links:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
const links = await fetchAllLinks();
|
||||
|
||||
return {
|
||||
all: links,
|
||||
feed: links.filter((links) => links.feed),
|
||||
}
|
||||
}
|
62
src/data/movies.js
Normal file
62
src/data/movies.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllMovies = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_movies?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching movies:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
const movies = await fetchAllMovies();
|
||||
const favoriteMovies = movies.filter((movie) => movie.favorite);
|
||||
const now = new Date();
|
||||
|
||||
const recentlyWatchedMovies = movies.filter((movie) => {
|
||||
const lastWatched = movie.last_watched;
|
||||
if (!lastWatched) return false;
|
||||
|
||||
const lastWatchedDate = new Date(lastWatched);
|
||||
if (isNaN(lastWatchedDate.getTime())) return false;
|
||||
|
||||
const sixMonthsAgo = new Date();
|
||||
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
||||
|
||||
return lastWatchedDate >= sixMonthsAgo;
|
||||
});
|
||||
|
||||
return {
|
||||
movies,
|
||||
watchHistory: movies.filter((movie) => movie.last_watched),
|
||||
recentlyWatched: recentlyWatchedMovies,
|
||||
favorites: favoriteMovies.sort((a, b) =>
|
||||
a.title.localeCompare(b.title),
|
||||
),
|
||||
feed: movies.filter((movie) => movie.feed),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing movies data:", error);
|
||||
return {
|
||||
movies: [],
|
||||
watchHistory: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
feed: [],
|
||||
};
|
||||
}
|
||||
}
|
89
src/data/music.js
Normal file
89
src/data/music.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchDataFromView = async (viewName) => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/${viewName}?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data from view ${viewName}:`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function fetchMusicData() {
|
||||
try {
|
||||
const [
|
||||
recentTracks,
|
||||
weekTracks,
|
||||
weekArtists,
|
||||
weekAlbums,
|
||||
weekGenres,
|
||||
monthTracks,
|
||||
monthArtists,
|
||||
monthAlbums,
|
||||
monthGenres,
|
||||
] = await Promise.all([
|
||||
fetchDataFromView("recent_tracks"),
|
||||
fetchDataFromView("week_tracks"),
|
||||
fetchDataFromView("week_artists"),
|
||||
fetchDataFromView("week_albums"),
|
||||
fetchDataFromView("week_genres"),
|
||||
fetchDataFromView("month_tracks"),
|
||||
fetchDataFromView("month_artists"),
|
||||
fetchDataFromView("month_albums"),
|
||||
fetchDataFromView("month_genres"),
|
||||
]);
|
||||
|
||||
return {
|
||||
recent: recentTracks,
|
||||
week: {
|
||||
tracks: weekTracks,
|
||||
artists: weekArtists,
|
||||
albums: weekAlbums,
|
||||
genres: weekGenres,
|
||||
totalTracks: weekTracks
|
||||
.reduce((acc, track) => acc + track.plays, 0)
|
||||
.toLocaleString("en-US"),
|
||||
},
|
||||
month: {
|
||||
tracks: monthTracks,
|
||||
artists: monthArtists,
|
||||
albums: monthAlbums,
|
||||
genres: monthGenres,
|
||||
totalTracks: monthTracks
|
||||
.reduce((acc, track) => acc + track.plays, 0)
|
||||
.toLocaleString("en-US"),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing music data:", error);
|
||||
return {
|
||||
recent: [],
|
||||
week: {
|
||||
tracks: [],
|
||||
artists: [],
|
||||
albums: [],
|
||||
genres: [],
|
||||
totalTracks: "0",
|
||||
},
|
||||
month: {
|
||||
tracks: [],
|
||||
artists: [],
|
||||
albums: [],
|
||||
genres: [],
|
||||
totalTracks: "0",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
52
src/data/nav.js
Normal file
52
src/data/nav.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllNavigation = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_navigation?select=*`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const nav = data.reduce((acc, item) => {
|
||||
const navItem = {
|
||||
title: item.title || item.page_title,
|
||||
permalink: item.permalink || item.page_permalink,
|
||||
icon: item.icon,
|
||||
sort: item.sort,
|
||||
};
|
||||
|
||||
if (!acc[item.menu_location]) {
|
||||
acc[item.menu_location] = [navItem];
|
||||
} else {
|
||||
acc[item.menu_location].push(navItem);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.keys(nav).forEach((location) => {
|
||||
nav[location].sort((a, b) => a.sort - b.sort);
|
||||
});
|
||||
|
||||
return nav;
|
||||
} catch (error) {
|
||||
console.error("Error fetching navigation data:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchAllNavigation();
|
||||
}
|
42
src/data/nowPlaying.js
Normal file
42
src/data/nowPlaying.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchLatestListen = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_latest_listen?select=*`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const trackData = data[0];
|
||||
if (!trackData) {
|
||||
return { content: "🎧 No recent listens found" };
|
||||
}
|
||||
|
||||
const emoji = trackData.artist_emoji || trackData.genre_emoji || "🎧";
|
||||
|
||||
return {
|
||||
content: `${emoji} ${
|
||||
trackData.track_name
|
||||
} by <a href="https://www.coryd.dev${trackData.url}">${trackData.artist_name}</a>`,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching the latest listen:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchLatestListen();
|
||||
}
|
26
src/data/pages.js
Normal file
26
src/data/pages.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllPages = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_pages?select=*`, {
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching pages:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchAllPages();
|
||||
}
|
34
src/data/posts.js
Normal file
34
src/data/posts.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllPosts = async () => {
|
||||
try {
|
||||
return await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_posts?select=*&order=date.desc`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching posts:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
const posts = await fetchAllPosts();
|
||||
|
||||
return {
|
||||
all: posts,
|
||||
feed: posts.filter((posts) => posts.feed),
|
||||
};
|
||||
}
|
29
src/data/recentActivity.js
Normal file
29
src/data/recentActivity.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_recent_activity`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const feeds = data?.[0]?.feed || [];
|
||||
|
||||
return feeds.filter((item) => item !== null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent activity:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
33
src/data/recentMedia.js
Normal file
33
src/data/recentMedia.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchRecentMedia = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_recent_media?select=*`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const [{ recent_activity } = {}] = data;
|
||||
|
||||
return recent_activity || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching recent media data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchRecentMedia();
|
||||
}
|
29
src/data/redirects.js
Normal file
29
src/data/redirects.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchRedirects = async () => {
|
||||
try {
|
||||
return await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_redirects?select=*`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error fetching redirect data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchRedirects();
|
||||
}
|
37
src/data/robots.js
Normal file
37
src/data/robots.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllRobots = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_robots?select=path,user_agents`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const sortedData = data.sort((a, b) => {
|
||||
const aHasWildcard = a.user_agents.includes("*") ? 0 : 1;
|
||||
const bHasWildcard = b.user_agents.includes("*") ? 0 : 1;
|
||||
return aHasWildcard - bHasWildcard || a.path.localeCompare(b.path);
|
||||
});
|
||||
|
||||
return sortedData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching robot data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchAllRobots();
|
||||
}
|
26
src/data/sitemap.js
Normal file
26
src/data/sitemap.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchSitemap = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_sitemap?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching sitemap entries:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchSitemap();
|
||||
}
|
27
src/data/stats.js
Normal file
27
src/data/stats.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_stats?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching stats data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
const stats = await fetchStats();
|
||||
return stats[0];
|
||||
}
|
31
src/data/syndication.js
Normal file
31
src/data/syndication.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchSyndication = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_syndication`,
|
||||
{
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return data?.[0] || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching syndication data:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchSyndication();
|
||||
}
|
31
src/data/topAlbums.js
Normal file
31
src/data/topAlbums.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchTopAlbums = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_albums?select=table&order=total_plays_raw.desc&limit=8`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching top albums:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchTopAlbums();
|
||||
}
|
31
src/data/topArtists.js
Normal file
31
src/data/topArtists.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchTopArtists = async () => {
|
||||
try {
|
||||
const data = await EleventyFetch(
|
||||
`${POSTGREST_URL}/optimized_artists?select=table&order=total_plays_raw.desc&limit=8`,
|
||||
{
|
||||
duration: "1d",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching top artists:", error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
return await fetchTopArtists();
|
||||
}
|
59
src/data/tv.js
Normal file
59
src/data/tv.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchAllShows = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_shows?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching shows:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
const shows = await fetchAllShows();
|
||||
|
||||
const watchedShows = shows.filter(
|
||||
(show) => show.last_watched_at !== null,
|
||||
);
|
||||
|
||||
const episodes = watchedShows.map((show) => ({
|
||||
title: show.episode.title,
|
||||
year: show.year,
|
||||
formatted_episode: show.episode.formatted_episode,
|
||||
url: show.episode.url,
|
||||
image: show.episode.image,
|
||||
backdrop: show.episode.backdrop,
|
||||
last_watched_at: show.episode.last_watched_at,
|
||||
grid: show.grid,
|
||||
}));
|
||||
|
||||
return {
|
||||
shows,
|
||||
recentlyWatched: episodes.slice(0, 125),
|
||||
favorites: shows
|
||||
.filter((show) => show.favorite)
|
||||
.sort((a, b) => a.title.localeCompare(b.title)),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error fetching and processing shows data:", error);
|
||||
|
||||
return {
|
||||
shows: [],
|
||||
recentlyWatched: [],
|
||||
favorites: [],
|
||||
};
|
||||
}
|
||||
}
|
29
src/data/upcomingShows.js
Normal file
29
src/data/upcomingShows.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const { POSTGREST_URL, POSTGREST_API_KEY } = process.env;
|
||||
|
||||
const fetchUpcomingShows = async () => {
|
||||
try {
|
||||
return await EleventyFetch(`${POSTGREST_URL}/optimized_scheduled_shows?select=*`, {
|
||||
duration: "1h",
|
||||
type: "json",
|
||||
fetchOptions: {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${POSTGREST_API_KEY}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching upcoming shows:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export default async function () {
|
||||
const data = await fetchUpcomingShows();
|
||||
const upcomingShows = data?.[0]?.scheduled_shows;
|
||||
|
||||
return upcomingShows
|
||||
}
|
36
src/feeds/json.liquid
Normal file
36
src/feeds/json.liquid
Normal file
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: null
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSitemap: true
|
||||
pagination:
|
||||
data: feeds
|
||||
size: 1
|
||||
alias: feed
|
||||
permalink: "{{ feed.permalink }}.json"
|
||||
---
|
||||
{
|
||||
"version": "https://jsonfeed.org/version/1",
|
||||
"title": "{{ feed.title | append: " • " | append: globals.site_name }}",
|
||||
"icon": "{{ globals.url }}/assets/icons/feed.png",
|
||||
"home_page_url": "{{ globals.url }}",
|
||||
"feed_url": "{{ globals.url }}{{ feed.permalink }}.json",
|
||||
"items": [
|
||||
{%- for entry in feed.entries limit:20 %}
|
||||
{%- assign feedItem = entry.feed | default: entry -%}
|
||||
{%- capture contentHtml -%}
|
||||
{%- if feedItem.content -%}
|
||||
{{ feedItem.content | markdown | convertRelativeLinks: globals.url }}
|
||||
{%- else -%}
|
||||
{{ feedItem.description | markdown | convertRelativeLinks: globals.url }}
|
||||
{%- endif -%}
|
||||
{%- endcapture -%}
|
||||
{
|
||||
"id": "{{ feedItem.url | generatePermalink: globals.url | encodeAmp }}",
|
||||
"title": "{{ feedItem.title | jsonEscape }}",
|
||||
"content_html": {{ contentHtml | jsonEscape }},
|
||||
"date_published": "{{ feedItem.date | date: "%a, %d %b %Y %H:%M:%S %z" }}",
|
||||
"url": "{{ feedItem.url | generatePermalink: globals.url | encodeAmp }}"
|
||||
}{%- unless forloop.last -%},{%- endunless -%}
|
||||
{%- endfor -%}
|
||||
]
|
||||
}
|
18
src/feeds/opml.liquid
Normal file
18
src/feeds/opml.liquid
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
permalink: "/blogroll.opml"
|
||||
layout: null
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSitemap: true
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="1.0">
|
||||
<head>
|
||||
<title>OPML for all feeds in {{ globals.site_name }}'s blogroll</title>
|
||||
<dateCreated>{{ page.date | stringToRFC822Date }}</dateCreated>
|
||||
</head>
|
||||
<body>
|
||||
{%- for blog in blogroll -%}
|
||||
<outline text="{{ blog.name }}" title="{{ blog.name }}" type="rss" xmlUrl="{{ blog.rss_feed }}" htmlUrl="{{ blog.url }}"/>
|
||||
{%- endfor -%}
|
||||
</body>
|
||||
</opml>
|
7
src/feeds/releases.liquid
Normal file
7
src/feeds/releases.liquid
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
layout: null
|
||||
permalink: "/music/releases.ics"
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSitemap: true
|
||||
---
|
||||
{{ collections.albumReleasesCalendar }}
|
49
src/feeds/rss.liquid
Normal file
49
src/feeds/rss.liquid
Normal file
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
layout: null
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSitemap: true
|
||||
pagination:
|
||||
data: feeds
|
||||
size: 1
|
||||
alias: feed
|
||||
permalink: "{{ feed.permalink }}.xml"
|
||||
---
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title><![CDATA[{{ feed.title | append: " • " | append: globals.site_name }}]]></title>
|
||||
<link>{{ globals.url }}{{ feed.permalink }}.xml</link>
|
||||
<description><![CDATA[{{ globals.site_description }}]]></description>
|
||||
<atom:link href="{{ globals.url }}{{ feed.permalink }}.xml" rel="self" type="application/rss+xml" />
|
||||
<lastBuildDate>{{ "now" | date:"%a, %d %b %Y %H:%M:%S %z" }}</lastBuildDate>
|
||||
<image>
|
||||
<url>{{ globals.url }}/assets/icons/feed.png</url>
|
||||
<title><![CDATA[{{ feed.title | append: " • " | append: globals.site_name }}]]></title>
|
||||
<link>{{ globals.url }}{{ feed.permalink }}.xml</link>
|
||||
<width>144</width>
|
||||
<height>144</height>
|
||||
</image>
|
||||
{%- for entry in feed.entries limit:20 %}
|
||||
{%- assign feedItem = entry.feed | default: entry -%}
|
||||
<item>
|
||||
<title><![CDATA[{{ feedItem.title }}]]></title>
|
||||
<link>{{ feedItem.url | generatePermalink: globals.url | encodeAmp }}</link>
|
||||
<guid isPermaLink="false">{{ feedItem.url | generatePermalink: globals.url | encodeAmp }}</guid>
|
||||
<pubDate>{{ feedItem.date | date: "%a, %d %b %Y %H:%M:%S %z" }}</pubDate>
|
||||
{%- if feedItem.image -%}
|
||||
<enclosure url="{{ globals.cdn_url }}{{ feedItem.image | encodeAmp }}?class=w800" type="image/jpeg" />
|
||||
{%- endif -%}
|
||||
<description><![CDATA[
|
||||
{%- if feedItem.image -%}
|
||||
<img src="{{ globals.cdn_url }}{{ feedItem.image | encodeAmp }}?class=w800" alt="Image from {{ feedItem.title }}" />
|
||||
{%- endif -%}
|
||||
{%- if feedItem.content -%}
|
||||
{{ feedItem.content | markdown | convertRelativeLinks: globals.url }}
|
||||
{%- else -%}
|
||||
{{ feedItem.description | markdown | convertRelativeLinks: globals.url }}
|
||||
{%- endif -%}
|
||||
]]></description>
|
||||
</item>
|
||||
{%- endfor -%}
|
||||
</channel>
|
||||
</rss>
|
14
src/feeds/sitemap.liquid
Normal file
14
src/feeds/sitemap.liquid
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
permalink: "/sitemap.xml"
|
||||
layout: null
|
||||
eleventyExcludeFromCollections: true
|
||||
excludeFromSitemap: true
|
||||
---
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{%- for page in sitemap %}
|
||||
<url>
|
||||
<loc>{{ page.url | prepend: globals.url }}</loc>
|
||||
</url>
|
||||
{%- endfor %}
|
||||
</urlset>
|
50
src/includes/blocks/associated-media.liquid
Normal file
50
src/includes/blocks/associated-media.liquid
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% assign media = artists | concat:books | concat:genres | concat:movies | concat:posts | concat:shows %}
|
||||
{% if media.size > 0 %}
|
||||
<div class="associated-media">
|
||||
{% assign sections =
|
||||
"artists:headphones:music:Related artist(s)," | append:
|
||||
"books:books:books:Related book(s)," | append:
|
||||
"genres:headphones:music:Related genre(s)," | append:
|
||||
"movies:movie:movies:Related movie(s)," | append:
|
||||
"posts:article:article:Related post(s)," | append:
|
||||
"shows:device-tv-old:tv:Related show(s)"
|
||||
| split:"," %}
|
||||
{% for section in sections %}
|
||||
{% assign parts = section | split:":" %}
|
||||
{% assign key = parts[0] %}
|
||||
{% assign icon = parts[1] %}
|
||||
{% assign css_class = parts[2] %}
|
||||
{% assign label = parts[3] %}
|
||||
{% case key %}
|
||||
{% when "artists" %} {% assign items = artists %}
|
||||
{% when "books" %} {% assign items = books %}
|
||||
{% when "genres" %} {% assign items = genres %}
|
||||
{% when "movies" %} {% assign items = movies %}
|
||||
{% when "posts" %} {% assign items = posts %}
|
||||
{% when "shows" %} {% assign items = shows %}
|
||||
{% endcase %}
|
||||
{% if items and items.size > 0 %}
|
||||
<p id="{{ key }}" class="{{ css_class }}">
|
||||
{% tablericon icon %}
|
||||
{{ label }}
|
||||
</p>
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li class="{{ css_class }}">
|
||||
<a href="{{ item.url }}">{{ item.title | default:item.name }}</a>
|
||||
{% if key == "artists" and item.total_plays > 0 %}
|
||||
({{ item.total_plays }} {{ item.total_plays | pluralize:"play" }})
|
||||
{% elsif key == "books" %}
|
||||
by {{ item.author }}
|
||||
{% elsif key == "movies" or key == "shows" %}
|
||||
({{ item.year }})
|
||||
{% elsif key == "posts" %}
|
||||
({{ item.date | date:"%B %e, %Y" }})
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
3
src/includes/blocks/banners/calendar.liquid
Normal file
3
src/includes/blocks/banners/calendar.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner calendar">
|
||||
<p>{% tablericon "calendar" %} <a href="{{ url }}">{{ text }}</a>.</p>
|
||||
</div>
|
5
src/includes/blocks/banners/coffee.liquid
Normal file
5
src/includes/blocks/banners/coffee.liquid
Normal file
|
@ -0,0 +1,5 @@
|
|||
<div class="banner coffee">
|
||||
<p>
|
||||
{% tablericon "coffee" %} <a class="coffee" href="https://buymeacoffee.com/cory">If you found this post helpful, you can buy me a coffee.</a>
|
||||
</p>
|
||||
</div>
|
3
src/includes/blocks/banners/error.liquid
Normal file
3
src/includes/blocks/banners/error.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner error">
|
||||
<p>{% tablericon "alert-circle" %} {{ text }}</p>
|
||||
</div>
|
3
src/includes/blocks/banners/github.liquid
Normal file
3
src/includes/blocks/banners/github.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner github">
|
||||
<p>{% tablericon "brand-github" %} Take a look at <a href="{{ url }}">the GitHub repository for this project</a>. (Give it a star if you feel like it.)</p>
|
||||
</div>
|
8
src/includes/blocks/banners/mastodon.liquid
Normal file
8
src/includes/blocks/banners/mastodon.liquid
Normal file
|
@ -0,0 +1,8 @@
|
|||
{%- if url -%}
|
||||
<div class="banner mastodon">
|
||||
<p>
|
||||
{% tablericon "brand-mastodon" %}
|
||||
<a class="mastodon" href="{{ url }}"> Discuss this post on Mastodon. </a>
|
||||
</p>
|
||||
</div>
|
||||
{%- endif -%}
|
3
src/includes/blocks/banners/npm.liquid
Normal file
3
src/includes/blocks/banners/npm.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner npm">
|
||||
<p>{% tablericon "brand-npm" %} <a href="{{ url }}">You can take a look at this package on NPM</a> or install it by running <code>{{ command }}</code>.</p>
|
||||
</div>
|
5
src/includes/blocks/banners/old-post.liquid
Normal file
5
src/includes/blocks/banners/old-post.liquid
Normal file
|
@ -0,0 +1,5 @@
|
|||
{%- if isOldPost -%}
|
||||
<div class="banner old-post">
|
||||
<p>{% tablericon "clock-x" %} This post is over 3 years old. I've probably changed my mind since it was written and it <em>could</em> be out of date.</p>
|
||||
</div>
|
||||
{%- endif -%}
|
3
src/includes/blocks/banners/rss.liquid
Normal file
3
src/includes/blocks/banners/rss.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner rss">
|
||||
<p>{% tablericon "rss" %} <a href="{{ url }}">{{ text }}</a>.</p>
|
||||
</div>
|
3
src/includes/blocks/banners/warning.liquid
Normal file
3
src/includes/blocks/banners/warning.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="banner warning">
|
||||
<p>{% tablericon "alert-triangle" %} {{ text }}</p>
|
||||
</div>
|
17
src/includes/blocks/hero.liquid
Normal file
17
src/includes/blocks/hero.liquid
Normal file
|
@ -0,0 +1,17 @@
|
|||
<img
|
||||
srcset="
|
||||
{{ globals.cdn_url }}{{ image }}?class=bannersm&type=webp 256w,
|
||||
{{ globals.cdn_url }}{{ image }}?class=bannermd&type=webp 512w,
|
||||
{{ globals.cdn_url }}{{ image }}?class=bannerbase&type=webp 1024w
|
||||
"
|
||||
sizes="(max-width: 450px) 256px,
|
||||
(max-width: 850px) 512px
|
||||
1024px"
|
||||
src="{{ globals.cdn_url }}{{ image }}?class=bannersm&type=webp"
|
||||
alt="{{ alt | replaceQuotes }}"
|
||||
class="image-banner"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="720"
|
||||
height="480"
|
||||
/>
|
32
src/includes/blocks/index.liquid
Normal file
32
src/includes/blocks/index.liquid
Normal file
|
@ -0,0 +1,32 @@
|
|||
{%- for block in blocks -%}
|
||||
{%- case block.type -%}
|
||||
{%- when "youtube_player" -%}
|
||||
{% render "blocks/youtube-player.liquid",
|
||||
url:block.url
|
||||
%}
|
||||
{%- when "github_banner" -%}
|
||||
{% render "blocks/banners/github.liquid",
|
||||
url:block.url
|
||||
%}
|
||||
{%- when "npm_banner" -%}
|
||||
{% render "blocks/banners/npm.liquid",
|
||||
url:block.url,
|
||||
command:block.command
|
||||
%}
|
||||
{%- when "rss_banner" -%}
|
||||
{% render "blocks/banners/rss.liquid",
|
||||
url:block.url,
|
||||
text:block.text
|
||||
%}
|
||||
{%- when "hero" -%}
|
||||
{% render "blocks/hero.liquid",
|
||||
globals:globals,
|
||||
image:block.image,
|
||||
alt:block.alt
|
||||
%}
|
||||
{%- when "markdown" -%}
|
||||
{{ block.text | markdown }}
|
||||
{%- when "divider" -%}
|
||||
{{ block.markup | markdown }}
|
||||
{%- endcase -%}
|
||||
{%- endfor -%}
|
29
src/includes/blocks/modal.liquid
Normal file
29
src/includes/blocks/modal.liquid
Normal file
|
@ -0,0 +1,29 @@
|
|||
{%- capture labelContent -%}
|
||||
{%- if icon -%}
|
||||
{% tablericon icon %}
|
||||
{%- elsif label -%}
|
||||
{{ label }}
|
||||
{%- endif -%}
|
||||
{%- endcapture -%}
|
||||
{% assign modalId = id | default:"modal-controls" %}
|
||||
<noscript>
|
||||
<input class="modal-input" id="{{ modalId }}" type="checkbox" tabindex="0" />
|
||||
<label class="modal-open" for="{{ modalId }}">{{ labelContent }}</label>
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-body">
|
||||
<label class="modal-close" for="{{ modalId }}">
|
||||
{% tablericon "circle-x" %}
|
||||
</label>
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
<button class="modal-open client-side" data-modal-trigger="{{ modalId }}" data-modal-button>
|
||||
{{ labelContent }}
|
||||
</button>
|
||||
<dialog id="dialog-{{ modalId }}" class="client-side">
|
||||
<button class="modal-close" data-modal-button>
|
||||
{% tablericon "circle-x" %}
|
||||
</button>
|
||||
{{ content }}
|
||||
</dialog>
|
7
src/includes/blocks/now-playing.liquid
Normal file
7
src/includes/blocks/now-playing.liquid
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script type="module" src="/assets/scripts/components/now-playing.js?v={% appVersion %}" defer></script>
|
||||
<p class="{{ section }}">
|
||||
<mark>Now playing</mark>
|
||||
<now-playing>
|
||||
<span class="content">{{ nowPlaying }}</span>
|
||||
</now-playing>
|
||||
</p>
|
2
src/includes/blocks/youtube-player.liquid
Normal file
2
src/includes/blocks/youtube-player.liquid
Normal file
|
@ -0,0 +1,2 @@
|
|||
<script type="module" src="/assets/scripts/components/youtube-video-element.js?v={% appVersion %}" defer></script>
|
||||
<youtube-video controls src="{{ url }}"></youtube-video>
|
80
src/includes/fetchers/artist.php.liquid
Normal file
80
src/includes/fetchers/artist.php.liquid
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$url = trim(parse_url($requestUri, PHP_URL_PATH), "/");
|
||||
$postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL");
|
||||
$postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY");
|
||||
|
||||
if (strpos($url, "music/artists/") !== 0) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$cacheKey = "artist_" . md5($url);
|
||||
$artist = null;
|
||||
$useRedis = false;
|
||||
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$useRedis = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis not available: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($useRedis && $redis->exists($cacheKey)) {
|
||||
$artist = json_decode($redis->get($cacheKey), true);
|
||||
} else {
|
||||
$apiUrl = "$postgrestUrl/optimized_artists?url=eq./" . $url;
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->request("GET", $apiUrl, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer {$postgrestApiKey}",
|
||||
],
|
||||
]);
|
||||
|
||||
$artistData = json_decode($response->getBody(), true);
|
||||
|
||||
if (!$artistData) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$artist = $artistData[0];
|
||||
|
||||
if ($useRedis) {
|
||||
$redis->setex($cacheKey, 3600, json_encode($artist));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$artist["description"] = parseMarkdown($artist["description"]);
|
||||
$pageTitle = htmlspecialchars(
|
||||
"Artists • " . $artist["name"],
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$pageDescription = truncateText(htmlspecialchars(
|
||||
strip_tags($artist["description"]),
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
), 250);
|
||||
$ogImage = htmlspecialchars($artist["image"] . "?class=w800", ENT_QUOTES, "UTF-8");
|
||||
$fullUrl = "https://www.coryd.dev" . $requestUri;
|
||||
|
||||
header("Cache-Control: public, max-age=3600");
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
|
||||
?>
|
103
src/includes/fetchers/book.php.liquid
Normal file
103
src/includes/fetchers/book.php.liquid
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
require __DIR__ . "/../vendor/autoload.php";
|
||||
require __DIR__ . "/../server/utils/init.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$url = trim(parse_url($requestUri, PHP_URL_PATH), "/");
|
||||
$postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL");
|
||||
$postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY");
|
||||
|
||||
if (preg_match('/^books\/years\/(\d{4})$/', $url, $matches)) {
|
||||
$year = $matches[1];
|
||||
$filePath = __DIR__ . "/years/{$year}.html";
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
echo file_get_contents($filePath);
|
||||
exit();
|
||||
} else {
|
||||
echo file_get_contents(__DIR__ . "/../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
if ($url === "books/years") {
|
||||
echo file_get_contents(__DIR__ . "/../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($url === "books") {
|
||||
readfile("index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!preg_match('/^books\/[\w-]+$/', $url)) {
|
||||
echo file_get_contents(__DIR__ . "/../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$cacheKey = "book_" . md5($url);
|
||||
|
||||
$book = null;
|
||||
$useRedis = false;
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$useRedis = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis not available: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($useRedis && $redis->exists($cacheKey)) {
|
||||
$book = json_decode($redis->get($cacheKey), true);
|
||||
} else {
|
||||
$apiUrl = "$postgrestUrl/optimized_books?url=eq./" . $url;
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->request("GET", $apiUrl, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer {$postgrestApiKey}",
|
||||
],
|
||||
]);
|
||||
|
||||
$bookData = json_decode($response->getBody(), true);
|
||||
|
||||
if (!$bookData) {
|
||||
echo file_get_contents(__DIR__ . "/../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$book = $bookData[0];
|
||||
|
||||
if ($useRedis) {
|
||||
$redis->setex($cacheKey, 3600, json_encode($book));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
echo file_get_contents(__DIR__ . "/../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$book["description"] = parseMarkdown($book["description"]);
|
||||
$pageTitle = htmlspecialchars(
|
||||
"Books • " . $book["title"] . " by " . $book["author"],
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$pageDescription = truncateText(htmlspecialchars(
|
||||
strip_tags($book["description"]),
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
), 250);
|
||||
$ogImage = htmlspecialchars($book["image"] . "?class=w800", ENT_QUOTES, "UTF-8");
|
||||
$fullUrl = "https://www.coryd.dev" . $requestUri;
|
||||
|
||||
header("Cache-Control: public, max-age=3600");
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
|
||||
?>
|
71
src/includes/fetchers/genre.php.liquid
Normal file
71
src/includes/fetchers/genre.php.liquid
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$url = trim(parse_url($requestUri, PHP_URL_PATH), "/");
|
||||
$postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL");
|
||||
$postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY");
|
||||
|
||||
if (!preg_match('/^music\/genres\/[\w-]+$/', $url)) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$cacheKey = "genre_" . md5($url);
|
||||
$genre = null;
|
||||
$useRedis = false;
|
||||
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$useRedis = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis not available: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($useRedis && $redis->exists($cacheKey)) {
|
||||
$genre = json_decode($redis->get($cacheKey), true);
|
||||
} else {
|
||||
$apiUrl = "$postgrestUrl/optimized_genres?url=eq./" . $url;
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->request("GET", $apiUrl, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer {$postgrestApiKey}",
|
||||
],
|
||||
]);
|
||||
|
||||
$genreData = json_decode($response->getBody(), true);
|
||||
|
||||
if (!$genreData) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$genre = $genreData[0];
|
||||
|
||||
if ($useRedis) {
|
||||
$redis->setex($cacheKey, 3600, json_encode($genre));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = htmlspecialchars("Genres • " . $genre["name"], ENT_QUOTES, "UTF-8");
|
||||
$pageDescription = truncateText(htmlspecialchars(strip_tags($genre["description"]), ENT_QUOTES, "UTF-8"), 250);
|
||||
$ogImage = htmlspecialchars($genre["artists"][0]["image"] . "?class=w800", ENT_QUOTES, "UTF-8");
|
||||
$fullUrl = "https://www.coryd.dev" . $requestUri;
|
||||
|
||||
header("Cache-Control: public, max-age=3600");
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
|
||||
?>
|
80
src/includes/fetchers/movie.php.liquid
Normal file
80
src/includes/fetchers/movie.php.liquid
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$url = trim(parse_url($requestUri, PHP_URL_PATH), "/");
|
||||
$postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL");
|
||||
$postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY");
|
||||
|
||||
if (!preg_match('/^watching\/movies\/[\w-]+$/', $url)) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$cacheKey = "movie_" . md5($url);
|
||||
$movie = null;
|
||||
$useRedis = false;
|
||||
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$useRedis = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis not available: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($useRedis && $redis->exists($cacheKey)) {
|
||||
$movie = json_decode($redis->get($cacheKey), true);
|
||||
} else {
|
||||
$apiUrl = "$postgrestUrl/optimized_movies?url=eq./" . $url;
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->request("GET", $apiUrl, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer {$postgrestApiKey}",
|
||||
],
|
||||
]);
|
||||
|
||||
$movieData = json_decode($response->getBody(), true);
|
||||
|
||||
if (!$movieData) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$movie = $movieData[0];
|
||||
|
||||
if ($useRedis) {
|
||||
$redis->setex($cacheKey, 3600, json_encode($movie));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$movie["description"] = parseMarkdown($movie["description"]);
|
||||
$pageTitle = htmlspecialchars(
|
||||
"Movies • " . $movie["title"],
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$pageDescription = truncateText(htmlspecialchars(
|
||||
strip_tags($movie["description"]),
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
), 250);
|
||||
$ogImage = htmlspecialchars($movie["backdrop"] . "?class=w800", ENT_QUOTES, "UTF-8");
|
||||
$fullUrl = "https://www.coryd.dev" . $requestUri;
|
||||
|
||||
header("Cache-Control: public, max-age=3600");
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
|
||||
?>
|
71
src/includes/fetchers/show.php.liquid
Normal file
71
src/includes/fetchers/show.php.liquid
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
require __DIR__ . "/../../vendor/autoload.php";
|
||||
require __DIR__ . "/../../server/utils/init.php";
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$requestUri = $_SERVER["REQUEST_URI"];
|
||||
$url = trim(parse_url($requestUri, PHP_URL_PATH), "/");
|
||||
$postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL");
|
||||
$postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY");
|
||||
|
||||
if (!preg_match('/^watching\/shows\/[\w-]+$/', $url)) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$cacheKey = "show_" . md5($url);
|
||||
$show = null;
|
||||
$useRedis = false;
|
||||
|
||||
try {
|
||||
if (extension_loaded('redis')) {
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$useRedis = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Redis not available: " . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($useRedis && $redis->exists($cacheKey)) {
|
||||
$show = json_decode($redis->get($cacheKey), true);
|
||||
} else {
|
||||
$apiUrl = "$postgrestUrl/optimized_shows?url=eq./" . $url;
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->request("GET", $apiUrl, [
|
||||
"headers" => [
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer {$postgrestApiKey}",
|
||||
],
|
||||
]);
|
||||
|
||||
$showData = json_decode($response->getBody(), true);
|
||||
|
||||
if (!$showData) {
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
|
||||
$show = $showData[0];
|
||||
|
||||
if ($useRedis) {
|
||||
$redis->setex($cacheKey, 3600, json_encode($show));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
echo file_get_contents(__DIR__ . "/../../404/index.html");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = htmlspecialchars("Show • " . $show["title"], ENT_QUOTES, "UTF-8");
|
||||
$pageDescription = truncateText(htmlspecialchars(strip_tags($show["description"]), ENT_QUOTES, "UTF-8"), 250);
|
||||
$ogImage = htmlspecialchars($show["image"] . "?class=w800", ENT_QUOTES, "UTF-8");
|
||||
$fullUrl = "https://www.coryd.dev" . $requestUri;
|
||||
|
||||
header("Cache-Control: public, max-age=3600");
|
||||
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
|
||||
?>
|
8
src/includes/home/intro.liquid
Normal file
8
src/includes/home/intro.liquid
Normal file
|
@ -0,0 +1,8 @@
|
|||
<article class="intro">
|
||||
{{ intro }}
|
||||
{% render "blocks/now-playing.liquid",
|
||||
nowPlaying:nowPlaying
|
||||
section:"music"
|
||||
%}
|
||||
<hr />
|
||||
</article>
|
60
src/includes/home/recent-activity.liquid
Normal file
60
src/includes/home/recent-activity.liquid
Normal file
|
@ -0,0 +1,60 @@
|
|||
<article>
|
||||
<h2>
|
||||
{% tablericon "activity" %}
|
||||
Recent activity
|
||||
</h2>
|
||||
<p>
|
||||
<a href="/posts" class="article">Posts</a> •
|
||||
<a href="/links" class="link">Links</a> •
|
||||
<a href="/watching" class="movies">Watching</a> •
|
||||
<a href="/books" class="books">Books</a>
|
||||
</p>
|
||||
{%- for item in items -%}
|
||||
<article class="{{ item.type }}">
|
||||
<aside>
|
||||
<time datetime="{{ item.content_date }}">
|
||||
{{ item.content_date | date:"%B %e, %Y" }}
|
||||
</time>
|
||||
• {{ item.label }}
|
||||
{%- if item.notes -%}
|
||||
{% assign notes = item.notes | prepend: "### Notes\n" | markdown %}
|
||||
• {% render "blocks/modal.liquid",
|
||||
label:"Notes",
|
||||
content:notes,
|
||||
id:item.content_date
|
||||
%}
|
||||
{%- endif -%}
|
||||
</aside>
|
||||
<h3>
|
||||
{%- if item.type == "concerts" -%}
|
||||
{%- capture artistName -%}
|
||||
{%- if item.artist_url -%}
|
||||
<a href="{{ item.artist_url }}">{{ item.title | split: ' at ' | first }}</a>
|
||||
{%- else -%}
|
||||
{{ item.title | split: ' at ' | first }}
|
||||
{%- endif -%}
|
||||
{%- endcapture -%}
|
||||
{%- capture venue -%}
|
||||
{%- if item.venue_lat and item.venue_lon -%}
|
||||
<a href="https://www.openstreetmap.org/?mlat={{ item.venue_lat }}&mlon={{ item.venue_lon }}#map=18/{{ item.venue_lat }}/{{ item.venue_lon }}">{{ item.venue_name }}</a>
|
||||
{%- else -%}
|
||||
{{ item.venue_name }}
|
||||
{%- endif -%}
|
||||
{%- endcapture -%}
|
||||
{{ artistName }}
|
||||
{% if venue %} at {{ venue }}{% endif %}
|
||||
{%- else -%}
|
||||
<a href="{{ item.url | prepend: globals.url }}">{{ item.title }}</a>
|
||||
{%- if item.type == "link" and item.author -%}
|
||||
<span> via </span>
|
||||
{%- if item.author.url -%}
|
||||
<a href="{{ item.author.url }}">{{ item.author.name }}</a>
|
||||
{%- else -%}
|
||||
{{ item.author.name }}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</h3>
|
||||
</article>
|
||||
{%- endfor -%}
|
||||
</article>
|
19
src/includes/home/recent-media.liquid
Normal file
19
src/includes/home/recent-media.liquid
Normal file
|
@ -0,0 +1,19 @@
|
|||
<article>
|
||||
{% render "media/grid.liquid",
|
||||
globals:globals,
|
||||
data:media.recentMusic,
|
||||
count:8,
|
||||
loading:"eager"
|
||||
%}
|
||||
{% render "media/grid.liquid",
|
||||
globals:globals,
|
||||
data:media.recentWatchedRead,
|
||||
shape:"vertical",
|
||||
count:6
|
||||
loading:"eager"
|
||||
%}
|
||||
{% render "blocks/banners/rss.liquid",
|
||||
url:"/feeds",
|
||||
text:"Subscribe to my movies, books, links or activity feed(s)"
|
||||
%}
|
||||
</article>
|
19
src/includes/layout/footer.liquid
Normal file
19
src/includes/layout/footer.liquid
Normal file
|
@ -0,0 +1,19 @@
|
|||
{%- assign updateTime = "" -%}
|
||||
{%- if updated == "now" -%}
|
||||
{%- assign updateTime = "now" | date:"%B %-d, %l:%M %P", "America/Los_Angeles" -%}
|
||||
{%- elsif pageUpdated -%}
|
||||
{%- assign updateTime = page.updated | date:"%B %-d, %l:%M %P", "America/Los_Angeles" -%}
|
||||
{%- endif -%}
|
||||
<footer{% unless updateTime %} style="margin-top:var(--spacing-3xl)"{% endunless %}>
|
||||
{%- if updateTime -%}
|
||||
<p class="updated"><em>This page was last updated on {{ updateTime | strip }}.</em></p>
|
||||
{%- endif -%}
|
||||
{% render "nav/social.liquid",
|
||||
page:page,
|
||||
links:nav.footer_icons
|
||||
%}
|
||||
{% render "nav/secondary.liquid",
|
||||
page:page,
|
||||
links:nav.footer_text
|
||||
%}
|
||||
</footer>
|
13
src/includes/layout/header.liquid
Normal file
13
src/includes/layout/header.liquid
Normal file
|
@ -0,0 +1,13 @@
|
|||
<section class="main-title">
|
||||
<h1>
|
||||
{%- if page.url == "/" -%}
|
||||
{{ globals.site_name }}
|
||||
{%- else -%}
|
||||
<a href="/" tabindex="0">{{ globals.site_name }}</a>
|
||||
{%- endif -%}
|
||||
</h1>
|
||||
{% render "nav/primary.liquid",
|
||||
page:page,
|
||||
nav:nav
|
||||
%}
|
||||
</section>
|
47
src/includes/media/grid.liquid
Normal file
47
src/includes/media/grid.liquid
Normal file
|
@ -0,0 +1,47 @@
|
|||
<div class="media-grid {{ shape | default: square }}">
|
||||
{%- assign loadingStrategy = loading | default:"lazy" -%}
|
||||
{%- for item in data limit:count -%}
|
||||
{%- assign alt = item.grid.alt | replaceQuotes -%}
|
||||
{%- assign imageUrl = item.grid.image -%}
|
||||
<a href="{{ item.grid.url }}" title="{{ alt }}">
|
||||
<div class="media-grid-item">
|
||||
{%- if item.grid.title or item.grid.subtext -%}
|
||||
<div class="meta-text {{ item.type }}">
|
||||
{% if item.grid.title %}
|
||||
<div class="header">{{ item.grid.title }}</div>
|
||||
{% endif %}
|
||||
{% if item.grid.subtext %}
|
||||
<div class="subheader">{{ item.grid.subtext }}</div>
|
||||
{% endif %}
|
||||
</mark>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- assign imageClass = "square" -%}
|
||||
{%- assign width = 150 -%}
|
||||
{%- assign height = 150 -%}
|
||||
{%- case shape -%}
|
||||
{%- when "vertical" -%}
|
||||
{%- assign imageClass = "vertical" -%}
|
||||
{%- assign width = 120 -%}
|
||||
{%- assign height = 184 -%}
|
||||
{%- endcase -%}
|
||||
<img
|
||||
srcset="
|
||||
{{ globals.cdn_url }}{{ imageUrl }}?class={{ imageClass }}sm&type=webp {{ width }}w,
|
||||
{{ globals.cdn_url }}{{ imageUrl }}?class={{ imageClass }}md&type=webp {{ width | times:2 }}w
|
||||
"
|
||||
sizes="(max-width: 450px) {{ width }}px, {{ width | times:2 }}px"
|
||||
src="{{ globals.cdn_url }}{{ imageUrl }}?class={{ imageClass }}sm&type=webp"
|
||||
alt="{{ alt }}"
|
||||
loading="{{ loadingStrategy }}"
|
||||
decoding="async"
|
||||
width="{{ width }}"
|
||||
height="{{ height }}"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
{% render "nav/paginator.liquid",
|
||||
pagination:pagination
|
||||
%}
|
13
src/includes/media/music/charts/item.liquid
Normal file
13
src/includes/media/music/charts/item.liquid
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div class="chart-item">
|
||||
<div class="chart-item-info">
|
||||
<a class="title" href="{{ item.chart.url }}">{{ item.chart.title }}</a>
|
||||
{%- assign playsLabel = item.chart.plays | pluralize:"play" -%}
|
||||
<span class="subheader">{{ item.chart.artist }}</span>
|
||||
<span class="subheader">{{ item.chart.plays }} {{ playsLabel }}</span>
|
||||
</div>
|
||||
<div class="chart-item-progress">
|
||||
{% render "media/progress-bar.liquid",
|
||||
percentage:item.chart.percentage
|
||||
%}
|
||||
</div>
|
||||
</div>
|
24
src/includes/media/music/charts/rank.liquid
Normal file
24
src/includes/media/music/charts/rank.liquid
Normal file
|
@ -0,0 +1,24 @@
|
|||
<div class="music-chart">
|
||||
<ol type="1">
|
||||
{%- if count -%}
|
||||
{%- for item in data limit:count -%}
|
||||
<li value="{{ item.chart.rank }}">
|
||||
{% render "media/music/charts/item.liquid",
|
||||
item:item
|
||||
%}
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
{%- else -%}
|
||||
{%- for item in pagination.items -%}
|
||||
<li value="{{ item.chart.rank }}">
|
||||
{% render "media/music/charts/item.liquid",
|
||||
item:item
|
||||
%}
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
</ol>
|
||||
</div>
|
||||
{% render "nav/paginator.liquid",
|
||||
pagination:pagination
|
||||
%}
|
30
src/includes/media/music/charts/recent.liquid
Normal file
30
src/includes/media/music/charts/recent.liquid
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class="music-chart">
|
||||
{%- for item in data limit:10 -%}
|
||||
<div class="chart-item">
|
||||
<div class="meta">
|
||||
<a href="{{ item.chart.url }}">
|
||||
<img
|
||||
srcset="
|
||||
{{ globals.cdn_url }}{{ item.chart.image }}?class=w50&type=webp 50w,
|
||||
{{ globals.cdn_url }}{{ item.chart.image }}?class=w100&type=webp 100w
|
||||
"
|
||||
sizes="(max-width: 450px) 50px, 100px"
|
||||
src="{{ globals.cdn_url }}{{ item.chart.image }}?class=w50&type=webp"
|
||||
alt="{{ item.chart.alt | replaceQuotes }}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
width="64"
|
||||
height="64"
|
||||
/>
|
||||
</a>
|
||||
<div class="meta-text">
|
||||
<a class="title" href="{{ item.chart.url }}">{{ item.chart.title }}</a>
|
||||
<span class="subheader">{{ item.chart.subtext }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<time datetime="item.chart.played_at">
|
||||
{{ item.chart.played_at | date:"%B %-d, %-I:%M%p", "America/Los_Angeles" }}
|
||||
</time>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
21
src/includes/media/music/tables/all-time/albums.liquid
Normal file
21
src/includes/media/music/tables/all-time/albums.liquid
Normal file
|
@ -0,0 +1,21 @@
|
|||
<table class="music-ranking">
|
||||
<tr>
|
||||
<th>Albums (all time)</th>
|
||||
<th>Artist</th>
|
||||
<th>Plays</th>
|
||||
<th>Year</th>
|
||||
</tr>
|
||||
{% for album in topAlbums %}
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<img src="{{ globals.cdn_url }}{{ album.table.image }}?class=w100" alt="{{ album.table.alt }}" width="50">
|
||||
{{ album.table.title }}
|
||||
</div>
|
||||
</td>
|
||||
<td><a href="{{ album.table.url }}">{{ album.table.artist }}</a></td>
|
||||
<td>{{ album.table.plays }}</td>
|
||||
<td>{{ album.table.year }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
19
src/includes/media/music/tables/all-time/artists.liquid
Normal file
19
src/includes/media/music/tables/all-time/artists.liquid
Normal file
|
@ -0,0 +1,19 @@
|
|||
<table class="music-ranking">
|
||||
<tr>
|
||||
<th>Artists (all time)</th>
|
||||
<th>Genre</th>
|
||||
<th>Plays</th>
|
||||
</tr>
|
||||
{% for artist in topArtists %}
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<img src="{{ globals.cdn_url }}{{ artist.table.image }}?class=w100" alt="{{ artist.table.alt }}" width="50">
|
||||
<a href="{{ artist.table.url }}">{{ artist.table.title }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ artist.table.emoji }} <a href="{{ artist.table.genre_url }}">{{ artist.table.genre }}</a></td>
|
||||
<td>{{ artist.table.plays }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
3
src/includes/media/progress-bar.liquid
Normal file
3
src/includes/media/progress-bar.liquid
Normal file
|
@ -0,0 +1,3 @@
|
|||
{%- if percentage -%}
|
||||
<progress value="{{ percentage }}" max="100">{{ percentage }}%</progress>
|
||||
{%- endif -%}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue