diff options
60 files changed, 2440 insertions, 0 deletions
diff --git a/Still Thinking About It.lektorproject b/Still Thinking About It.lektorproject new file mode 100644 index 0000000..92e8963 --- /dev/null +++ b/Still Thinking About It.lektorproject @@ -0,0 +1,11 @@ +[project] +name = Still Thinking About It +url = https://dennisfink.me +url_style = absolute +output_path = build + +[packages] +lektor-atom = 0.4.0 +lektor-markdown-image-attrs = 0.2.1 +lektor-index-pages = 1.1.0 +lektor-tags = 0.5.2 diff --git a/assets/static/css/.djlintrc b/assets/static/css/.djlintrc new file mode 100644 index 0000000..5882faa --- /dev/null +++ b/assets/static/css/.djlintrc @@ -0,0 +1,14 @@ +{ +"blank_line_before_tag": "macro", +"blank_line_after_tag": "endmacro,extends,import,from", +"profile": "jinja", +"format_css": true, +"css": { +"indent_size": 2 +}, +"format_js": true, +"js": { +"indent_size": 2 +}, +"indent": "2" +} diff --git a/assets/static/css/blog.css b/assets/static/css/blog.css new file mode 100644 index 0000000..48a716b --- /dev/null +++ b/assets/static/css/blog.css @@ -0,0 +1,10 @@ +@charset "UTF-8"; + +article > p:first-of-type:first-letter { + color: var(--blue); + float: left; + font-weight: 700; + font-size: 3.8rem; + line-height: 3.8rem; + margin: 0.2em 0.1em 0 0; +} diff --git a/assets/static/css/categories.css b/assets/static/css/categories.css new file mode 100644 index 0000000..5e26021 --- /dev/null +++ b/assets/static/css/categories.css @@ -0,0 +1,61 @@ +@charset "UTF-8"; + +article > ul { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + list-style: none; + margin: 0; + padding: 0; + + @media (max-width: 500px) { + grid-template-columns: 1fr; + } + + li { + max-inline-size: unset; + } + + a { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 1rem; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + height: 100%; + + &:hover, + &:hover > dl { + border-color: var(--blue); + /*background-color: var(--blue);*/ + color: var(--blue); + } + + h3 { + margin: 0; + } + + dl { + display: flex; + flex-direction: column; + color: var(--dim-foreground); + gap: 0.25rem; + font-size: 1.2rem; + font-style: italic; + line-height: initial; + margin: 0; + + div { + display: flex; + flex-direction: row; + gap: 0.25rem; + } + + dd { + margin: 0; + max-inline-size: unset; + } + } + } +} diff --git a/assets/static/css/category.css b/assets/static/css/category.css new file mode 100644 index 0000000..e3567d8 --- /dev/null +++ b/assets/static/css/category.css @@ -0,0 +1,17 @@ +@charset "UTF-8"; + +article > table { + border-collapse: collapse; + + td { + vertical-align: top; + padding-bottom: 0.25rem; + } + + td:first-child { + color: var(--dim-foreground); + font-style: italic; + padding-right: 2rem; + white-space: nowrap; + } +} diff --git a/assets/static/css/libre_baskerville.css b/assets/static/css/libre_baskerville.css new file mode 100644 index 0000000..fa61e73 --- /dev/null +++ b/assets/static/css/libre_baskerville.css @@ -0,0 +1,73 @@ +@charset "UTF-8"; + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 400; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 400; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 500; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 500; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 600; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 600; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: normal; + font-weight: 700; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2") + format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "Libre Baskerville"; + font-style: italic; + font-weight: 700; + src: url("/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2") + format("woff2"); +} diff --git a/assets/static/css/modern_normalize.css b/assets/static/css/modern_normalize.css new file mode 100644 index 0000000..708b6a2 --- /dev/null +++ b/assets/static/css/modern_normalize.css @@ -0,0 +1,194 @@ +/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ +/* +Document +======== +*/ +/** +Use a better box model (opinionated). +*/ +*, +::before, +::after { + box-sizing: border-box; +} + +html { + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ + font-family: + system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + line-height: 1.15; + /* 1. Correct the line height in all browsers. */ + -webkit-text-size-adjust: 100%; + /* 2. Prevent adjustments of font size after orientation changes in iOS. */ + tab-size: 4; + /* 3. Use a more readable tab size (opinionated). */ +} + +/* +Sections +======== +*/ +body { + margin: 0; + /* Remove the margin in all browsers. */ +} + +/* +Text-level semantics +==================== +*/ +/** +Add the correct font weight in Chrome and Safari. +*/ +b, +strong { + font-weight: bolder; +} + +/** +1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) +2. Correct the odd 'em' font sizing in all browsers. +*/ +code, +kbd, +samp, +pre { + font-family: + ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** +Add the correct font size in all browsers. +*/ +small { + font-size: 80%; +} + +/** +Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. +*/ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +Tabular data +============ +*/ +/** +Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) +*/ +table { + border-color: currentcolor; +} + +/* +Forms +===== +*/ +/** +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +*/ +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ +} + +/** +Correct the inability to style clickable types in iOS and Safari. +*/ +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** +Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. +*/ +legend { + padding: 0; +} + +/** +Add the correct vertical alignment in Chrome and Firefox. +*/ +progress { + vertical-align: baseline; +} + +/** +Correct the cursor style of increment and decrement buttons in Safari. +*/ +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/** +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** +Remove the inner padding in Chrome and Safari on macOS. +*/ +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to 'inherit' in Safari. +*/ +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Interactive +=========== +*/ +/* +Add the correct display in Chrome and Safari. +*/ +summary { + display: list-item; +} diff --git a/assets/static/css/style.css b/assets/static/css/style.css new file mode 100644 index 0000000..ca7443a --- /dev/null +++ b/assets/static/css/style.css @@ -0,0 +1,753 @@ +@charset "UTF-8"; + +/* +============================================================================= +CUSTOM PROPERTIES +============================================================================= +*/ +:root { + /* Named palette */ + --black: light-dark(#575279, #393b44); + --red: light-dark(#b4637a, #c94f6d); + --green: light-dark(#618774, #81b29a); + --yellow: light-dark(#ea9d34, #dbc074); + --blue: light-dark(#286983, #719cd6); + --magenta: light-dark(#907aa9, #9d79d6); + --cyan: light-dark(#56949f, #63cdcf); + --white: light-dark(#e5e9f0, #dfdfe0); + --orange: light-dark(#d7827e, #f4a261); + --pink: light-dark(#d685af, #d67ad2); + /* Dim palette */ + --dim-black: light-dark(#504c6b, #30323a); + --dim-red: light-dark(#a5576d, #ab435d); + --dim-green: light-dark(#597668, #6e9783); + --dim-yellow: light-dark(#dd9024, #baa363); + --dim-blue: light-dark(#295e73, #6085b6); + --dim-magenta: light-dark(#816b9a, #8567b7); + --dim-cyan: light-dark(#50848c, #54aeb0); + --dim-white: light-dark(#c8cfde, #bebebe); + --dim-foreground: light-dark(#4c4769, #aeafb0); + /* Bright palette */ + --bright-black: light-dark(#5f5695, #575860); + --bright-red: light-dark(#c26d85, #d16983); + --bright-green: light-dark(#629f81, #8ebaa4); + --bright-yellow: light-dark(#eea846, #e0c989); + --bright-blue: light-dark(#2d81a3, #86abdc); + --bright-magenta: light-dark(#9a80b9, #baa1e2); + --bright-cyan: light-dark(#5ca7b4, #7ad5d6); + --bright-white: light-dark(#e6ebf3, #e4e4e5); + --bright-foreground: light-dark(#625c87, #d6d6d7); + /* Semantic */ + --foreground: light-dark(#575279, #cdcecf); + --background: light-dark(#faf4ed, #192330); + /* Layout */ + --measure: 60ch; + --column-min-width: calc(1/3 * 100vw); + --column-max-width: 100%; + /* Gradient border — reused in header, footer */ + --gradient-border: linear-gradient( + to right, + var(--foreground), + var(--green), + var(--foreground) + ); + + /* Responsive layout */ + @media (min-width: 500px) { + --column-max-width: var(--measure); + } + + &:has(input[name="theme-switcher"][value="light dark"]:checked) { + color-scheme: light dark; + } + + &:has(input[name="theme-switcher"][value="light"]:checked) { + color-scheme: light; + } + + &:has(input[name="theme-switcher"][value="dark"]:checked) { + color-scheme: dark; + } +} + +/* +============================================================================= +BASE +============================================================================= + */ +html { + font-size: 14px; +} + +body { + display: flex; + flex-direction: column; + min-height: 100vh; + min-width: var(--column-min-width); + max-width: var(--column-max-width); + margin: 0 auto; + background-color: var(--background); + color: var(--foreground); + font-family: "Libre Baskerville", Georgia, serif; + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 1.6; + + @media (max-width: 500px) { + padding: 0 1.5rem; + } + + & > footer { + border-image-slice: 1; + border-image-source: var(--gradient-border); + border-image-width: 1px 0 0 0; + border-top: 1px solid; + display: flex; + flex-direction: row; + justify-content: space-between; + margin-bottom: 2rem; + margin-top: auto; + padding-top: 0.5rem; + + @media (max-width: 500px) { + flex-direction: column; + gap: 2rem; + } + + h2 { + font-size: clamp(1.2rem, 2.5vw, 1.7rem); + font-style: italic; + font-weight: 400; + line-height: 1; + } + + section div { + display: flex; + flex-direction: row; + gap: 0.25rem; + } + + & > section[aria-label="Follow me"] a { + background-color: var(--green); + display: inline-block; + height: 3rem; + mask-size: cover; + width: 3rem; + + &:hover { + background-color: var(--blue); + } + + &#atom { + mask-image: url("/static/images/rss.svg"); + } + + &#mastodon { + mask-image: url("/static/images/mastodon.svg"); + } + + &#xmpp { + mask-image: url("/static/images/xmpp.svg"); + } + + &#email { + mask-image: url("/static/images/email.svg"); + } + } + + ul { + list-style: none; + margin: 0; + padding: 0; + } + } +} + +/* +============================================================================= +TYPOGRAPHY +============================================================================= +*/ + +/* --- Headings --- */ +h1 { + font-size: clamp(2rem, 4vw, 3.2rem); + font-weight: 700; + line-height: 1; + margin: 0 0 0.5rem 0; +} + +h2 { + font-size: clamp(1.5rem, 3vw, 2.2rem); + font-style: italic; + font-weight: 500; + line-height: 1; + margin: 1rem 0; +} + +h3 { + font-size: clamp(1.2rem, 2.5vw, 1.7rem); + font-style: italic; + font-weight: 400; + line-height: 1; + margin-bottom: 1rem; +} + +/* --- Body text --- */ +p { + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 1.5em; + margin: 0; + text-align: justify; + text-wrap: pretty; + word-spacing: 0.16em; + + &.center { + text-align: center; + } +} + +blockquote { + font-style: italic; + border-left: 2px solid var(--blue); + margin: 1em 0 1em 0.5em; + padding-left: 0.5em; +} + +/* --- Lists --- */ +ul, +ol, +dl { + font-size: clamp(1rem, 1.5vw, 1.4rem); + line-height: 2rem; +} + +li, +dd { + max-inline-size: var(--measure); +} + +/* --- Rules --- */ +hr { + border: 0; + border-top: 2px solid var(--blue); + margin: 1em 0; +} + +/* --- Links --- */ +a { + color: var(--green); + text-decoration: none; + text-decoration-thickness: 0.05em; + text-underline-offset: 0.1em; + + &:hover { + color: var(--blue); + } +} + +/* +============================================================================= +SKIP LINK (accessibility) +============================================================================= + */ +.skip-link { + background: #fff; + color: #000; + left: 0; + padding: 0.5rem 1rem; + position: absolute; + text-decoration: none; + top: 0; + transform: translateY(-120%); + transition: transform 0.2s ease; + z-index: 1000; + + &:focus, + &:focus-visible { + transform: translateY(0); + } +} + +.visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +/* +============================================================================= +SITE HEADER +============================================================================= +*/ +body > header { + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: space-evenly; + margin-top: 4rem; + padding-bottom: 1rem; + border-bottom: 1px solid; + border-image-slice: 1; + border-image-source: var(--gradient-border); + border-image-width: 0 0 1px 0; + + & > div { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 0.25rem; + } + + svg { + max-width: 4em; + max-height: 4em; + fill: var(--green); + + @media (max-width: 500px) { + display: none; + } + } + + p { + font-size: clamp(1.1rem, 2vw, 1.75rem); + font-style: italic; + max-width: unset; + } + + /* --- Navigation --- */ + nav { + ul { + display: flex; + flex-direction: row; + gap: 1rem; + list-style: none; + margin: 0; + padding: 0; + } + + a[aria-current="page"] { + color: var(--blue); + + &:hover { + color: var(--bright-blue); + } + } + } + + /* --- Theme switcher --- */ + fieldset { + align-items: center; + border: 0; + display: flex; + flex-direction: row; + gap: 0.25rem; + justify-content: flex-start; + margin: 0; + padding: 0; + + input[type="radio"] { + display: none; + + &:checked + span { + background-color: var(--yellow); + } + } + + label span { + background-color: var(--foreground); + cursor: pointer; + display: block; + height: 1.5rem; + mask-size: cover; + width: 1.5rem; + } + } +} + +#lightmode + span, +#automode + span, +#darkmode + span { + mask-repeat: no-repeat; + mask-position: 50% 50%; +} + +#lightmode + span { + mask-image: url("/static/images/light.svg"); +} + +#automode + span { + mask-image: url("/static/images/auto.svg"); +} + +#darkmode + span { + mask-image: url("/static/images/dark.svg"); +} + +/* +============================================================================= +MAIN +============================================================================= +*/ +main { + display: flex; + flex-direction: column; + margin-bottom: 2rem; + margin-top: 2rem; +} + +/* +============================================================================= +ARTICLES +============================================================================= +*/ +article { + max-width: var(--column-max-width); + min-width: var(--column-min-width); + + & > header { + display: flex; + flex-direction: row; + align-items: start; + justify-content: space-between; + + &:not(:has(nav)) { + margin-bottom: 2rem; + } + + & > div { + display: flex; + flex-direction: column; + gap: 0.75rem; + max-width: calc(2 / 3 * 100%); + + /* --- Article metadata --- */ + & > dl { + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + font-size: 1rem; + gap: calc(1 / 3 * 1rem); + margin: 0; + + div { + align-items: center; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + display: flex; + flex-direction: row; + } + + dt { + color: var(--bright-blue); + padding-left: calc(1 / 3 * 1rem); + padding-right: calc(1 / 3 * 1rem); + } + + dd { + margin: 0; + padding-right: calc(1 / 3 * 1rem); + + a { + color: var(--green); + } + } + + @media (max-width: 700px) { + margin-top: 2rem; + } + + @media (max-width: 500px) { + flex-direction: column; + align-items: flex-start; + } + } + } + + h2 { + margin-bottom: 0.25rem; + margin-top: 0; + } + + /* --- TOC --- */ + & > nav { + border: 1px solid var(--foreground); + border-radius: 0.5rem; + font-size: 1rem; + line-height: 1.4; + padding: 0.75rem; + max-width: calc(1 / 3 * 100%); + + & > p { + color: var(--blue); + font-size: 1em; + font-weight: 700; + line-height: 1.2; + margin: 0 0 0.5rem 0; + } + + ol { + font-size: 1em; + line-height: 1.4; + margin: 0; + padding-left: 1.5em; + + ol { + margin-top: 0.25rem; + padding-left: 1.25em; + } + } + + li { + max-inline-size: none; + + & + li { + margin-top: 0.25rem; + } + } + + a { + overflow-wrap: anywhere; + } + + @media (max-width: 700px) { + margin-top: 2rem; + } + } + } + + /* --- Article body --- */ + p { + max-inline-size: var(--measure); + + & + p { + margin-top: 2rem; + } + } + + & > h3:not(:first-of-type) { + margin-top: 2rem; + } + + & > footer { + color: var(--dim-foreground); + font-style: italic; + margin-top: 1.5rem; + + p { + display: inline; + font-size: 1.2rem; + line-height: 1.5; + margin: 0; + padding: 0; + } + + ul { + font-size: 1.2rem; + line-height: 1.5; + margin: 0; + padding: 0; + display: inline-flex; + flex-wrap: wrap; + list-style: none; + } + + li { + display: flex; + max-inline-size: none; + } + } + + /* --- Article separator --- */ + & + hr { + align-items: center; + background: none; + border: 0; + display: flex; + height: 1em; + justify-content: center; + margin: 4em 0; + position: relative; + width: 100%; + + &::before { + background-image: var(--gradient-border); + content: ""; + height: 1px; + left: 0; + position: absolute; + right: 0; + top: 50%; + } + + &::after { + background-color: var(--background); + color: var(--blue); + content: "§"; + padding: 0 1rem; + position: relative; + transform: rotate(90deg); + z-index: 1; + } + } +} + +/* +============================================================================= +PAGINATION +============================================================================= + */ +nav:is( + [aria-label="Page navigation"], + [aria-label="Year archive navigation"], + [aria-label="Monthly archive navigation"] +) { + display: flex; + flex-direction: row; + justify-content: center; + + ol { + align-items: center; + border: 1px solid var(--foreground); + border-radius: 0.5rem; + display: flex; + flex-direction: row; + gap: 0.25rem; + list-style: none; + margin: 2rem 0; + padding: 0.25rem; + } + + a, + span { + align-items: center; + display: flex; + height: 2rem; + justify-content: center; + min-width: 2rem; + padding-inline: 0.5rem; + } + + & a { + border-radius: 0.5rem; + + &:hover, + &[aria-current="page"]:hover { + background-color: var(--blue); + color: var(--foreground); + } + + &[aria-current="page"] { + color: var(--blue); + } + } + + span[aria-disabled="true"] { + color: var(--dim-foreground); + } +} + +/* +============================================================================= +IMAGES +============================================================================= +*/ +img { + height: auto; + max-width: 100%; + + &.center { + display: block; + margin-inline: auto; + } + + &.round { + border-radius: 100%; + } + + &.w25 { + width: 25%; + } + + &.w33 { + width: calc(1 / 3 * 100%); + } + + &.w50 { + width: 50%; + } + + &.w66 { + width: calc(2 / 3 * 100%); + } + + &.w75 { + width: 75%; + } + + &.breakout-xs { + width: calc(100% + 16rem); + transform: translateX(-8rem); + } + + &.breakout-sm { + width: calc(100% + 24rem); + transform: translateX(-12rem); + } + + &.breakout-md { + width: calc(100% + 32rem); + transform: translateX(-16rem); + } + + &.breakout-lg { + width: calc(100% + 48rem); + transform: translateX(-24rem); + } + + &.breakout-xl { + width: calc(100% + 64rem); + transform: translateX(-32rem); + } + + &.breakout-full { + width: 100vw; + margin-left: calc(50% - 50vw); + } + + &.breakout-xs, + &.breakout-sm, + &.breakout-md, + &.breakout-lg, + &.breakout-xl, + &.breakout-full { + max-width: 100vw; + } + + @media (max-width: 500px) { + &.breakout-xs, + &.breakout-sm, + &.breakout-md, + &.breakout-lg, + &.breakout-xl { + width: 100vw; + transform: none; + margin-left: calc(50% - 50vw); + } + } +} + +.missing-publication-date-warning { + background: var(--red); + color: var(--foreground); + font-weight: 700; + font-size: 1.1rem; +} + +.missing-publication-date-warning > dt { + color: var(--foreground); +} diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 Binary files differnew file mode 100644 index 0000000..4c373fe --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 Binary files differnew file mode 100644 index 0000000..7e780a9 --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-500italic.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 Binary files differnew file mode 100644 index 0000000..f94f429 --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 Binary files differnew file mode 100644 index 0000000..067b7fb --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-600italic.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 Binary files differnew file mode 100644 index 0000000..4e0f4e7 --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 Binary files differnew file mode 100644 index 0000000..5351437 --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-700italic.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 Binary files differnew file mode 100644 index 0000000..733d0dd --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-italic.woff2 diff --git a/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 Binary files differnew file mode 100644 index 0000000..3c2170e --- /dev/null +++ b/assets/static/fonts/libre-baskerville-v24-latin_latin-ext-regular.woff2 diff --git a/assets/static/images/auto.svg b/assets/static/images/auto.svg new file mode 100644 index 0000000..5032980 --- /dev/null +++ b/assets/static/images/auto.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"> + <path d="M276-280h76l40-112h176l40 112h76L520-720h-80zm138-176 64-182h4l64 182zm66 376q-83 0-156-31.5T197-197t-85.5-127T80-480t31.5-156T197-763t127-85.5T480-880t156 31.5T763-763t85.5 127T880-480t-31.5 156T763-197t-127 85.5T480-80m0-80q133 0 226.5-93.5T800-480t-93.5-226.5T480-800t-226.5 93.5T160-480t93.5 226.5T480-160"/> +</svg> diff --git a/assets/static/images/dark.svg b/assets/static/images/dark.svg new file mode 100644 index 0000000..ccb82f5 --- /dev/null +++ b/assets/static/images/dark.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"> + <path d="M480-120q-150 0-255-105T120-480t105-255 255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120m0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82m-10-270"/> +</svg> diff --git a/assets/static/images/email.svg b/assets/static/images/email.svg new file mode 100644 index 0000000..2c197d4 --- /dev/null +++ b/assets/static/images/email.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640"> + <defs> + <clipPath id="a" clipPathUnits="userSpaceOnUse"> + <path fill="#fff" d="M-5-5h650v650H-5Zm145.8 65A81 81 0 0 0 60 140.8v2.3L291.4 345a43.3 43.3 0 0 0 57.2 0L580 143.1v-2.3A81 81 0 0 0 499.2 60ZM60 210.1v242l138.8-121Zm520 0-138.8 121 138.8 121ZM237.2 364.6 62 517.2a81 81 0 0 0 78.7 62.8h358.4a81 81 0 0 0 78.7-62.8L403 364.6 381.6 383a94 94 0 0 1-123.4 0z" class="powerclip"/> + </clipPath> + </defs> + <path d="M91.4 0C41 0 0 41 0 91.4v457.2C0 599 41 640 91.4 640h457.2c50.4 0 91.4-41 91.4-91.4V91.4C640 41 599 0 548.6 0Z" clip-path="url(#a)"/> +</svg> diff --git a/assets/static/images/favicon.png b/assets/static/images/favicon.png Binary files differnew file mode 100644 index 0000000..cb4a2f8 --- /dev/null +++ b/assets/static/images/favicon.png diff --git a/assets/static/images/light.svg b/assets/static/images/light.svg new file mode 100644 index 0000000..56e1084 --- /dev/null +++ b/assets/static/images/light.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"> + <path d="M480-360q50 0 85-35t35-85-35-85-85-35-85 35-35 85 35 85 85 35m0 80q-83 0-141.5-58.5T280-480t58.5-141.5T480-680t141.5 58.5T680-480t-58.5 141.5T480-280M200-440H40v-80h160zm720 0H760v-80h160zM440-760v-160h80v160zm0 720v-160h80v160zM256-650l-101-97 57-59 96 100zm492 496-97-101 53-55 101 97zm-98-550 97-101 59 57-100 96zM154-212l101-97 55 53-97 101zm326-268"/> +</svg> diff --git a/assets/static/images/logo.svg b/assets/static/images/logo.svg new file mode 100644 index 0000000..a3090ad --- /dev/null +++ b/assets/static/images/logo.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="800" + height="800" + viewBox="0 0 800 800" + version="1.1" + id="svg1" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1" /> + <g + id="layer1"> + <path + id="logo" + style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;stroke-linecap:round;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" + d="M 89.449219 0 C 40.058022 0 1.8947806e-14 40.058022 0 89.449219 L 0 710.55078 C 0 759.94198 40.058022 800 89.449219 800 L 710.55078 800 C 759.94198 800 800 759.94198 800 710.55078 L 800 89.449219 C 800 40.058022 759.94198 1.8947806e-14 710.55078 0 L 89.449219 0 z M 89.449219 18.898438 L 710.55078 18.898438 C 749.79958 18.898438 781.10156 50.200416 781.10156 89.449219 L 781.10156 710.55078 C 781.10156 749.79958 749.79958 781.10156 710.55078 781.10156 L 89.449219 781.10156 C 50.200416 781.10156 18.898438 749.79958 18.898438 710.55078 L 18.898438 89.449219 C 18.898438 50.200416 50.200416 18.898438 89.449219 18.898438 z M 237.22852 89.824219 C 225.30581 89.764099 212.63778 89.813149 199.9668 89.984375 C 174.62483 90.326834 149.28529 91.151988 129.875 92.552734 C 120.16986 93.253107 111.95683 94.094899 105.89258 95.111328 C 102.86045 95.619543 100.37354 96.163921 98.419922 96.806641 C 96.466308 97.44936 94.86156 97.846472 93.806641 99.865234 C 93.279181 100.87462 93.231332 101.55582 93.056641 102.66797 C 92.881949 103.78011 92.722961 105.17836 92.570312 106.88086 C 92.265016 110.28586 91.986428 114.89969 91.728516 120.58594 C 91.212692 131.95844 90.783451 147.59876 90.4375 166.10156 C 89.745598 203.10716 89.388911 251.55165 89.359375 299.99805 C 89.329835 348.44444 89.628091 396.88867 90.246094 433.89258 C 90.555095 452.39453 90.944683 468.0332 91.414062 479.40234 C 91.648753 485.08692 91.901424 489.70206 92.179688 493.10156 C 92.318821 494.80131 92.46257 496.19404 92.621094 497.29883 C 92.779617 498.40362 92.824569 499.06954 93.294922 500.05469 C 94.235628 502.02499 95.788862 502.51581 97.603516 503.17188 C 99.418169 503.82794 101.70553 504.38069 104.48633 504.90039 C 110.04793 505.93978 117.5577 506.81642 126.42773 507.5918 C 144.16781 509.14256 167.31086 510.26508 190.44922 511.20312 C 213.58758 512.14117 236.7154 512.89431 254.34375 513.69336 C 263.15792 514.09289 270.60218 514.50483 275.94727 514.95312 C 278.61981 515.17728 280.7727 515.41242 282.25781 515.64648 C 282.90195 515.74801 283.35182 515.84713 283.70117 515.93359 C 283.78347 516.22135 283.87385 516.54876 283.97266 517.07031 C 284.22082 518.38022 284.47263 520.31606 284.70312 522.72266 C 285.16413 527.53585 285.55582 534.26272 285.86914 542.23047 C 286.49577 558.16597 286.81533 579.08637 286.83008 600.00195 C 286.84478 620.91754 286.55476 641.83795 285.96484 657.77539 C 285.66989 665.74411 285.29805 672.47212 284.85938 677.28906 C 284.64004 679.69754 284.40142 681.63565 284.16406 682.95117 C 284.07046 683.46982 283.98337 683.79442 283.9043 684.08594 C 283.58194 684.16714 283.19736 684.25852 282.61719 684.35352 C 281.21411 684.58325 279.16828 684.81373 276.62695 685.02734 C 271.5443 685.45456 264.45709 685.825 256.06445 686.14258 C 239.27918 686.77773 217.24939 687.20155 195.21289 687.48438 C 151.13989 688.05004 107.02734 688.05078 104.52734 688.05078 C 102.02734 688.05078 98.641913 688.05141 95.818359 688.11133 C 94.406583 688.14133 93.141642 688.18323 92.148438 688.25586 C 91.651836 688.29216 91.23338 688.33186 90.820312 688.40234 C 90.61378 688.43764 90.414344 688.47274 90.121094 688.57227 C 89.827843 688.67177 89.236367 688.53317 88.550781 689.9043 C 88.207986 690.58988 88.31307 690.61039 88.292969 690.74414 C 88.272869 690.87789 88.263066 690.97709 88.253906 691.08398 C 88.235576 691.29777 88.222867 691.52748 88.210938 691.80273 C 88.187088 692.35324 88.169277 693.07184 88.154297 693.91797 C 88.124347 695.61022 88.109375 697.80361 88.109375 700 C 88.109375 702.19639 88.124347 704.38978 88.154297 706.08203 C 88.169277 706.92816 88.187088 707.64676 88.210938 708.19727 C 88.222868 708.47252 88.235576 708.70223 88.253906 708.91602 C 88.263106 709.02291 88.272869 709.12211 88.292969 709.25586 C 88.313069 709.38961 88.207989 709.41012 88.550781 710.0957 C 89.236367 711.46688 89.829462 711.32826 90.123047 711.42773 C 90.416631 711.52723 90.615224 711.56244 90.822266 711.59766 C 91.236349 711.66806 91.655986 711.70786 92.15625 711.74414 C 93.156778 711.81674 94.437764 711.8587 95.878906 711.88867 C 98.761191 711.94857 102.26377 711.94922 105 711.94922 C 107.73623 711.94922 156.24228 711.95001 204.73047 711.26562 C 228.97457 710.92344 253.21138 710.41009 271.75 709.63867 C 281.01931 709.25296 288.8579 708.80451 294.60352 708.27344 C 297.47632 708.0079 299.81921 707.72376 301.61523 707.40039 C 302.51324 707.23871 303.27067 707.07151 303.94531 706.85938 C 304.61996 706.64724 305.2084 706.55281 306.01953 705.70508 C 306.83066 704.85735 306.89408 704.29292 307.0918 703.60547 C 307.28951 702.91802 307.44677 702.13619 307.59961 701.20508 C 307.90529 699.34285 308.17506 696.89835 308.42383 693.90039 C 308.92137 687.90447 309.33027 679.71432 309.66016 670.0293 C 310.31992 650.65926 310.65515 625.33315 310.66992 600.00195 C 310.68472 574.67076 310.37858 549.34304 309.75586 529.97461 C 309.4445 520.2904 309.05597 512.10021 308.58203 506.10742 C 308.34506 503.11103 308.08984 500.6699 307.79883 498.8125 C 307.65332 497.8838 307.50174 497.10586 307.31445 496.42383 C 307.12717 495.7418 307.08097 495.19821 306.29492 494.33789 C 305.50888 493.47757 304.9182 493.36892 304.26172 493.15234 C 303.60524 492.93577 302.8759 492.7654 302.01367 492.60156 C 300.28921 492.27388 298.04949 491.98609 295.30469 491.71094 C 289.81508 491.16063 282.33094 490.67502 273.48438 490.21484 C 255.79127 489.29449 232.66667 488.48402 209.55078 487.54688 C 186.43489 486.60974 163.32537 485.54426 145.74414 484.125 C 136.95353 483.41537 129.53535 482.61374 124.26367 481.71484 C 121.62783 481.2654 119.5206 480.78292 118.13281 480.32617 C 117.33987 480.0652 116.84141 479.69833 116.63477 479.57617 C 116.5799 479.36568 116.51307 479.08883 116.46484 478.7832 C 116.33281 477.94625 116.19434 476.75342 116.06055 475.26758 C 115.79295 472.2959 115.53859 468.14179 115.30469 463.00781 C 114.93817 454.96289 114.65616 442.90145 114.38281 430.66406 C 111.81701 367.27316 111.8204 241.56099 114.04883 173.92188 C 114.31219 159.38466 114.61266 146.17097 114.99023 136.98047 C 115.20123 131.84464 115.42929 127.68866 115.67188 124.71094 C 115.79317 123.22208 115.92033 122.02473 116.04102 121.17969 C 116.08887 120.84462 116.15604 120.54034 116.20898 120.31836 C 116.38248 120.20406 116.78698 119.894 117.45508 119.65234 C 118.67592 119.21077 120.56406 118.73745 122.93555 118.30078 C 127.67852 117.42745 134.38147 116.66617 142.32812 116.03906 C 158.22144 114.78484 179.12946 114.0481 200.0332 113.76562 C 220.93695 113.48314 241.85112 113.65282 257.78125 114.20508 C 265.74631 114.48121 272.472 114.85497 277.2832 115.30664 C 279.68881 115.53248 281.62259 115.78039 282.93164 116.02734 C 283.45251 116.12564 283.77897 116.21676 284.06641 116.29883 C 284.15271 116.64787 284.25061 117.09658 284.35156 117.74023 C 284.58436 119.22454 284.81786 121.37722 285.03711 124.04883 C 285.4756 129.39204 285.86809 136.8329 286.22852 145.64453 C 286.94936 163.26779 287.54486 186.39045 288.16992 209.51758 C 288.79498 232.6447 289.44871 255.7764 290.29102 273.47461 C 290.71217 282.32372 291.18012 289.80734 291.7207 295.29883 C 291.99099 298.04457 292.27505 300.28644 292.60156 302.01172 C 292.76482 302.87436 292.93586 303.60489 293.15234 304.26172 C 293.36883 304.91855 293.47757 305.50888 294.33789 306.29492 C 295.19821 307.08097 295.74181 307.12716 296.42383 307.31445 C 297.10584 307.50174 297.88382 307.6533 298.8125 307.79883 C 300.66986 308.08989 303.11107 308.34483 306.10742 308.58203 C 312.10013 309.05643 320.29051 309.44656 329.97461 309.75977 C 349.34282 310.38618 374.66929 310.69922 400 310.69922 C 425.33071 310.69922 450.65718 310.38618 470.02539 309.75977 C 479.70949 309.44656 487.89987 309.05643 493.89258 308.58203 C 496.88893 308.34483 499.33014 308.08989 501.1875 307.79883 C 502.11618 307.6533 502.89416 307.50174 503.57617 307.31445 C 504.25819 307.12716 504.80179 307.08097 505.66211 306.29492 C 506.52243 305.50888 506.63117 304.91855 506.84766 304.26172 C 507.06414 303.60489 507.23518 302.87436 507.39844 302.01172 C 507.72495 300.28644 508.00901 298.04457 508.2793 295.29883 C 508.81988 289.80734 509.28783 282.32372 509.70898 273.47461 C 510.55129 255.7764 511.20502 232.6447 511.83008 209.51758 C 512.45514 186.39045 513.05064 163.26779 513.77148 145.64453 C 514.13191 136.8329 514.5244 129.39204 514.96289 124.04883 C 515.18214 121.37722 515.41564 119.22454 515.64844 117.74023 C 515.74939 117.09658 515.84734 116.64787 515.93359 116.29883 C 516.22103 116.21673 516.54749 116.12561 517.06836 116.02734 C 518.37741 115.78039 520.31119 115.53248 522.7168 115.30664 C 527.528 114.85497 534.25369 114.48121 542.21875 114.20508 C 558.14888 113.65282 579.06305 113.48313 599.9668 113.76562 C 620.87054 114.04811 641.77857 114.78484 657.67188 116.03906 C 665.61853 116.66617 672.32148 117.42745 677.06445 118.30078 C 679.43594 118.73745 681.32408 119.21077 682.54492 119.65234 C 683.21303 119.894 683.61752 120.20407 683.79102 120.31836 C 683.84396 120.54034 683.91114 120.84462 683.95898 121.17969 C 684.07967 122.02473 684.20684 123.22208 684.32812 124.71094 C 684.57071 127.68866 684.79877 131.84464 685.00977 136.98047 C 685.38734 146.17097 685.68781 159.38466 685.95117 173.92188 C 688.1796 241.56099 688.18299 367.27316 685.61719 430.66406 C 685.34384 442.90145 685.06183 454.96289 684.69531 463.00781 C 684.46141 468.14179 684.20705 472.2959 683.93945 475.26758 C 683.80566 476.75342 683.66719 477.94625 683.53516 478.7832 C 683.48695 479.08881 683.4201 479.36569 683.36523 479.57617 C 683.15861 479.6983 682.66017 480.06514 681.86719 480.32617 C 680.4794 480.78292 678.37217 481.2654 675.73633 481.71484 C 670.46465 482.61374 663.04647 483.41537 654.25586 484.125 C 636.67463 485.54426 613.56511 486.60975 590.44922 487.54688 C 567.33333 488.48401 544.20875 489.29449 526.51562 490.21484 C 517.66908 490.67502 510.18492 491.16063 504.69531 491.71094 C 501.95051 491.98609 499.71079 492.27388 497.98633 492.60156 C 497.1241 492.7654 496.39476 492.93577 495.73828 493.15234 C 495.0818 493.36892 494.49112 493.47757 493.70508 494.33789 C 492.91903 495.19821 492.87283 495.7418 492.68555 496.42383 C 492.49826 497.10586 492.34668 497.8838 492.20117 498.8125 C 491.91016 500.6699 491.65494 503.11103 491.41797 506.10742 C 490.94403 512.10021 490.5555 520.2904 490.24414 529.97461 C 489.62142 549.34304 489.3153 574.67076 489.33008 600.00195 C 489.34488 625.33315 489.68008 650.65926 490.33984 670.0293 C 490.66973 679.71432 491.07863 687.90447 491.57617 693.90039 C 491.82494 696.89835 492.09471 699.34285 492.40039 701.20508 C 492.55323 702.13619 492.71049 702.91802 492.9082 703.60547 C 493.10592 704.29292 493.16934 704.85735 493.98047 705.70508 C 494.7916 706.55281 495.38004 706.64725 496.05469 706.85938 C 496.72933 707.07151 497.48676 707.23871 498.38477 707.40039 C 500.18079 707.72376 502.52368 708.0079 505.39648 708.27344 C 511.1421 708.80451 518.98069 709.25296 528.25 709.63867 C 546.78862 710.41009 571.02543 710.92344 595.26953 711.26562 C 643.75772 711.95002 692.26377 711.94922 695 711.94922 C 697.73623 711.94922 701.23881 711.94857 704.12109 711.88867 C 705.56224 711.85867 706.84322 711.81667 707.84375 711.74414 C 708.34401 711.70784 708.76365 711.66804 709.17773 711.59766 C 709.38478 711.56246 709.58337 711.52716 709.87695 711.42773 C 710.17054 711.32823 710.76363 711.46683 711.44922 710.0957 C 711.79201 709.41012 711.68693 709.38961 711.70703 709.25586 C 711.72713 709.12211 711.73689 709.02291 711.74609 708.91602 C 711.76439 708.70224 711.77716 708.47252 711.78906 708.19727 C 711.81296 707.64676 711.8308 706.92816 711.8457 706.08203 C 711.8756 704.38978 711.89062 702.19639 711.89062 700 C 711.89062 697.80361 711.8756 695.61022 711.8457 693.91797 C 711.8307 693.07184 711.81286 692.35324 711.78906 691.80273 C 711.77716 691.52748 711.76449 691.29776 711.74609 691.08398 C 711.73709 690.97709 711.72713 690.87789 711.70703 690.74414 C 711.68693 690.61039 711.79203 690.58988 711.44922 689.9043 C 710.76363 688.53312 710.17216 688.67177 709.87891 688.57227 C 709.58566 688.47277 709.38622 688.4376 709.17969 688.40234 C 708.76662 688.33184 708.34816 688.29217 707.85156 688.25586 C 706.85836 688.18326 705.59342 688.14131 704.18164 688.11133 C 701.35809 688.05133 697.97266 688.05078 695.47266 688.05078 C 692.97266 688.05078 648.86011 688.05004 604.78711 687.48438 C 582.75061 687.20155 560.72082 686.77773 543.93555 686.14258 C 535.54291 685.825 528.4557 685.45456 523.37305 685.02734 C 520.83172 684.81373 518.78589 684.58325 517.38281 684.35352 C 516.80264 684.25852 516.41806 684.16713 516.0957 684.08594 C 516.0166 683.79442 515.92951 683.46982 515.83594 682.95117 C 515.59858 681.63565 515.35996 679.69754 515.14062 677.28906 C 514.70195 672.47212 514.33011 665.74411 514.03516 657.77539 C 513.44524 641.83795 513.15517 620.91754 513.16992 600.00195 C 513.18462 579.08637 513.50423 558.16597 514.13086 542.23047 C 514.44418 534.26272 514.83588 527.53585 515.29688 522.72266 C 515.52738 520.31606 515.77918 518.38022 516.02734 517.07031 C 516.12614 516.54876 516.21652 516.22135 516.29883 515.93359 C 516.64818 515.84709 517.09805 515.74801 517.74219 515.64648 C 519.2273 515.41242 521.38019 515.17727 524.05273 514.95312 C 529.39782 514.50483 536.84208 514.09289 545.65625 513.69336 C 563.2846 512.89431 586.41242 512.14117 609.55078 511.20312 C 632.68914 510.26508 655.83219 509.14256 673.57227 507.5918 C 682.4423 506.81642 689.95207 505.93978 695.51367 504.90039 C 698.29447 504.38069 700.58183 503.82794 702.39648 503.17188 C 704.21114 502.51581 705.76437 502.02499 706.70508 500.05469 C 707.17543 499.06954 707.22038 498.40362 707.37891 497.29883 C 707.53743 496.19404 707.68118 494.80131 707.82031 493.10156 C 708.09858 489.70206 708.35125 485.08692 708.58594 479.40234 C 709.05532 468.0332 709.44491 452.39453 709.75391 433.89258 C 710.37191 396.88867 710.67017 348.44444 710.64062 299.99805 C 710.61113 251.55165 710.2544 203.10716 709.5625 166.10156 C 709.21655 147.59876 708.78731 131.95844 708.27148 120.58594 C 708.01357 114.89969 707.73499 110.28586 707.42969 106.88086 C 707.27704 105.17836 707.11805 103.78011 706.94336 102.66797 C 706.76867 101.55582 706.72082 100.87462 706.19336 99.865234 C 705.13844 97.846472 703.53369 97.44936 701.58008 96.806641 C 699.62646 96.163921 697.13955 95.619543 694.10742 95.111328 C 688.04317 94.094899 679.83014 93.253107 670.125 92.552734 C 650.71471 91.151988 625.37517 90.326834 600.0332 89.984375 C 574.69124 89.641916 549.35769 89.783367 529.98438 90.324219 C 520.29773 90.594645 512.10787 90.964009 506.11328 91.427734 C 503.11599 91.659597 500.67262 91.913357 498.81445 92.203125 C 497.88537 92.348009 497.10619 92.498373 496.42383 92.685547 C 495.74146 92.87272 495.19821 92.919034 494.33789 93.705078 C 493.47757 94.491122 493.36883 95.08145 493.15234 95.738281 C 492.93586 96.395112 492.76482 97.125644 492.60156 97.988281 C 492.27505 99.713557 491.99099 101.95543 491.7207 104.70117 C 491.18012 110.19266 490.71217 117.67628 490.29102 126.52539 C 489.44871 144.2236 488.79498 167.3553 488.16992 190.48242 C 487.54486 213.60955 486.94936 236.73221 486.22852 254.35547 C 485.86809 263.1671 485.4756 270.60796 485.03711 275.95117 C 484.81786 278.62278 484.58436 280.77546 484.35156 282.25977 C 484.25061 282.90342 484.15266 283.35213 484.06641 283.70117 C 483.77867 283.78347 483.45122 283.87387 482.92969 283.97266 C 481.61982 284.22076 479.68389 284.47287 477.27734 284.70312 C 472.46425 285.16366 465.73715 285.55376 457.76953 285.86523 C 441.8343 286.48818 420.91499 286.80078 400 286.80078 C 379.08501 286.80078 358.1657 286.48818 342.23047 285.86523 C 334.26285 285.55376 327.53575 285.16366 322.72266 284.70312 C 320.31611 284.47287 318.38018 284.22076 317.07031 283.97266 C 316.54878 283.87386 316.22133 283.78347 315.93359 283.70117 C 315.84729 283.35213 315.74939 282.90342 315.64844 282.25977 C 315.41564 280.77546 315.18214 278.62278 314.96289 275.95117 C 314.5244 270.60796 314.13191 263.1671 313.77148 254.35547 C 313.05064 236.73221 312.45514 213.60955 311.83008 190.48242 C 311.20502 167.3553 310.55129 144.2236 309.70898 126.52539 C 309.28783 117.67628 308.81988 110.19266 308.2793 104.70117 C 308.00901 101.95543 307.72495 99.713557 307.39844 97.988281 C 307.23518 97.125644 307.06414 96.395112 306.84766 95.738281 C 306.63117 95.08145 306.52243 94.491122 305.66211 93.705078 C 304.80179 92.919034 304.25854 92.87272 303.57617 92.685547 C 302.89381 92.498373 302.11463 92.348009 301.18555 92.203125 C 299.32738 91.913357 296.88401 91.659597 293.88672 91.427734 C 287.89213 90.964009 279.70229 90.594645 270.01562 90.324219 C 260.32898 90.053793 249.15122 89.884343 237.22852 89.824219 z M 95.96875 147.48047 C 92.795425 212.48563 92.806701 378.38905 95.611328 448.07031 C 95.498822 442.87459 95.343065 439.61492 95.246094 433.80859 C 94.628861 396.85074 94.329853 348.42582 94.359375 300.00195 C 94.388895 251.57808 94.746523 203.15147 95.4375 166.19531 C 95.579972 158.57537 95.797801 154.02401 95.96875 147.48047 z M 704.03125 147.48047 C 704.2022 154.02401 704.42003 158.57537 704.5625 166.19531 C 705.25348 203.15147 705.6111 251.57808 705.64062 300.00195 C 705.67012 348.42582 705.37114 396.85074 704.75391 433.80859 C 704.65693 439.61492 704.50118 442.87459 704.38867 448.07031 C 707.1933 378.38905 707.20458 212.48563 704.03125 147.48047 z M 323.76367 304.44922 C 360.24537 307.13718 439.75463 307.13718 476.23633 304.44922 C 473.87002 304.54468 472.46282 304.6796 469.86328 304.76367 C 450.58223 305.38726 425.28955 305.69922 400 305.69922 C 374.71045 305.69922 349.41777 305.38726 330.13672 304.76367 C 327.53718 304.6796 326.12998 304.54468 323.76367 304.44922 z M 304.41797 523.19141 C 304.52261 525.74502 304.6687 527.30239 304.75977 530.13477 C 305.37967 549.41559 305.68467 574.70898 305.66992 599.99805 C 305.65522 625.28712 305.32074 650.58016 304.66406 669.85938 C 304.55103 673.17803 304.37449 675.06296 304.24219 677.99414 C 307.16881 642.74268 307.16449 559.65308 304.41797 523.19141 z M 495.58203 523.19141 C 492.83551 559.65308 492.83119 642.74268 495.75781 677.99414 C 495.62551 675.06296 495.44897 673.17803 495.33594 669.85938 C 494.67926 650.58016 494.34483 625.28712 494.33008 599.99805 C 494.31538 574.70898 494.62033 549.41559 495.24023 530.13477 C 495.3313 527.30239 495.47739 525.74502 495.58203 523.19141 z " /> + </g> +</svg> diff --git a/assets/static/images/mastodon.svg b/assets/static/images/mastodon.svg new file mode 100644 index 0000000..f6b8203 --- /dev/null +++ b/assets/static/images/mastodon.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"> + <defs> + <clipPath id="a" clipPathUnits="userSpaceOnUse"> + <path fill="#fff" d="M-5-5h650v650H-5Zm324.4 45q-57.1.6-102 6.8-49.7 6.7-78.6 20.2l-8.6 5q-9.8 6-19.6 15.3-13.6 12.9-23.4 28.9-12.9 20.2-19.6 44.8-8.6 28.9-8.6 63.3V247q0 47.3.6 73 1.8 69.5 11 115.6 11.7 58.3 38.1 94.5 30.7 41.2 82.3 54.7 67 17.8 129 14.7 28.3-1.8 54-7.3 18.5-4.3 33.8-10.5l11.7-4.9-2.5-46-13.5 3.6q-17.2 3.7-35 6.2-25.2 3.7-47.9 2.4-32.5-.6-49.1-4.3-26.4-4.9-41.2-18.4-17.8-15.3-21.5-44.2-1.2-8.6-1.2-17.8L223 462q19 3.7 39.9 6.1 29.5 4.3 57.1 5.5 25.8 1.3 56.5-1.2 19.7-1.2 54-5.5 34.5-4.3 65.8-18.4 30-14.2 50-33.5t23.7-39q5.5-27.7 8-67 2.4-28.2 3-59.6v-25.1q0-34.4-8.6-63.3-6.7-24.6-19.6-44.8-9.8-16-23.4-28.9-9.2-9.2-19.6-15.4-7.4-4.9-8.6-4.9-28.3-13.5-78-20.2-45.5-6.2-102-6.8zm-81 95.2q-38.8 0-62.1 26.4t-23.4 71.9v146.8h58.4V237.8q0-45.5 37.5-45.5 42.3 0 42.3 54v78H349v-78q0-54 42.3-54 37.5 0 37.5 45.5v142.5h58.4V233.5q0-45.5-23.4-71.9t-62-26.4q-44.8 0-67 34.4l-14.7 24-14.7-24q-22.2-34.4-67-34.4" class="powerclip"/> + </clipPath> + </defs> + <path d="M91.4 0C41 0 0 41 0 91.4v457.2C0 599 41 640 91.4 640h457.2c50.4 0 91.4-41 91.4-91.4V91.4C640 41 599 0 548.6 0Z" clip-path="url(#a)"/> +</svg> diff --git a/assets/static/images/opengraph.png b/assets/static/images/opengraph.png Binary files differnew file mode 100644 index 0000000..c44994f --- /dev/null +++ b/assets/static/images/opengraph.png diff --git a/assets/static/images/rss.svg b/assets/static/images/rss.svg new file mode 100644 index 0000000..0a6bed9 --- /dev/null +++ b/assets/static/images/rss.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"> +<!--!Font Awesome Free 7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--> + <path d="M91.4 0C41 0 0 41 0 91.4v457.2C0 599 41 640 91.4 640h457.2c50.4 0 91.4-41 91.4-91.4V91.4C640 41 599 0 548.6 0Zm45.7 148.6c0-19 15.3-34.3 34.3-34.3a354 354 0 0 1 354.3 354.3 34.2 34.2 0 1 1-68.6 0 285.6 285.6 0 0 0-285.7-285.7c-19 0-34.3-15.3-34.3-34.3m0 137.1c0-19 15.3-34.3 34.3-34.3a217 217 0 0 1 217.2 217.2 34.2 34.2 0 1 1-68.6 0c0-82-66.6-148.6-148.6-148.6-19 0-34.3-15.3-34.3-34.3m0 171.4a45.7 45.7 0 1 1 91.4 0 45.7 45.7 0 0 1-91.4 0"/> +</svg> diff --git a/assets/static/images/xmpp.svg b/assets/static/images/xmpp.svg new file mode 100644 index 0000000..9d33519 --- /dev/null +++ b/assets/static/images/xmpp.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"> + <defs> + <clipPath id="a" clipPathUnits="userSpaceOnUse"> + <path fill="#fff" d="M-5-5h650v650H-5Zm428.5 526.2c36.8 7.8 54.7 3.4 24.7-6.2a288 288 0 0 1-83-43.6l-8.4-6.4 26.8-17.7c92.7-61.1 166.8-150.5 199-240.3 8-22 17.4-66 17.4-81v-10.4L557.5 132c-23.4 9-61.6 22-85 29L430 173.2v20.2c0 69.5-40.4 165.3-95.4 226.1l-11.8 13-12-13c-27.3-30-58.4-83.7-74-127.7a281 281 0 0 1-18.1-86.6l-1.8-28.6-44.5-13.7c-39-12-65.2-21.5-118.5-42.6L40 114.8l1.6 15.1c9.6 87.8 54 171 132.6 248.5a489 489 0 0 0 86.7 70.7l25.7 16.9-12.2 8.6a345 345 0 0 1-71.3 36.3c-22.2 7.5-25.4 13.5-5.8 10.9a513 513 0 0 0 97.6-27.4l25.7-10.5 19.7 9a462 462 0 0 0 83.2 28.3" class="powerclip"/> + </clipPath> + </defs> + <path d="M91.4 0C41 0 0 41 0 91.4v457.2C0 599 41 640 91.4 640h457.2c50.4 0 91.4-41 91.4-91.4V91.4C640 41 599 0 548.6 0Z" clip-path="url(#a)"/> +</svg> diff --git a/configs/atom.ini b/configs/atom.ini new file mode 100644 index 0000000..84b78ec --- /dev/null +++ b/configs/atom.ini @@ -0,0 +1,18 @@ +[latest] +name = Still Thinking About It — Latest posts +source_path = /blog +url_path = /blog/latest.xml +item_model = blog-post +item_date_field = publication_date +blog_author_field = author +item_author_field = author + +[all] +name = Still Thinking About It — All posts +source_path = /blog +url_path = /blog/all.xml +item_model = blog-post +item_date_field = publication_date +blog_author_field = author +item_author_field = author +limit = 0 diff --git a/configs/index-pages.ini b/configs/index-pages.ini new file mode 100644 index 0000000..bc428ce --- /dev/null +++ b/configs/index-pages.ini @@ -0,0 +1,23 @@ +[year] +parent_path = /blog +key = '{.year:04d}'.format(item.publication_date) +template = blog-year-index.html +subindex = month + +[year.fields] +date = this.children.first().publication_date.replace(month=1, day=1) +year = this.key|int +previous_year_page = site.get('/blog@index-pages/year/{:04d}'.format((this.key|int) - 1)) +next_year_page = site.get('/blog@index-pages/year/{:04d}'.format((this.key|int) + 1)) +first_year_page = index_pages('year')|sort(attribute='year')|first +last_year_page = index_pages('year')|sort(attribute='year')|last + +[year.month] +key = '{.month:02d}'.format(item.publication_date) +template = blog-month-index.html + +[year.month.fields] +# this.parent is the year-index page this month-index belongs to. +date = this.parent.date.replace(month=this.key|int) +year = this.parent.year +month = this.key|int diff --git a/configs/tags.ini b/configs/tags.ini new file mode 100644 index 0000000..3ecc49a --- /dev/null +++ b/configs/tags.ini @@ -0,0 +1,4 @@ +parent = /blog +items = this.parent.children.filter(F.tags.contains(tag)).order_by('-publication_date') +template = tag.html +url_path = {{ this.parent.url_path }}tag/{{ tag|trim|lower|replace(" ", "-") }} diff --git a/content/about/contents.lr b/content/about/contents.lr new file mode 100644 index 0000000..9a65a65 --- /dev/null +++ b/content/about/contents.lr @@ -0,0 +1,29 @@ +_model: page +--- +_template: about.html +--- +title: About +--- +body: + +<p class="center"> +Hi, I'm Dennis. Welcome! +</p> + + + +I am originally from Luxembourg and grew up in the south of the country. During +my formative years, I spent a lot of time in a local scouts group, which was a +fantastic way to develop a deep connection to nature and a desire to learn +through exploration and experimentation. I later got involved in the local +hacker and maker movement, where the development and exploration of technology +often went hand in hand with discussions around privacy, security and the role +of technology in society. I'm a trained educator and now I get to do what I +love most: sharing my passion for science with others through education and +communication. + +This blog is my place to capture my thoughts before they disappear. It'll most +likely include little things from my day-to-day life, things I've been reading +about and recurring ideas, as well as the occasional rabbit hole I've fallen +down. There won't be a theme or a schedule, but it will be a space where I can +collect all the things that I find interesting and share them with you. diff --git a/content/about/me.jpg b/content/about/me.jpg Binary files differnew file mode 100644 index 0000000..300ffd5 --- /dev/null +++ b/content/about/me.jpg diff --git a/content/blog/back-to-blogging/contents.lr b/content/blog/back-to-blogging/contents.lr new file mode 100644 index 0000000..a68236a --- /dev/null +++ b/content/blog/back-to-blogging/contents.lr @@ -0,0 +1,43 @@ +title: Back to Blogging +--- +publication_date: 2026-06-17 +--- +render_toc: no +--- +body: + +Welcome back to my blog! Yes, it's time to get back to basics again. But why +now? There are several reasons for this, which I may discuss in more detail in +future posts. For now, let me recap on two things. + +Firstly, the recent rise of the ["Digital Independence Day"](https://di.day/en) +events in Germany, has inspired me to become even more digitally independent. +Since joining the [local Chaos Computer Club](https://c3l.lu) over 15 years +ago, I have been on a mission to leave big tech behind. This has led me to +self-host most of my digital tools and start blogging again. + +The main reason, though, is that the web is fundamentally about sharing +information, not just consuming it. At a time when content generated by LLMs +seems to dominate the web, it's more important than ever to have content +written by humans. This is my attempt to contribute to this. I may blog about +my views on AI in the future. + +What can you find here? As I write on the ["About" page](/about/): + +> This blog is my place to capture my thoughts before they disappear. It'll most +> likely include little things from my day-to-day life, things I've been reading +> about and recurring ideas, as well as the occasional rabbit hole I've fallen +> down. There won't be a theme or a schedule, but it will be a space where I can +> collect all the things that I find interesting and share them with you. + +Thank you for reading my blog, and I hope to see you again soon! You might want to subscribe to the [Atom feed](/blog/latest.xml)! +--- +tags: + +blogging +personal web +digital independence +human-written web +atom feed +--- +category: site-notes diff --git a/content/blog/contents.lr b/content/blog/contents.lr new file mode 100644 index 0000000..8aa3c00 --- /dev/null +++ b/content/blog/contents.lr @@ -0,0 +1,3 @@ +_model: blog +--- +author: Dennis Fink diff --git a/content/categories/contents.lr b/content/categories/contents.lr new file mode 100644 index 0000000..01afcc8 --- /dev/null +++ b/content/categories/contents.lr @@ -0,0 +1 @@ +_model: categories diff --git a/content/categories/site-notes/contents.lr b/content/categories/site-notes/contents.lr new file mode 100644 index 0000000..75971e1 --- /dev/null +++ b/content/categories/site-notes/contents.lr @@ -0,0 +1 @@ +name: Site Notes diff --git a/content/contents.lr b/content/contents.lr new file mode 100644 index 0000000..cd8811e --- /dev/null +++ b/content/contents.lr @@ -0,0 +1,3 @@ +_model: index +--- +title: Index diff --git a/databags/blog.ini b/databags/blog.ini new file mode 100644 index 0000000..fdf0986 --- /dev/null +++ b/databags/blog.ini @@ -0,0 +1,2 @@ +name = Still Thinking About It +tagline = Personal notes on things that linger. diff --git a/justfile b/justfile new file mode 100644 index 0000000..e40389a --- /dev/null +++ b/justfile @@ -0,0 +1,14 @@ +set shell := ["bash", "-uc"] + +serve: + lektor serve -h 0.0.0.0 + +clean: + lektor clean --yes + +build: + lektor build + +deploy: build + rsync --archive --compress --verbose build/ draconis:/home/dennis/blog/ + ssh -t draconis -- /home/dennis/bin/,deploy_blog diff --git a/models/blog-post.ini b/models/blog-post.ini new file mode 100644 index 0000000..4f36c1e --- /dev/null +++ b/models/blog-post.ini @@ -0,0 +1,39 @@ +[model] +name = Blog Post +label = {{ this.title }} + +[fields.title] +label = Title +type = string +size = large + +[fields.publication_date] +label = Publication date +type = date +width = 1/2 + +[fields.updated_date] +label = Updated date +type = date +width = 1/2 + +[fields.category] +label = Category +type = select +source = site.query('/categories') +width = 1/2 + +[fields.render_toc] +label = Render TOC +type = boolean +checkbox_label = If true, then a TOC will be rendered in the webpage. +default = false +width = 1/2 + +[fields.body] +label = Body +type = markdown + +[fields.tags] +label = Tags +type = strings diff --git a/models/blog.ini b/models/blog.ini new file mode 100644 index 0000000..0d612ff --- /dev/null +++ b/models/blog.ini @@ -0,0 +1,17 @@ +[model] +name = Blog +label = Blog +hidden = yes + +[fields.author] +label = Author +type = string + +[children] +model = blog-post +order_by = -publication_date, title +slug_format = {{ (this.publication_date|dateformat('YYYY/MM/') if this.publication_date) ~ this._id }} + +[pagination] +enabled = yes +per_page = 5 diff --git a/models/categories.ini b/models/categories.ini new file mode 100644 index 0000000..2b4c222 --- /dev/null +++ b/models/categories.ini @@ -0,0 +1,9 @@ +[model] +name = Categories +label = Categories +hidden = yes +protected = yes + +[children] +model = category +order_by = name diff --git a/models/category.ini b/models/category.ini new file mode 100644 index 0000000..3a6e10d --- /dev/null +++ b/models/category.ini @@ -0,0 +1,11 @@ +[model] +name = Category +label = {{ this.name }} +hidden = yes + +[children] +replaced_with = site.query('/blog').filter(F.category.contains(this)) + +[fields.name] +label = Name +type = string diff --git a/models/index.ini b/models/index.ini new file mode 100644 index 0000000..475c4a1 --- /dev/null +++ b/models/index.ini @@ -0,0 +1,8 @@ +[model] +name = Index +label = index + +[pagination] +enabled = yes +per_page = 5 +items = site.query('/blog') diff --git a/models/page.ini b/models/page.ini new file mode 100644 index 0000000..dcddfe8 --- /dev/null +++ b/models/page.ini @@ -0,0 +1,11 @@ +[model] +name = Page +label = {{ this.title }} + +[fields.title] +label = Title +type = string + +[fields.body] +label = Body +type = markdown diff --git a/packages/blog-markdown-headings/lektor_blog_markdown_headings.py b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py new file mode 100644 index 0000000..5293676 --- /dev/null +++ b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py @@ -0,0 +1,152 @@ +import uuid +from collections import namedtuple +from html.parser import HTMLParser + +import mistune +from lektor.pluginsystem import Plugin +from lektor.utils import slugify +from markupsafe import Markup, escape + +TocEntry = namedtuple("TocEntry", ["anchor", "title", "children"]) + + +def get_int_config(config, key, default, minimum=None, maximum=None): + value = config.get(key, str(default)) + + try: + value = int(value) + except (TypeError, ValueError): + value = default + + if minimum is not None: + value = max(value, minimum) + + if maximum is not None: + value = min(value, maximum) + + return value + + +class BlogMarkdownHeadingsPlugin(Plugin): + name = "Blog Markdown Headings" + description = ( + "Adds Markdown heading anchors, TOC metadata, and configurable heading offsets." + ) + + def on_markdown_config(self, config, **extra): + plugin_config = self.get_config() + + anchor_type = plugin_config.get("anchors.type", "slug") + + heading_offset = get_int_config( + plugin_config, + "headings.offset", + default=2, + minimum=-5, + maximum=5, + ) + + max_heading_level = get_int_config( + plugin_config, + "headings.max_level", + default=6, + minimum=1, + maximum=6, + ) + + min_heading_level = get_int_config( + plugin_config, + "headings.min_level", + default=1, + minimum=1, + maximum=6, + ) + + def adjusted_level(level): + return min( + max(level + heading_offset, min_heading_level), + max_heading_level, + ) + + config.renderer_mixins.append( + renderer_mixin_factory( + anchor_type=anchor_type, + adjusted_level=adjusted_level, + ) + ) + + def on_markdown_meta_init(self, meta, **extra): + meta["toc"] = [] + + def on_markdown_meta_postprocess(self, meta, **extra): + prev_level = None + toc = [] + stack = [toc] + + for level, anchor, title in meta["toc"]: + if prev_level is None: + prev_level = level + elif prev_level == level - 1: + stack.append(stack[-1][-1].children) + prev_level = level + elif prev_level > level: + while prev_level > level: + if len(stack) > 1: + stack.pop() + prev_level -= 1 + + stack[-1].append(TocEntry(anchor, title, [])) + + meta["toc"] = toc + + +def renderer_mixin_factory(anchor_type, adjusted_level): + def render_heading(text, original_level, meta, raw=None): + level = adjusted_level(original_level) + + if anchor_type == "random": + anchor = uuid.uuid4().hex[:6] + else: + if raw is None: + raw = escape(_strip_tags(text)) + anchor = slugify(raw) + + meta["toc"].append((level, anchor, Markup(text))) + + return '<h%d id="%s">%s</h%d>' % (level, anchor, text, level) + + class RendererMixinMistune0(object): + def header(self, text, level, raw): + return render_heading(text, level, self.meta, raw) + + class RendererMixinMistune2(object): + def heading(self, text, level): + meta = self.lektor.meta + return render_heading(text, level, meta) + + if mistune.__version__.startswith("0."): + return RendererMixinMistune0 + + if mistune.__version__.startswith("2."): + return RendererMixinMistune2 + + raise RuntimeError("Unsupported mistune version: %s" % mistune.__version__) + + +def _strip_tags(inline_html): + parser = _StripTagsParser() + parser.feed(inline_html) + return parser.close() + + +class _StripTagsParser(HTMLParser): + def reset(self): + super().reset() + self._data = [] + + def handle_data(self, data): + self._data.append(data) + + def close(self): + super().close() + return "".join(self._data) diff --git a/packages/blog-markdown-headings/setup.py b/packages/blog-markdown-headings/setup.py new file mode 100644 index 0000000..59f9895 --- /dev/null +++ b/packages/blog-markdown-headings/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup + +setup( + name="lektor-blog-markdown-headings", + version="0.1", + py_modules=["lektor_blog_markdown_headings"], + entry_points={ + "lektor.plugins": [ + "blog-markdown-headings = lektor_blog_markdown_headings:BlogMarkdownHeadingsPlugin", + ] + }, + install_requires=[], +) diff --git a/templates/.djlintrc b/templates/.djlintrc new file mode 100644 index 0000000..5882faa --- /dev/null +++ b/templates/.djlintrc @@ -0,0 +1,14 @@ +{ +"blank_line_before_tag": "macro", +"blank_line_after_tag": "endmacro,extends,import,from", +"profile": "jinja", +"format_css": true, +"css": { +"indent_size": 2 +}, +"format_js": true, +"js": { +"indent_size": 2 +}, +"indent": "2" +} diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..55a0778 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="profile"> + <meta property="profile:first_name" content="Dennis"> + <meta property="profile:last_name" content="Fink"> +{% endblock %} +{% block content %} + <article>{{ this.body }}</article> +{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..1000014 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html lang="en" + prefix="og: https://ogp.me/ns# article: https://ogp.me/ns/article# profile: https://ogp.me/ns/profile#"> + <head> + {% block head %} + {% block meta %} + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="color-scheme" content="dark light"> + <meta name="generator" content="lektor"> + <meta name="description" + property="og:description" + content="{{ bag('blog.tagline') }}"> + <meta property="og:site_name" content="dennisfink.me"> + <meta property="og:title" + content="{%- if (this.title or title) -%}{{ (this.title or title) | safe }} — {% endif -%}{{ bag('blog.name') }}"> + <meta property="og:url" + content="{{ this | url(absolute=True, external=True) }}"> + <meta property="og:image" + content="{{ '/static/images/opengraph.png' | url(absolute=True, external=True) }}"> + <meta property="og:image:type" content="image/png"> + <meta property="og:image:width" content="32"> + <meta property="og:image:height" content="32"> + <meta property="og:image:alt" + content="Logo of the site showing a second-order Hilbert curve segment drawn as a continuous right-angled line inside a rounded square, with both colored in muted green"> + <meta name="fediverse:creator" content="@me@dennisfink.me"> + {% endblock meta %} + {% block styles %} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/modern_normalize.css' | asseturl }}"> + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/libre_baskerville.css' | asseturl }}"> + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/style.css' | asseturl }}"> + {% endblock styles %} + {% block meta_links %} + <link rel="canonical" href="{{ this|url(absolute=True, external=True) }}"> + <link rel="alternate" + type="application/atom+xml" + title="{{ bag('blog.name') }} — Latest posts" + href="{{ '/blog@atom/latest'|url(absolute=True) }}"> + <link rel="alternate" + type="application/atom+xml" + title="{{ bag('blog.name') }} — All posts" + href="{{ '/blog@atom/all'|url(absolute=True) }}"> + <link rel="shortcut icon" + type="image/png" + href="{{ '/static/images/favicon.png' | asseturl }}"> + {% endblock meta_links %} + <title> + {% block meta_title %} + {%- if (this.title or title) -%}{{ (this.title or title) | safe }} — + {% endif -%} + {{ bag("blog.name") }} — dennisfink.me + {% endblock meta_title %} + </title> + {% endblock head %} + </head> + <body> + {% block body %} + <a href="#main" class="skip-link">Skip to main content</a> + <header> + {% block site_header %} + <div> + <hgroup> + <h1> + <a href="/">{{ bag("blog.name") }}</a> + </h1> + <p>{{ bag("blog.tagline") }}</p> + </hgroup> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800"> + <path d="M89.4 0C40.1 0 0 40 0 89.4v621.2C0 759.9 40 800 89.4 800h621.2c49.3 0 89.4-40 89.4-89.4V89.4C800 40.1 760 0 710.6 0zm0 18.9h621.2A70 70 0 0 1 781 89.4v621.2a70 70 0 0 1-70.5 70.5H89.4A70 70 0 0 1 19 710.6V89.4A70 70 0 0 1 89.4 19m147.8 71c-11.9-.1-24.6 0-37.2 0-25.4.4-50.7 1.3-70.1 2.7-9.7.7-18 1.5-24 2.5q-4.5.7-7.5 1.7c-2 .6-3.5 1-4.6 3q-.6 1.4-.7 2.9-.3 1.6-.5 4.2l-.9 13.7c-.5 11.4-1 27-1.3 45.5a8909 8909 0 0 0-.2 267.8 1985 1985 0 0 0 2 59.2l.4 4.2q.1 1.5.7 2.8c1 2 2.5 2.4 4.3 3q2.7 1 6.9 1.8 8.4 1.6 22 2.7a1491 1491 0 0 0 64 3.6c23 1 46.2 1.7 63.8 2.5a614 614 0 0 1 28 2l1.4.2.3 1.2.7 5.6c.5 4.8.9 11.6 1.2 19.5a1632 1632 0 0 1 .1 115.5 390 390 0 0 1-1.8 25.3l-.3 1q-.4.3-1.3.4l-6 .6q-7.7.7-20.5 1.1c-16.8.7-38.9 1.1-60.9 1.4a7900 7900 0 0 1-103 .8l-2 .3c-.4 0-1 0-1.6 1.3q-.5.9-.3.8v1.1L88 700l.2 9.3.3.8c.6 1.4 1.2 1.2 1.5 1.3l.7.2 1.4.1 3.7.2h9.1c2.7 0 51.2 0 99.7-.6 24.3-.4 48.5-.9 67-1.7a485 485 0 0 0 30-2.2l2.2-.5c.7-.3 1.3-.3 2.1-1.2q1-1.1 1-2 .4-1.2.6-2.5.5-2.8.8-7.3.8-9.2 1.3-23.9a2277 2277 0 0 0 .1-140 547 547 0 0 0-2-31.2l-.5-2.4c-.2-.7-.2-1.2-1-2-.8-1-1.4-1-2-1.2q-1-.4-2.3-.6l-6.7-.9q-8.4-.7-21.8-1.5c-17.7-1-40.8-1.7-64-2.7-23-.9-46.2-2-63.8-3.4-8.7-.7-16.2-1.5-21.4-2.4q-4-.7-6.2-1.4-1.2-.4-1.5-.7l-.1-.8-.4-3.5q-.5-4.6-.8-12.3c-.4-8-.6-20.1-1-32.3a4578 4578 0 0 1 1.4-306l.5-4.4 1.3-.6q1.8-.8 5.4-1.4 7.4-1.3 19.4-2.3a1269 1269 0 0 1 115.5-1.8 355 355 0 0 1 25.1 1.8l1.2.3.3 1.4.6 6.3q.8 8.2 1.2 21.6c.7 17.7 1.3 40.8 2 64 .6 23 1.2 46.2 2 63.9a465 465 0 0 0 2.4 28.5q.2 1.3.6 2.3.1.9 1.1 2c1 1.1 1.4.8 2.1 1l2.4.5 7.3.8q9.2.7 23.9 1.2a2305 2305 0 0 0 163.9-1.2 101 101 0 0 0 9.7-1.3c.7-.2 1.2-.2 2-1 1-.8 1-1.4 1.2-2q.4-1 .6-2.3l.9-6.7q.8-8.4 1.4-21.8c.9-17.7 1.5-40.9 2.1-64s1.3-46.2 2-63.9a571 571 0 0 1 1.8-27.9l.3-1.4 1.2-.3 5.6-.7a355 355 0 0 1 19.5-1 1269 1269 0 0 1 115.5 1.7q12.1 1 19.4 2.3 3.5.7 5.4 1.4l1.3.6.2.9.3 3.5q.4 4.5.7 12.3a4578 4578 0 0 1-1 338.3l-.6 4.3q-.3.3-1.5.7-2.1.8-6.2 1.4c-5.2 1-12.7 1.7-21.4 2.4-17.6 1.4-40.7 2.5-63.9 3.4-23 1-46.2 1.8-63.9 2.7a493 493 0 0 0-28.5 2.4q-1.3.2-2.3.6c-.6.2-1.2.3-2 1.1s-.8 1.4-1 2.1l-.5 2.4-.8 7.3q-.7 9.2-1.2 23.9a2277 2277 0 0 0 1.4 163.9 97 97 0 0 0 1.3 9.7q.1 1 1 2.1c.9.9 1.5 1 2.2 1.2l2.3.5 7 .9q8.8.8 22.9 1.3c18.5.8 42.7 1.3 67 1.7 48.5.7 97 .6 99.7.6l14.2-.3.7-.2c.3 0 .9 0 1.5-1.3q.5-.9.3-.8v-1.1l.2-8.2-.2-9.3-.3-.8c-.6-1.4-1.2-1.2-1.5-1.3l-.7-.2-1.3-.1-3.7-.2h-8.7c-2.5 0-46.6 0-90.7-.6-22-.3-44-.7-60.9-1.4a483 483 0 0 1-26.5-1.7l-1.3-.3-.3-1.1-.7-5.7a390 390 0 0 1-1-19.5 1632 1632 0 0 1 1.2-135l1-6.9 1.4-.3 6.4-.6 21.6-1.3c17.6-.8 40.7-1.6 63.9-2.5 23-1 46.2-2 64-3.6q13.4-1.2 22-2.7 4-.7 6.8-1.7c1.8-.7 3.4-1.2 4.3-3.1q.6-1.3.7-2.8l.4-4.2q.5-5.1.8-13.7c.5-11.4.8-27 1.2-45.5a8229 8229 0 0 0-1.5-313.3 331 331 0 0 0-1.4-18q0-1.3-.7-2.7c-1-2-2.7-2.5-4.6-3q-3-1-7.5-1.8c-6-1-14.3-1.8-24-2.5a1786 1786 0 0 0-140-2.2 517 517 0 0 0-31.2 1.8l-2.4.5c-.7.2-1.2.2-2 1-1 .8-1 1.4-1.2 2q-.4 1-.6 2.3l-.9 6.7q-.8 8.4-1.4 21.8c-.9 17.7-1.5 40.9-2.1 64s-1.3 46.2-2 63.9a571 571 0 0 1-1.8 27.9l-.3 1.4-1.2.3-5.6.7c-4.8.5-11.6.9-19.5 1.2a1575 1575 0 0 1-135-1.2l-6.9-1-.3-1.4-.6-6.3q-.7-8.2-1.2-21.6c-.7-17.7-1.3-40.8-2-64-.6-23-1.2-46.2-2-63.9a465 465 0 0 0-2.4-28.5q-.2-1.3-.6-2.3-.1-.8-1.1-2c-1-1.2-1.4-.8-2.1-1l-2.4-.5-7.3-.8q-9.2-.7-23.9-1t-32.8-.6M96 147.4c-3.2 65-3.2 230.9-.4 300.6l-.4-14.3a8224 8224 0 0 1 .2-267.6zm608 0 .6 18.7a8887 8887 0 0 1 .2 267.6l-.4 14.3a5034 5034 0 0 0-.4-300.6m-380.2 157c36.4 2.6 116 2.6 152.4 0-2.3 0-3.7.2-6.3.3a2302 2302 0 0 1-139.8 0zm-19.4 218.7.4 7a2273 2273 0 0 1-.1 139.7c-.1 3.3-.3 5.2-.5 8 3-35.2 3-118.2.2-154.7m191.2 0c-2.8 36.5-2.8 119.5.2 154.8-.2-3-.4-4.8-.5-8.1a2274 2274 0 0 1-.1-139.7z" /> + </svg> + </div> + <div> + <nav aria-label="Site navigation"> + {% block site_navigation %} + <ul> + {% block site_navigation_items %} + <li> + <a href="{{ '/blog' | url(absolute=True) }}" + {% if "/blog@" in this.path %}aria-current="page"{% endif %}>Blog</a> + </li> + <li> + <a href="{{ '/categories' | url(absolute=True) }}" + {% if this.path == "/categories" %}aria-current="page"{% endif %}>Categories</a> + </li> + <li> + <a href="{{ '/about' | url(absolute=True) }}" + {% if this.path == "/about" %}aria-current="page"{% endif %}>About</a> + </li> + {% endblock site_navigation_items %} + </ul> + {% endblock site_navigation %} + </nav> + <fieldset class="theme-switcher"> + <legend class="visually-hidden">Color theme</legend> + <label title="Light mode"> + <input type="radio" + name="theme-switcher" + value="light" + id="lightmode" + aria-label="Light Mode"> + <span aria-hidden="true"></span> + </label> + <label title="Auto mode"> + <input type="radio" + name="theme-switcher" + value="light dark" + id="automode" + aria-label="Auto Mode" + checked> + <span aria-hidden="true"></span> + </label> + <label title="Dark mode"> + <input type="radio" + name="theme-switcher" + value="dark" + id="darkmode" + aria-label="Dark Mode"> + <span aria-hidden="true"></span> + </label> + </fieldset> + </div> + {% endblock site_header %} + </header> + <main> + {% block content required %} + {% endblock content %} + </main> + <footer> + {% block site_footer %} + <section aria-label="Follow me"> + <h2>Follow me:</h2> + <div> + <a href="{{ '/blog@atom/latest' | url(absolute=True) }}" + id="atom" + aria-label="Atom feed — Latest posts"></a> + <a rel="nofollow" + href="/email/" + id="email" + aria-label="Mail address: me plus blog at dennisfink.me"></a> + <a rel="me" + href="https://social.dennisfink.me/@me" + id="mastodon" + aria-label="Mastodon profile: me on social.dennisfink.me"></a> + <a href="xmpp:me@dennisfink.me" + id="xmpp" + aria-label="XMPP address: me on dennisfink.me"></a> + </div> + </section> + <section> + <h2>Blogroll</h2> + <ul> + <li> + <a href="https://blog.janguth.eu/">Broadsheet (Jan Guth)</a> + </li> + </ul> + </section> + {% endblock %} + </footer> + {% endblock body %} + </body> +</html> diff --git a/templates/blog-month-index.html b/templates/blog-month-index.html new file mode 100644 index 0000000..d114628 --- /dev/null +++ b/templates/blog-month-index.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% from "macros/pagination.html" import render_month_archive_navigation %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/category.css'|asseturl }}"> +{% endblock %} +{% set title = "Monthly archive: " + this.date | dateformat('MMMM y') %} +{% block content %} + {% set post_count = this.children.count() %} + <article> + <header> + <hgroup> + <h2>Monthly archive: {{ this.date | dateformat("MMMM y") }}</h2> + <p> + ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} + </p> + </hgroup> + </header> + <table> + <thead class="visually-hidden"> + <tr> + <th scope="col">Date</th> + <th scope="col">Title</th> + </tr> + </thead> + <tbody> + {% for post in this.children.order_by('-publication_date') %} + <tr> + <td> + <time datetime="{{ post.publication_date }}">{{ post.publication_date }}</time> + </td> + <td> + <a href="{{ post|url }}">{{ post.title }}</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + {{ render_month_archive_navigation(this) }} + </article> +{% endblock %} diff --git a/templates/blog-post.html b/templates/blog-post.html new file mode 100644 index 0000000..6ffb600 --- /dev/null +++ b/templates/blog-post.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_post %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="article"> + <meta property="article:author" content="https://dennisfink.me/about"> + {% if this.publication_date %} + <meta property="article:published_time" + content="{{ this.publication_date }}T00:00:00Z"> + {% endif %} + {% if this.updated_date %} + <meta property="article:modified_time" + content="{{ this.updated_date }}T00:00:0Z"> + {% endif %} + {% if this.category %} + {% set category = site.get("/categories/" + this.category) %} + <meta property="article:section" content="{{ category.name }}"> + {% endif %} + {% for t in this.tags %} + <meta property="article:tag" content="{{ t }}"> + {% endfor %} +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/blog.css'|asseturl }}"> +{% endblock %} +{% block content %}{{ render_blog_post(this) }}{% endblock %} diff --git a/templates/blog-year-index.html b/templates/blog-year-index.html new file mode 100644 index 0000000..138cc86 --- /dev/null +++ b/templates/blog-year-index.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% from "macros/pagination.html" import render_year_archive_navigation %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/category.css'|asseturl }}"> +{% endblock %} +{% set title = "Yearly archive: " + this.key %} +{% block content %} + {% set post_count = this.children.count() %} + <article> + <header> + <hgroup> + <h2>Yearly archive: {{ this.key }}</h2> + <p> + ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} + </p> + </hgroup> + </header> + <table> + <thead class="visually-hidden"> + <tr> + <th scope="col">Date</th> + <th scope="col">Title</th> + </tr> + </thead> + <tbody> + {% for post in this.children.order_by('-publication_date') %} + <tr> + <td> + <time datetime="{{ post.publication_date }}">{{ post.publication_date }}</time> + </td> + <td> + <a href="{{ post|url }}">{{ post.title }}</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + {{ render_year_archive_navigation(this) }} + </article> +{% endblock %} diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..0ee96c7 --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_index %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/blog.css'|asseturl }}"> +{% endblock %} +{% block content %}{{ render_blog_index(this.pagination) }}{% endblock %} diff --git a/templates/categories.html b/templates/categories.html new file mode 100644 index 0000000..4dbdf1a --- /dev/null +++ b/templates/categories.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/categories.css'|asseturl }}"> +{% endblock %} +{% set title = "Categories" %} +{% block content %} + <article> + <header> + <h2>Categories</h2> + </header> + <ul> + {% set sorted = [] %} + {% for category in this.children.order_by('name') %} + {% set post_count = category.children.count() %} + {% if post_count > 0 %} + {% set _ = sorted.append((post_count, category)) %} + {% endif %} + {% endfor %} + {% for post_count, category in sorted|sort(attribute='0', reverse=True) %} + {% set last_post = category.children.order_by('-publication_date').first() %} + <li> + <a href="{{ category|url }}"> + <h3>{{ category.name }}</h3> + <dl> + <div> + <dt>Posts:</dt> + <dd> + {{ post_count }} + </dd> + </div> + {% if last_post %} + <div> + <dt>Last post:</dt> + <dd> + <time datetime="{{ last_post.publication_date }}">{{ last_post.publication_date }}</time> + </dd> + </div> + {% endif %} + </dl> + </a> + </li> + {% endfor %} + </ul> + </article> +{% endblock %} diff --git a/templates/category.html b/templates/category.html new file mode 100644 index 0000000..9d6b35d --- /dev/null +++ b/templates/category.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/category.css'|asseturl }}"> +{% endblock %} +{% set title = "Category " + this.name %} +{% block content %} + {% set post_count = this.children.count() %} + <article> + <header> + <hgroup> + <h2>Category: {{ this.name }}</h2> + <p> + ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} + </p> + </hgroup> + </header> + <table> + <thead class="visually-hidden"> + <tr> + <th scope="col">Date</th> + <th scope="col">Title</th> + </tr> + </thead> + <tbody> + {% for post in this.children.order_by('-publication_date') %} + <tr> + <td> + <time datetime="{{ post.publication_date }}">{{ post.publication_date }}</time> + </td> + <td> + <a href="{{ post|url }}">{{ post.title }}</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </article> +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..0ee96c7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% from "macros/blog.html" import render_blog_index %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/blog.css'|asseturl }}"> +{% endblock %} +{% block content %}{{ render_blog_index(this.pagination) }}{% endblock %} diff --git a/templates/macros/blog.html b/templates/macros/blog.html new file mode 100644 index 0000000..b4caafe --- /dev/null +++ b/templates/macros/blog.html @@ -0,0 +1,100 @@ +{% from "macros/pagination.html" import render_pagination %} + +{% macro render_toc(toc, maxdepth=2) %} + <nav aria-label="Table of contents"> + <p>Table of contents</p> + <ol> + {% for item in toc recursive %} + <li> + <a href="#{{ item.anchor }}">{{ item.title }}</a> + {% if item.children and loop.depth < maxdepth %} + <ol> + {{ loop(item.children) }} + </ol> + {% endif %} + </li> + {% endfor %} + </ol> + </nav> +{% endmacro %} + +{% macro render_blog_post(post, show_toc=True) %} + <article> + <header> + <div> + <h2> + <a href="{{ post|url }}">{{ post.title }}</a> + </h2> + <dl> + {% if post.publication_date %} + <div> + <dt>Posted:</dt> + <dd> + <a href="{{ ('/blog@index-pages/year/{}'.format(post.publication_date.year) ) | url }}"> + <time datetime="{{ post.publication_date | dateformat('yyyy-MM-dd') }}">{{ post.publication_date | dateformat("d MMMM yyyy") }}</time> + </a> + </dd> + </div> + {% else %} + <div class="missing-publication-date-warning" role="alert"> + <dt>⚠</dt> + <dd> + Missing publication date + </dd> + </div> + {% endif %} + {% if post.updated_date and post.publication_date and (post.updated_date > post.publication_date) %} + <div> + <dt>Last updated:</dt> + <dd> + <time datetime="{{ post.updated_date | dateformat('yyyy-MM-dd') }}">{{ post.updated_date | dateformat("d MMMM yyyy") }}</time> + </dd> + </div> + {% endif %} + {% if post.category %} + <div> + <dt>Category:</dt> + <dd> + {% set category = site.get('/categories/'+post.category) %} + <a href="{{ category|url }}">{{ category.name }}</a> + </dd> + </div> + {% endif %} + </dl> + </div> + {% if show_toc and post.render_toc %} + {% set toc = post.body.toc|default([]) %} + {{ render_toc(toc) }} + {% endif %} + </header> + {{ post.body }} + {% if post.tags %} + <footer> + <p>Tagged with</p> + <ul aria-label="Tags"> + {% for tag in post.tags %} + <li> + {% set tag_slug = tag|trim|lower|replace(" ", "-") %} + <a href="{{ ('/blog/tag/' ~ tag_slug ~ '/')|url }}" rel="tag">{{ tag }}</a> + {%- if not loop.last -%}, {%- else -%}.{%- endif -%} + </li> + {% endfor %} + </ul> + </footer> + {% endif %} + </article> +{% endmacro %} + +{% macro render_blog_index(pagination) %} + {% for post in pagination.items %} + {{ render_blog_post(post, False) }} + {% if not loop.last %} + <hr> + {% endif %} + {% endfor %} + {% if pagination.pages > 1 %} + <footer> + {{ render_pagination(pagination) }} + </footer> + {% endif %} +{% endmacro %} diff --git a/templates/macros/pagination.html b/templates/macros/pagination.html new file mode 100644 index 0000000..adb1b5c --- /dev/null +++ b/templates/macros/pagination.html @@ -0,0 +1,221 @@ +{% macro render_pagination(pagination, pagination_window=2) %} + {% set current = pagination.page %} + {% set total = pagination.pages %} + {% set window_start = [current - pagination_window, 1] | max %} + {% set window_end = [current + pagination_window, total] | min %} + <nav aria-label="Page navigation"> + <ol> + <li> + {% if pagination.has_prev %} + <a href="{{ pagination.prev | url }}" aria-label="Previous page">❮</a> + {% else %} + <span aria-disabled="true">❮</span> + {% endif %} + </li> + {% if window_start > 1 %} + <li> + <a href="{{ pagination.for_page(1) | url }}" aria-label="Page 1">1</a> + </li> + {% if window_start > 2 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + {% endif %} + {% for page_num in range(window_start, window_end + 1) %} + <li> + {% if page_num == current %} + <a href="{{ pagination.for_page(page_num) | url }}" aria-current="page">{{ page_num }}</a> + {% else %} + <a href="{{ pagination.for_page(page_num) | url }}" + aria-label="Page {{ page_num }}">{{ page_num }}</a> + {% endif %} + </li> + {% endfor %} + {% if window_end < total %} + {% if window_end < total - 1 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + <li> + <a href="{{ pagination.for_page(total) | url }}" + aria-label="Page {{ total }}">{{ total }}</a> + </li> + {% endif %} + <li> + {% if pagination.has_next %} + <a href="{{ pagination.next | url }}" aria-label="Next page">❯</a> + {% else %} + <span aria-disabled="true">❯</span> + {% endif %} + </li> + </ol> + </nav> +{% endmacro %} + +{% macro render_year_archive_navigation( + current_year_page, + archive_window=2 + ) %} + {% set current_year = current_year_page.year %} + {% set first_year_page = current_year_page.first_year_page %} + {% set last_year_page = current_year_page.last_year_page %} + {% set previous_year_page = current_year_page.previous_year_page %} + {% set next_year_page = current_year_page.next_year_page %} + {% set first_year = first_year_page.year %} + {% set last_year = last_year_page.year %} + {% set window_start = [current_year - archive_window, first_year] | max %} + {% set window_end = [current_year + archive_window, last_year] | min %} + <nav aria-label="Year archive navigation"> + <ol> + <li> + {% if previous_year_page %} + <a href="{{ previous_year_page | url }}" + rel="prev" + aria-label="Previous year: {{ previous_year_page.year }}">❮</a> + {% else %} + <span aria-disabled="true">❮</span> + {% endif %} + </li> + {% if window_start > first_year %} + <li> + <a href="{{ first_year_page | url }}" aria-label="Year {{ first_year }}">{{ first_year }}</a> + </li> + {% if window_start > first_year + 1 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + {% endif %} + {% for year in range(window_start, window_end + 1) %} + {% set year_page = site.get( + '/blog@index-pages/year/{:04d}'.format(year) + ) %} + {% if year_page %} + <li> + {% if year == current_year %} + <a href="{{ year_page | url }}" aria-current="page">{{ year }}</a> + {% else %} + <a href="{{ year_page | url }}" aria-label="Year {{ year }}">{{ year }}</a> + {% endif %} + </li> + {% endif %} + {% endfor %} + {% if window_end < last_year %} + {% if window_end < last_year - 1 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + <li> + <a href="{{ last_year_page | url }}" aria-label="Year {{ last_year }}">{{ last_year }}</a> + </li> + {% endif %} + <li> + {% if next_year_page %} + <a href="{{ next_year_page | url }}" + rel="next" + aria-label="Next year: {{ next_year_page.year }}">❯</a> + {% else %} + <span aria-disabled="true">❯</span> + {% endif %} + </li> + </ol> + </nav> +{% endmacro %} + +{% macro render_month_archive_navigation( + current_month_page, + archive_window=2 + ) %} + {% set year_pages = index_pages('year') | sort(attribute='year') %} + {% set ns = namespace( + month_pages=[], + current_position=none + ) %} + {% for year_page in year_pages %} + {% set ns.month_pages = ns.month_pages + (year_page.subindexes | sort(attribute='month')) %} + {% endfor %} + {% for month_page in ns.month_pages %} + {% if month_page.year == current_month_page.year + and month_page.month == current_month_page.month %} + {% set ns.current_position = loop.index0 %} + {% endif %} + {% endfor %} + {% set total = ns.month_pages | length %} + {% set current_position = ns.current_position %} + {% set window_start = [current_position - archive_window, 0] | max %} + {% set window_end = [current_position + archive_window, total - 1] | min %} + {% set first_month_page = ns.month_pages[0] %} + {% set last_month_page = ns.month_pages[total - 1] %} + {% set previous_month_page = none %} + {% set next_month_page = none %} + {% if current_position > 0 %} + {% set previous_month_page = ns.month_pages[current_position - 1] %} + {% endif %} + {% if current_position < total - 1 %} + {% set next_month_page = ns.month_pages[current_position + 1] %} + {% endif %} + <nav aria-label="Monthly archive navigation"> + <ol> + <li> + {% if previous_month_page %} + <a href="{{ previous_month_page | url }}" + rel="prev" + aria-label="Previous month: {{ previous_month_page.date | dateformat('yyyy-MM') }}">❮</a> + {% else %} + <span aria-disabled="true">❮</span> + {% endif %} + </li> + {% if window_start > 0 %} + <li> + <a href="{{ first_month_page | url }}" + aria-label="{{ first_month_page.date | dateformat('yyyy-MM') }}"> + {{ first_month_page.date | dateformat("MMMM y") }} + </a> + </li> + {% if window_start > 1 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + {% endif %} + {% for position in range(window_start, window_end + 1) %} + {% set month_page = ns.month_pages[position] %} + <li> + {% if position == current_position %} + <a href="{{ month_page | url }}" aria-current="page">{{ month_page.date | dateformat("MMMM y") }}</a> + {% else %} + <a href="{{ month_page | url }}" + aria-label="{{ month_page.date | dateformat('yyyy-MM') }}"> + {{ month_page.date | dateformat("MMMM y") }} + </a> + {% endif %} + </li> + {% endfor %} + {% if window_end < total - 1 %} + {% if window_end < total - 2 %} + <li> + <span aria-hidden="true">…</span> + </li> + {% endif %} + <li> + <a href="{{ last_month_page | url }}" + aria-label="{{ last_month_page.date | dateformat('yyyy-MM') }}"> + {{ last_month_page.date | dateformat("MMMM y") }} + </a> + </li> + {% endif %} + <li> + {% if next_month_page %} + <a href="{{ next_month_page | url }}" + rel="next" + aria-label="Next month: {{ next_month_page.date | dateformat('yyyy-MM') }}">❯</a> + {% else %} + <span aria-disabled="true">❯</span> + {% endif %} + </li> + </ol> + </nav> +{% endmacro %} diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..dd20955 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,3 @@ +{% extends "base.html" %} + +{% block content %}<article>{{ this.body }}</article>{% endblock %} diff --git a/templates/tag.html b/templates/tag.html new file mode 100644 index 0000000..b433616 --- /dev/null +++ b/templates/tag.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% block meta %} + {{ super() }} + <meta property="og:type" content="website"> +{% endblock %} +{% block styles %} + {{ super() }} + <link rel="stylesheet" + type="text/css" + href="{{ '/static/css/category.css'|asseturl }}"> +{% endblock %} +{% set title = "Tag " + this.tag %} +{% block content %} + {% set post_count = this.items.count() %} + <article> + <header> + <hgroup> + <h2>Tag: {{ this.tag }}</h2> + <p> + ({{ post_count }} + {% if post_count > 1 %} + posts) + {% else %} + post) + {% endif %} + </p> + </hgroup> + </header> + <table> + <thead class="visually-hidden"> + <tr> + <th scope="col">Date</th> + <th scope="col">Title</th> + </tr> + </thead> + <tbody> + {% for post in this.items %} + <tr> + <td> + {% if post.publication_date %} + <time datetime="{{ post.publication_date }}">{{ post.publication_date }}</time> + {% endif %} + </td> + <td> + <a href="{{ post|url }}">{{ post.title }}</a> + </td> + </tr> + {% else %} + <tr> + <td colspan="2"> + <em>No posts for this tag.</em> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </article> +{% endblock %} |
