🛠 Podcast and Team pages

This commit is contained in:
Dennis Reimann
2020-10-05 14:51:06 +02:00
parent b2ed9bfb76
commit 7507cd5249
19 changed files with 261 additions and 171 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,4 @@
/dist
/generated
/log
/node_modules
/rev-manifest.json
/site-data.json
/feed.json

View File

@@ -3,21 +3,21 @@
"name": "Markus",
"twitter": "MarkusTurm",
"github": "MarkusTurm",
"text": "Bester Mann.",
"text": "Bester Mann. Toxic ☣️ aber fair. Sorgt für die Bitcoin Mass-Adoption \"one [Currywurstbude](http://www.curry-alm.info/) at a time\" 🌭",
"image": "/img/team/markus.jpg"
},
{
"name": "Gigi",
"twitter": "dergigi",
"github": "dergigi",
"text": "Der Gigi.",
"text": "Der Gigi leiht dir seine Taschenlampe 🔦 solltest du dich auf deinem Weg im [Kaninchenbau](https://21lessons.com/) mal verlaufen 🕳🐇",
"image": "/img/team/gigi.jpg"
},
{
"name": "Fab",
"twitter": "fabthefoxx",
"url": "http://fabthefox.com",
"text": "The Fox 🦊",
"text": "The Fox 🦊 verbreitet mit seinem Verlag Aprycot das Bitcoin-Wissen und ist der Wirt hinter der [Media-Theke](https://aprycot.media/thek/) 📙",
"image": "/img/team/fab.jpg"
},
{
@@ -25,13 +25,13 @@
"twitter": "dennisreimann",
"github": "dennisreimann",
"url": "https://d11n.net",
"text": "d11n",
"text": "Mag Open Source und [BTCPay Server](https://btcpayserver.org/) 💚 und schreibt lieber Software als Texte über sich 👨🏻‍💻",
"image": "/img/team/dennis.png"
},
{
"name": "Daniel",
"twitter": "danielwingen",
"text": "Value Of Bitcoin.",
"text": "Kennt den [Value Of Bitcoin](https://valueofbitcoin.com/) 🧊 und ist daher nicht nur Sound Money Maximalist, sondern auch Fiat Minimalist 💸",
"image": "/img/team/daniel.jpg"
}
]

View File

@@ -9,10 +9,10 @@
"node": ">=14.0.0"
},
"scripts": {
"clean": "rm -rf rev-manifest.json feed.json site-data.json dist/* && mkdir -p dist",
"clean": "rm -rf dist generated && mkdir -p dist generated",
"fetch": "node tasks/fetch_feed.js",
"start": "npm-run-all clean fetch -p start:*",
"start:pages": "onchange -i -k 'site-data.json' 'pug.config.js' 'markdown.js' 'src/**/*.pug' 'src/**/*.svg' 'tasks/generate_pages.js' -- npm run build:pages",
"start:pages": "onchange -i -k 'pug.config.js' 'markdown.js' 'content/**' 'generated/**' 'src/**/*.pug' 'src/**/*.svg' 'tasks/generate_pages.js' -- npm run build:pages",
"start:styles": "onchange -i -k 'src/**/*.css' -- npm run build:styles",
"start:data": "onchange -i -k 'content/**/*' -- npm run build:data",
"start:serve": "browser-sync start --config browser-sync.config.js --watch",
@@ -23,7 +23,7 @@
"build:styles": "postcss src/css/main.css --output dist/css/main.css",
"optimize": "npm-run-all -p optimize:* -s rev",
"optimize:styles": "csso dist/css/main.css --output dist/css/main.css",
"rev": "node-file-rev --root=dist dist/css/* dist/js/* dist/img/*.png dist/img/*.svg dist/img/team/*.jpg dist/img/team/*.png",
"rev": "node-file-rev --manifest=generated/rev.json --root=dist dist/css/* dist/js/* dist/img/*.png dist/img/*.svg dist/img/team/*.jpg dist/img/team/*.png",
"prod": "npm-run-all build optimize -s build:pages",
"images": "node tasks/optimize_images.js"
},

View File

@@ -3,10 +3,12 @@ const renderMarkdown = require('./markdown')
const slugify = str => str.toLowerCase().replace(/\W/, '-')
const random = max => Math.floor(Math.random() * Math.floor(max))
const shuffle = arr => { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * i); const temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }; return arr }
const formatDate = date => (new Date(date)).toISOString().replace(/T.*/, '').split('-').reverse().join('.')
const linkTarget = url => url.startsWith('http') ? '_blank' : null
const assetPath = path => {
let revs
try { revs = require('./rev-manifest.json') } catch (error) { }
try { revs = require('./generated/rev.json') } catch (error) { }
return `${(revs && revs[path]) || path}`
}
const assetUrl = (path, protocol = 'https') => {
@@ -16,9 +18,11 @@ const assetUrl = (path, protocol = 'https') => {
module.exports = {
basedir: './src/includes',
random,
shuffle,
slugify,
assetUrl,
assetPath,
formatDate,
linkTarget,
renderMarkdown,
}

View File

@@ -38,46 +38,55 @@ h4,
h5,
h6 {
font-family: var(--font-family-head);
letter-spacing: 0.04em;
line-height: 1.05;
& a {
color: currentColor;
color: inherit;
text-decoration: none !important;
}
}
h1 {
font-size: var(--font-size-xxxl);
}
h2 {
font-size: var(--font-size-xxl);
}
h3 {
h2 {
font-size: var(--font-size-xl);
}
h3 {
font-size: var(--font-size-l);
}
h4, h5, h6 {
font-size: var(--font-size-m);
}
a {
outline: 0;
color: inherit;
text-decoration: underline;
transition-property: background, color;
color: var(--color-accent);
text-decoration: none;
transition-property: color;
transition-duration: var(--transition-duration-fast);
&.plain {
color: inherit;
}
&:hover {
@media not all and (hover: none) {
color: var(--color-accent-highlight);
text-decoration: none;
&.plain {
color: var(--color-accent);
text-decoration: underline;
}
}
}
& svg {
transition-property: background, color;
transition-property: color;
transition-duration: var(--transition-duration-fast);
}
}
@@ -87,7 +96,7 @@ p {
}
ul {
margin-left: var(--space-m);
margin-left: 1rem;
margin-bottom: var(--space-l);
}
@@ -112,3 +121,21 @@ img:-moz-loading {
[aria-hidden="true"] {
display: none;
}
.button {
display: inline-block;
text-align: center;
padding: var(--space-m) var(--space-l);
color: var(--color-neutral-0);
background-color: var(--color-accent);
text-decoration: none;
border-radius: var(--space-m);
&:hover {
@media not all and (hover: none) {
color: var(--color-neutral-0);
background-color: var(--color-accent-highlight);
text-decoration: none;
}
}
}

View File

@@ -1,4 +1,5 @@
.footer {
text-align: center;
font-size: var(--font-size-xs);
color: var(--color-secondary);
}

View File

@@ -26,6 +26,17 @@
transition-duration: var(--transition-duration-fast);
}
& a {
color: var(--color-body-text);
&:hover {
@media not all and (hover: none) {
color: var(--color-accent);
text-decoration: none;
}
}
}
& .wrap {
@media (--L_and_up) {
display: flex;

View File

@@ -21,4 +21,14 @@
flex: 1;
padding-top: var(--space-xl);
padding-bottom: var(--space-xl);
& h1,
& h2 {
margin-bottom: var(--space-l);
color: var(--color-secondary);
}
& .lead {
margin-bottom: var(--space-xxl);
}
}

View File

@@ -6,14 +6,19 @@
:root {
--color-neutral-0: #fff;
--color-neutral-10: #ddd;
--color-neutral-10: #f6f6f6;
--color-neutral-50: #888;
--color-neutral-90: #222;
--color-neutral-95: #1B1B1B;
--color-body-text: var(--color-neutral-90);
--color-body-bg: var(--color-neutral-0);
--color-card-bg: var(--color-neutral-10);
--color-accent: #f7931a;
--color-accent-highlight: #dd7901;
--color-derweg: #00B4CF;
--color-interview: #151515;
--color-secondary: var(--color-neutral-50);
--space-xs: .125rem;
--space-s: .25rem;
@@ -26,16 +31,12 @@
--transition-duration-medium: 0.75s;
--transition-duration-slow: 1.5s;
--border-radius: 16px;
--opacity-text: 0.7;
--font-family-base: sans-serif;
--font-family-head: 'The Bold Font', var(--font-family-base);
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-size-base: 18px;
@@ -43,7 +44,7 @@
--font-size-s: .85rem;
--font-size-m: 1rem;
--font-size-l: 1.25rem;
--font-size-xl: 1.5rem;
--font-size-xl: 1.75rem;
--font-size-xxl: 2.5rem;
--font-size-xxxl: 4rem;
}
@@ -51,11 +52,13 @@
:root[data-theme="dark"] {
--color-body-text: var(--color-neutral-0);
--color-body-bg: var(--color-neutral-90);
--color-card-bg: var(--color-neutral-95);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-body-text: var(--color-neutral-0);
--color-body-bg: var(--color-neutral-90);
--color-card-bg: var(--color-neutral-95);
}
}

View File

@@ -1,104 +1,78 @@
#updates {
position: relative;
padding-top: 120px;
#podcast {
}
.episodes {
display: grid;
grid-gap: var(--space-xl);
grid-template-columns: 1fr;
margin: 0;
list-style: none;
@media (--up_to_L) {
grid-template-columns: 1fr;
}
@media (--L_and_up) {
grid-template-columns: 1fr 1fr;
}
}
.episodeItem {
margin: 0;
background-color: var(--color-card-bg);
border-radius: var(--space-l);
@media (--up_to_M) {
padding: var(--space-l);
}
@media (--M_and_up) {
padding-bottom: 180px;
padding: var(--space-xl);
}
& a {
display: flex;
}
& .media {
margin-right: var(--space-l);
& a,
& img {
display: block;
border-radius: var(--space-s);
@media (--up_to_M) {
height: 60px;
width: 60px;
}
@media (--M_and_up) {
height: 100px;
width: 100px;
}
}
}
& .meta {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
font-family: var(--font-family-head);
/* color: var(--color-secondary); */
}
& .content {
flex: 1;
& > *:last-child {
margin-bottom: 0;
}
}
& h3 {
@media (--up_to_M) {
font-size: 36px;
margin-bottom: var(--space-l);
}
@media (--M_and_up) {
font-size: 48px;
margin-bottom: var(--space-xl);
}
}
& .update + .update {
margin-top: var(--space-xxl);
}
&:after {
display: inline-block;
content: '';
position: absolute;
left: -65px;
bottom: -80px;
z-index: -1;
background-image: url(../img/bg/updates.svg);
background-repeat: no-repeat;
background-position: 0 100%;
background-size: contain;
max-width: 55%;
width: 369px;
height: 344px;
@media (--up_to_M) {
display: none;
}
}
}
.update {
display: block;
text-decoration: none !important;
@media (--M_and_up) {
display: flex;
align-items: center;
max-width: 53em;
transition-property: background, border, transform;
transition-duration: var(--transition-duration-fast);
}
& .image {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--color-neutral-90);
box-shadow: 0px 100px 80px rgba(12, 11, 24, 0.15), 0px 41.7776px 33.4221px rgba(12, 11, 24, 0.107828), 0px 22.3363px 17.869px rgba(12, 11, 24, 0.0894161), 0px 12.5216px 10.0172px rgba(12, 11, 24, 0.075), 0px 6.6501px 5.32008px rgba(12, 11, 24, 0.0605839), 0px 2.76726px 2.21381px rgba(12, 11, 24, 0.0421718);
border: 3px solid var(--color-neutral-90);
border-radius: var(--space-l);
& img {
max-width: 80%;
max-height: 80%;
}
@media (--up_to_M) {
width: 100%;
height: 195px;
margin-bottom: 25px;
}
@media (--M_and_up) {
flex: 1 0 42.5%;
height: 275px;
margin-right: 25px;
}
}
& h4 {
margin-bottom: 15px;
@media (--up_to_M) {
font-size: var(--font-size-xl);
}
}
& p {
opacity: var(--opacity-text);
color: var(--color-body-text) !important;
}
&:hover {
& .image {
@media not all and (hover: none) {
border-color: var(--color-accent);
background-image: linear-gradient(45deg, #1A136E 29.26%, #0D0AB7 92.45%);
}
}
margin-top: var(--space-s);
font-family: var(--font-family-base);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-m);
line-height: 1.2;
}
}

View File

@@ -1,7 +1,7 @@
#team {
& .members {
display: grid;
grid-gap: var(--space-xxl);
grid-gap: var(--space-xl);
margin: 0;
list-style: none;
@@ -18,37 +18,26 @@
& .member {
margin: 0;
text-align: center;
background-color: var(--color-card-bg);
border-radius: var(--space-l);
padding: var(--space-xl);
& img {
display: block;
display: inline-block;
border-radius: 50%;
@media (--up_to_L) {
height: 87px;
width: 87px;
}
@media (--L_and_up) {
height: 112px;
width: 112px;
}
height: 100px;
width: 100px;
}
& h4 {
margin-top: var(--space-m);
font-size: 21px;
}
& a {
font-size: var(--font-size-xs);
text-decoration: none;
text-transform: uppercase;
& h2 {
margin-top: var(--space-l);
margin-bottom: var(--space-m);
font-size: var(--font-size-xl);
}
& p {
margin: var(--space-s) 0 var(--space-l);
font-size: var(--font-size-s);
opacity: var(--opacity-text);
max-width: 30em;
margin-bottom: var(--space-l);
overflow-wrap: anywhere;
}

24
src/includes/mixins.pug Normal file
View File

@@ -0,0 +1,24 @@
mixin sprite(id)
svg(role="img" title=id)&attributes(attributes)
use(xlink:href=`${assetPath("/img/sprite.svg")}#${id}`)
mixin episodeItem(e)
article.episodeItem&attributes(attributes)
a.plain(href=e.anchor)
.media
img(src=e.image alt=e.title loading="lazy")
.content
.meta
span= e.categoryName + (e.number ? ` #${e.number}` : '')
time(datetime=e.date)= e.block || formatDate(e.date)
h3=e.titlePlain
mixin episodeDetails(e)
article.episodeDetails&attributes(attributes)
.media
a(href=e.anchor)
img(src=e.image alt=e.title loading="lazy")
.content
h3: a(href=e.anchor)=e.title
p=formatDate(e.date)
!=e.content

View File

@@ -1,3 +1,5 @@
include mixins
block vars
- const pageTitle = title ? `${title} · ${site.title}` : site.meta.title
@@ -6,10 +8,6 @@ block vars
- const pageCard = cardImage || site.meta.cardImage
- const themeColor = '#FFFFFF'
mixin sprite(id)
svg(role="img" title=id)&attributes(attributes)
use(xlink:href=`${assetPath("/img/sprite.svg")}#${id}`)
<!DOCTYPE html>
html(lang="en")
head

View File

@@ -1,13 +1,18 @@
extends /template.pug
block main
section
section#podcast
.lead
h1 Podcast
:markdown-it(html linkify typographer)
# Podcast
Content
Du findest unsere Episoden auf den üblichen Plattformen wie
[Spotify](https://open.spotify.com/show/10408JFbE1n8MexfrBv33r),
[Apple Podcasts](https://podcasts.apple.com/de/podcast/einundzwanzig-der-bitcoin-podcast/id1488229907),
[Overcast](https://overcast.fm/itunes1488229907/einundzwanzig-der-bitcoin-podcast) und
[Anchor](https://anchor.fm/einundzwanzig).
a.button(href="https://anchor.fm/s/d8d3c38/podcast/rss") Abonnieren / RSS
- [Spotify](https://open.spotify.com/show/10408JFbE1n8MexfrBv33r)
- [Apple Podcasts](https://podcasts.apple.com/de/podcast/einundzwanzig-der-bitcoin-podcast/id1488229907)
- [Overcast](https://overcast.fm/itunes1488229907/einundzwanzig-der-bitcoin-podcast)
- [Anchor](https://anchor.fm/einundzwanzig)
- [RSS](https://anchor.fm/s/d8d3c38/podcast/rss)
h2 Alle Episoden
.episodes
each e in episodes
+episodeItem(e)

View File

@@ -2,20 +2,21 @@ extends /template.pug
block main
section#team
.lead
h1 Team
ul.members
each m in team
ul.members(data-shuffle)
each m in shuffle(team)
li.member
img(src=(assetPath(m.image)) alt=m.name loading="lazy")
h4=m.name
p(style=(m.name.startsWith('Arik') ? 'word-break:break-all;' : null))=m.text
h2=m.name
!=renderMarkdown(m.text)
.links
if m.twitter
a(href=(m.twitter.startsWith('https://') ? m.twitter : `https://twitter.com/${m.twitter}`) target="_blank" title=`${m.name} on Twitter`)
a.plain(href=(m.twitter.startsWith('https://') ? m.twitter : `https://twitter.com/${m.twitter}`) target="_blank" title=`${m.name} on Twitter`)
+sprite("twitter")
if m.github
a(href=(m.github.startsWith('https://') ? m.github : `https://github.com/${m.github}`) target="_blank" title=`${m.name} on GitHub`)
a.plain(href=(m.github.startsWith('https://') ? m.github : `https://github.com/${m.github}`) target="_blank" title=`${m.name} on GitHub`)
+sprite("github")
if m.url
a(href=m.url target="_blank")
a.plain(href=m.url target="_blank")
+sprite("url")

View File

@@ -1,3 +1,5 @@
const shuffle = arr => { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * i); const temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }; return arr }
document.addEventListener("DOMContentLoaded", () => {
const $body = document.body
const $headerAnchor = document.getElementById('header-anchor')
@@ -32,4 +34,12 @@ document.addEventListener("DOMContentLoaded", () => {
headerObserver.observe($headerAnchor)
}
// List shuffling
const lists = document.querySelectorAll('[data-shuffle]')
lists.forEach(list => {
const items = Array.from(list.children)
list.innerHTML = ""
shuffle(items).forEach(item => list.appendChild(item))
})
})

View File

@@ -2,12 +2,46 @@ const { writeFileSync } = require('fs')
const { join, resolve } = require('path')
const Parser = require('rss-parser')
const dir = resolve(__dirname, '..')
const dst = join(dir, 'feed.json')
const dir = resolve(__dirname, '..', 'generated')
const write = (name, data) => writeFileSync(join(dir, `${name}.json`), JSON.stringify(data, null, 2))
const parseInfo = e => {
const titleMatch = e.title.match(/([\w\s]+?)?\s?#(\d+) - (.*)/)
const [, categoryName = 'News', number, titlePlain] = titleMatch ? titleMatch : [,,,e.title]
const blockMatch = e.contentSnippet.match(/Blockzeit\s(\d+)/)
const block = blockMatch ? parseInt(blockMatch[1]) : null
const category = categoryName.toLowerCase().replace(/\W/, '-')
return { block, category, categoryName, number, titlePlain }
}
;(async () => {
const parser = new Parser()
const feed = await parser.parseURL('https://anchor.fm/s/d8d3c38/podcast/rss')
writeFileSync(dst, JSON.stringify(feed, null, 2))
// Original Anchor-Feed
write('feed', feed)
// All episodes
const episodes = feed.items.map(e => ({
title: e.title.trim(),
content: e.content.trim(),
anchor: e.link,
date: e.isoDate,
enclosure: e.enclosure,
duration: e.itunes.duration,
image: e.itunes.image,
season: e.itunes.season,
episode: e.itunes.episode,
guid: e.guid,
...parseInfo(e)
}))
write('episodes', episodes)
// By category/season
write('news', episodes.filter(e => e.category === 'news'))
write('der-weg', episodes.filter(e => e.category === 'der-weg'))
write('interview', episodes.filter(e => e.category === 'interview'))
write('lesestunde', episodes.filter(e => e.category === 'lesestunde'))
})()

View File

@@ -1,9 +1,10 @@
const pug = require('pug')
const { mkdirSync, writeFileSync } = require('fs')
const { dirname, resolve } = require('path')
const config = require('../pug.config')
const site = require('../site-data')
const feed = require('../feed.json')
const site = require('../generated/site-data.json')
const episodes = require('../generated/episodes.json')
const team = require('../content/team.json')
const renderPage = (name, out, data = {}) => {
@@ -19,4 +20,4 @@ const renderPage = (name, out, data = {}) => {
renderPage('index', 'index', { navCurrent: 'index' })
renderPage('team', 'team/index', { navCurrent: 'team', team })
renderPage('podcast', 'podcast/index', { navCurrent: 'podcast', feed })
renderPage('podcast', 'podcast/index', { navCurrent: 'podcast', episodes })

View File

@@ -3,7 +3,7 @@ const { join, resolve } = require('path')
const meta = require('../content/meta.json')
const dir = resolve(__dirname, '..')
const dir = resolve(__dirname, '..', 'generated')
const dst = join(dir, 'site-data.json')
const date = (new Date()).toJSON().split('T')[0]