Compare commits

...

3 Commits

Author SHA1 Message Date
0af676766a Fix link handling
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-08-08 18:09:48 +02:00
61650e9c0f Add photoswipe
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-08-08 17:55:55 +02:00
8e240134b0 Add list page
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-08-08 16:14:18 +02:00
26 changed files with 10065 additions and 106 deletions

View File

@@ -1,19 +1,14 @@
.PHONY: all css js font update-inter reset-submodules
.PHONY: default update update-inter update-photoswipe format
all: css js font reset-submodules
default:
css:
js:
font:
update: update-inter update-photoswipe format
update-inter:
@tmp_zip=$$(mktemp) && \
tmp_dir=$$(mktemp -d) && \
curl -s https://api.github.com/repos/rsms/inter/releases/latest \
| grep "browser_download_url.*zip" \
| cut -d '"' -f 4 \
| jq -r '.assets[] | select(.name | test("\\.zip$$")) | .browser_download_url' \
| xargs curl -Ls -o $$tmp_zip && \
unzip -q -o $$tmp_zip "web/*" -d $$tmp_dir && \
rm -rf static/font/inter && \
@@ -21,5 +16,19 @@ update-inter:
cp -r $$tmp_dir/web/{InterVariable*.woff2,Inter-*.woff2} static/font/inter && \
rm -rf $$tmp_zip $$tmp_dir
reset-submodules:
git submodule foreach --recursive 'git reset --hard'
update-photoswipe:
@tmp_zip=$$(mktemp) && \
tmp_dir=$$(mktemp -d) && \
curl -s https://api.github.com/repos/dimsemenov/photoswipe/releases/latest \
| jq -r '.zipball_url' \
| xargs curl -Ls -o $$tmp_zip && \
unzip -q -o $$tmp_zip -d $$tmp_dir && \
rm -rf assets/js/photoswipe assets/css/photoswipe && \
mkdir -p assets/js/photoswipe assets/css/photoswipe && \
cp $$tmp_dir/*PhotoSwipe*/dist/photoswipe-lightbox.esm.js assets/js/photoswipe && \
cp $$tmp_dir/*PhotoSwipe*/dist/photoswipe.esm.js assets/js/photoswipe && \
cp $$tmp_dir/*PhotoSwipe*/dist/photoswipe.css assets/css/photoswipe && \
rm -rf $$tmp_zip $$tmp_dir
format:
nix fmt

View File

@@ -0,0 +1,417 @@
/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */
.pswp {
--pswp-bg: #000;
--pswp-placeholder-bg: #222;
--pswp-root-z-index: 100000;
--pswp-preloader-color: rgba(79, 79, 79, 0.4);
--pswp-preloader-color-secondary: rgba(255, 255, 255, 0.9);
/* defined via js:
--pswp-transition-duration: 333ms; */
--pswp-icon-color: #fff;
--pswp-icon-color-secondary: #4f4f4f;
--pswp-icon-stroke-color: #4f4f4f;
--pswp-icon-stroke-width: 2px;
--pswp-error-text-color: var(--pswp-icon-color);
}
/*
Styles for basic PhotoSwipe (pswp) functionality (sliding area, open/close transitions)
*/
.pswp {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: var(--pswp-root-z-index);
display: none;
touch-action: none;
outline: 0;
opacity: 0.003;
contain: layout style size;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* Prevents focus outline on the root element,
(it may be focused initially) */
.pswp:focus {
outline: 0;
}
.pswp * {
box-sizing: border-box;
}
.pswp img {
max-width: none;
}
.pswp--open {
display: block;
}
.pswp,
.pswp__bg {
transform: translateZ(0);
will-change: opacity;
}
.pswp__bg {
opacity: 0.005;
background: var(--pswp-bg);
}
.pswp,
.pswp__scroll-wrap {
overflow: hidden;
}
.pswp__scroll-wrap,
.pswp__bg,
.pswp__container,
.pswp__item,
.pswp__content,
.pswp__img,
.pswp__zoom-wrap {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.pswp__img,
.pswp__zoom-wrap {
width: auto;
height: auto;
}
.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img {
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in;
}
.pswp--click-to-zoom.pswp--zoomed-in .pswp__img {
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* :active to override grabbing cursor */
.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,
.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,
.pswp__img {
cursor: -webkit-zoom-out;
cursor: -moz-zoom-out;
cursor: zoom-out;
}
/* Prevent selection and tap highlights */
.pswp__container,
.pswp__img,
.pswp__button,
.pswp__counter {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.pswp__item {
/* z-index for fade transition */
z-index: 1;
overflow: hidden;
}
.pswp__hidden {
display: none !important;
}
/* Allow to click through pswp__content element, but not its children */
.pswp__content {
pointer-events: none;
}
.pswp__content > * {
pointer-events: auto;
}
/*
PhotoSwipe UI
*/
/*
Error message appears when image is not loaded
(JS option errorMsg controls markup)
*/
.pswp__error-msg-container {
display: grid;
}
.pswp__error-msg {
margin: auto;
font-size: 1em;
line-height: 1;
color: var(--pswp-error-text-color);
}
/*
class pswp__hide-on-close is applied to elements that
should hide (for example fade out) when PhotoSwipe is closed
and show (for example fade in) when PhotoSwipe is opened
*/
.pswp .pswp__hide-on-close {
opacity: 0.005;
will-change: opacity;
transition: opacity var(--pswp-transition-duration)
cubic-bezier(0.4, 0, 0.22, 1);
z-index: 10; /* always overlap slide content */
pointer-events: none; /* hidden elements should not be clickable */
}
/* class pswp--ui-visible is added when opening or closing transition starts */
.pswp--ui-visible .pswp__hide-on-close {
opacity: 1;
pointer-events: auto;
}
/* <button> styles, including css reset */
.pswp__button {
position: relative;
display: block;
width: 50px;
height: 60px;
padding: 0;
margin: 0;
overflow: hidden;
cursor: pointer;
background: none;
border: 0;
box-shadow: none;
opacity: 0.85;
-webkit-appearance: none;
-webkit-touch-callout: none;
}
.pswp__button:hover,
.pswp__button:active,
.pswp__button:focus {
transition: none;
padding: 0;
background: none;
border: 0;
box-shadow: none;
opacity: 1;
}
.pswp__button:disabled {
opacity: 0.3;
cursor: auto;
}
.pswp__icn {
fill: var(--pswp-icon-color);
color: var(--pswp-icon-color-secondary);
}
.pswp__icn {
position: absolute;
top: 14px;
left: 9px;
width: 32px;
height: 32px;
overflow: hidden;
pointer-events: none;
}
.pswp__icn-shadow {
stroke: var(--pswp-icon-stroke-color);
stroke-width: var(--pswp-icon-stroke-width);
fill: none;
}
.pswp__icn:focus {
outline: 0;
}
/*
div element that matches size of large image,
large image loads on top of it,
used when msrc is not provided
*/
div.pswp__img--placeholder,
.pswp__img--with-bg {
background: var(--pswp-placeholder-bg);
}
.pswp__top-bar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 60px;
display: flex;
flex-direction: row;
justify-content: flex-end;
z-index: 10;
/* allow events to pass through top bar itself */
pointer-events: none !important;
}
.pswp__top-bar > * {
pointer-events: auto;
/* this makes transition significantly more smooth,
even though inner elements are not animated */
will-change: opacity;
}
/*
Close button
*/
.pswp__button--close {
margin-right: 6px;
}
/*
Arrow buttons
*/
.pswp__button--arrow {
position: absolute;
top: 0;
width: 75px;
height: 100px;
top: 50%;
margin-top: -50px;
}
.pswp__button--arrow:disabled {
display: none;
cursor: default;
}
.pswp__button--arrow .pswp__icn {
top: 50%;
margin-top: -30px;
width: 60px;
height: 60px;
background: none;
border-radius: 0;
}
.pswp--one-slide .pswp__button--arrow {
display: none;
}
/* hide arrows on touch screens */
.pswp--touch .pswp__button--arrow {
visibility: hidden;
}
/* show arrows only after mouse was used */
.pswp--has_mouse .pswp__button--arrow {
visibility: visible;
}
.pswp__button--arrow--prev {
right: auto;
left: 0px;
}
.pswp__button--arrow--next {
right: 0px;
}
.pswp__button--arrow--next .pswp__icn {
left: auto;
right: 14px;
/* flip horizontally */
transform: scale(-1, 1);
}
/*
Zoom button
*/
.pswp__button--zoom {
display: none;
}
.pswp--zoom-allowed .pswp__button--zoom {
display: block;
}
/* "+" => "-" */
.pswp--zoomed-in .pswp__zoom-icn-bar-v {
display: none;
}
/*
Loading indicator
*/
.pswp__preloader {
position: relative;
overflow: hidden;
width: 50px;
height: 60px;
margin-right: auto;
}
.pswp__preloader .pswp__icn {
opacity: 0;
transition: opacity 0.2s linear;
animation: pswp-clockwise 600ms linear infinite;
}
.pswp__preloader--active .pswp__icn {
opacity: 0.85;
}
@keyframes pswp-clockwise {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/*
"1 of 10" counter
*/
.pswp__counter {
height: 30px;
margin-top: 15px;
margin-inline-start: 20px;
font-size: 14px;
line-height: 30px;
color: var(--pswp-icon-color);
text-shadow: 1px 1px 3px var(--pswp-icon-color-secondary);
opacity: 0.85;
}
.pswp--one-slide .pswp__counter {
display: none;
}

10
assets/js/photoswipe.js Normal file
View File

@@ -0,0 +1,10 @@
import PhotoSwipeLightbox from "js/photoswipe/photoswipe-lightbox.esm.js";
new PhotoSwipeLightbox({
gallery: ".gallery",
children: "a",
showAnimationDuration: 300,
hideAnimationDuration: 300,
initialZoomLevel: "fit",
pswpModule: () => import("js/photoswipe/photoswipe.esm.js"),
}).init();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
padding: 2rem;
.header-container {
max-width: $width-max;
max-width: $width-wide;
width: 100%;
margin: 0 auto;

View File

@@ -3,6 +3,10 @@
margin: 0 auto;
padding: 2rem 2rem 0 2rem;
h1 {
font-size: 2.5rem;
}
.meta {
font-size: 0.8rem;
@@ -14,6 +18,10 @@
color: color-mix(in srgb, var(--text) 50%, var(--background));
}
}
.image {
padding-top: 1.5rem;
}
}
.hero + .content {

View File

@@ -4,6 +4,7 @@
img {
width: 100%;
height: auto;
display: block;
}

96
assets/sass/list.scss Normal file
View File

@@ -0,0 +1,96 @@
main.with-meta {
max-width: $width-wide;
margin: 0 auto;
padding: 2rem;
> .content {
padding: 0;
}
$width-sidebar: ($width-wide - $width-content) / 2 - 2rem;
display: grid;
grid-template-columns: $width-sidebar $width-content $width-sidebar;
column-gap: 2rem;
h1 {
font-size: 1.75rem;
}
.length {
color: color-mix(in srgb, var(--text) 50%, var(--background));
margin: 0.25rem 0;
font-size: 0.8rem;
}
}
@media (max-width: $width-wide) {
main.with-meta {
display: block;
padding: 0;
> .meta {
max-width: $width-content;
margin: 0 auto;
padding: 2rem;
}
> .content {
padding: 2rem;
}
}
}
@media (max-width: $width-mobile) {
main.with-meta {
> .meta {
padding: 1rem;
}
> .content {
padding: 0 1rem 1rem 1rem;
}
}
}
.posts-list {
.post {
display: block;
color: inherit;
text-decoration: none;
&:not(:first-child) {
margin-top: 4rem;
}
.title {
font-size: 2.25rem;
}
.meta {
font-size: 0.8rem;
.date {
color: var(--text-600);
}
.duration {
color: color-mix(in srgb, var(--text) 50%, var(--background));
}
}
}
.post:hover .title,
.post:hover .summary {
color: var(--text-900);
}
}
@media (max-width: $width-mobile) {
.posts-list {
.post {
&:not(:first-child) {
margin-top: 2rem;
}
}
}
}

View File

@@ -1,6 +1,6 @@
$width-mobile: 30rem;
$width-content: 45rem;
$width-max: 60rem;
$width-wide: 75rem;
@import "colors";
@import "fonts";
@@ -9,8 +9,11 @@ $width-max: 60rem;
@import "heading";
@import "image";
@import "list";
@import "header";
@import "posts";
@import "content";
@import "hero";
@import "footer";
@import "photoswipe";

View File

@@ -0,0 +1,5 @@
.pswp {
--pswp-bg: var(--background);
--pswp-icon-color: var(--text);
--pswp-icon-color-secondary: color-mix(in srgb, var(--text) 30%, var(--background));
}

View File

@@ -1,42 +0,0 @@
.posts-list {
.post {
display: block;
color: inherit;
text-decoration: none;
&:not(:first-child) {
margin-top: 4rem;
}
.title {
font-size: 2.25rem;
}
.meta {
font-size: 0.8rem;
.date {
color: var(--text-600);
}
.duration {
color: color-mix(in srgb, var(--text) 50%, var(--background));
}
}
}
.post:hover .title,
.post:hover .summary {
color: var(--text-900);
}
}
@media (max-width: $width-mobile) {
.posts-list {
.post {
&:not(:first-child) {
margin-top: 2rem;
}
}
}
}

View File

@@ -27,6 +27,7 @@
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
hugo
jq
unzip
];
};

View File

@@ -4,7 +4,7 @@
<h1>404</h1>
<p>This is a dead link. Whatever was here, it&apos;s gone now.</p>
<p>
<a href="{{ .Site.BaseURL }}">Let&apos;s pretend this never happened</a
<a href="{{- relURL "" -}}">Let&apos;s pretend this never happened</a
>.
</p>
</div>

View File

@@ -19,27 +19,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="all,follow" />
<meta name="googlebot" content="index,follow,snippet,archive" />
{{ with resources.Get "sass/main.scss" }}
{{ with . | toCSS (dict "targetPath" "css/main.css") | fingerprint }}
<link
rel="stylesheet"
href="{{- .RelPermalink -}}"
integrity="{{- .Data.Integrity -}}"
/>
{{ end }}
{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf `
<link href="%s" rel="%s" type="%s" title="%s" />` .Permalink .Rel .MediaType.Type $.Site.Title | safeHTML
}}
{{ end }}
{{ partial "opengraph.html" . }}
{{ partial "twitter.html" . }}
{{ partial "head/bots.html" }}
{{ partial "head/rss.html" }}
{{ partial "head/opengraph.html" . }}
{{ partial "head/twitter.html" . }}
{{ partial "head/photoswipe.html" }}
{{ partial "head/styles.html" }}
</head>
<body>

View File

@@ -0,0 +1,12 @@
{{ define "main" }}
<main class="with-meta">
<section class="meta">
<h1>{{ .Page.Title }}</h1>
<p class="length">A {{ .Kind }} with {{ len .Pages }} items</p>
{{ .Content }}
</section>
<div class="content">
{{ partial "posts/list.html" . }}
</div>
</main>
{{ end }}

View File

@@ -1,7 +1,7 @@
<footer class="footer">
<div class="footer-container">
<div class="copyright">
<a href="{{- "/" | relURL -}}">{{ .Site.Title }}</a> &copy;
<a href="{{- relURL "" -}}">{{ .Site.Title }}</a> &copy;
{{ now.Year }}
</div>
@@ -10,7 +10,7 @@
{{ $len := len .Site.Params.footer }}
{{ range $i, $el := .Site.Params.footer }}
<li>
<a href="{{- $el.url -}}">{{ $el.name }}</a>
<a href="{{- $el.url | safeURL -}}">{{ $el.name }}</a>
</li>
{{ if lt (add $i 1) $len }}
<span class="middot">&middot;</span>

View File

@@ -0,0 +1,2 @@
<meta name="robots" content="all,follow" />
<meta name="googlebot" content="index,follow,snippet,archive" />

View File

@@ -58,12 +58,11 @@
{{ end }}
{{ end }}
{{ $permalink := .Permalink }}
{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }}
{{ range $name := . }}
{{ $series := index $siteSeries $name }}
{{ range $page := first 6 $series.Pages }}
{{ if ne $page.Permalink $permalink }}
{{ if ne $page.Permalink .Permalink }}
<meta property="og:see_also" content="{{- $page.Permalink -}}" />
{{ end }}
{{ end }}

View File

@@ -0,0 +1,17 @@
{{ with resources.Get "css/photoswipe/photoswipe.css" | minify | fingerprint }}
<link
rel="stylesheet"
href="{{- .RelPermalink -}}"
integrity="{{- .Data.Integrity -}}"
/>
{{ end }}
{{ $opts := dict "minify" true }}
{{ with resources.Get "js/photoswipe.js" | js.Build $opts | minify | fingerprint }}
<script
src="{{ .RelPermalink }}"
integrity="{{ .Data.Integrity }}"
crossorigin="anonymous"
async
></script>
{{ end }}

View File

@@ -0,0 +1,5 @@
{{ with .OutputFormats.Get "RSS" }}
{{ printf `
<link href="%s" rel="%s" type="%s" title="%s" />` .Permalink .Rel .MediaType.Type $.Site.Title | safeHTML
}}
{{ end }}

View File

@@ -0,0 +1,8 @@
{{ $opts := dict "targetPath" "css/main.css" }}
{{ with resources.Get "sass/main.scss" | css.Sass $opts | minify | fingerprint }}
<link
rel="stylesheet"
href="{{- .RelPermalink -}}"
integrity="{{- .Data.Integrity -}}"
/>
{{ end }}

View File

@@ -1,13 +1,3 @@
{{ with .Params.image }}
{{ $image := $.Resources.GetMatch . }}
{{ with $image }}
{{ $thumb := .Resize "400x" }}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="{{- $thumb.Permalink -}}" />
{{ end }}
{{ end }}
<meta name="twitter:title" content="{{- .Title -}}" />
<meta
name="twitter:description"
@@ -21,3 +11,12 @@
{{- end -}}
{{- end -}}"
/>
{{ with .Params.image }}
{{ $image := $.Resources.GetMatch . }}
{{ with $image }}
{{ $thumb := .Resize "400x" }}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="{{- $thumb.Permalink -}}" />
{{ end }}
{{ end }}

View File

@@ -1,7 +1,7 @@
<header class="header">
<div class="header-container">
<div class="site-title">
<a href="{{- "/" | relURL -}}">{{ .Site.Title }}</a>
<a href="{{- relURL "" -}}">{{ .Site.Title }}</a>
</div>
<input
@@ -18,7 +18,7 @@
<nav class="nav">
<ul>
{{ range .Site.Params.navigation }}
<li><a href="{{- .url -}}">{{ .name }}</a></li>
<li><a href="{{- .url | safeURL -}}">{{ .name }}</a></li>
{{ end }}
</ul>
</nav>

View File

@@ -2,15 +2,28 @@
{{- $caption := .caption -}}
{{- $alt := default .caption .alt -}}
{{ if $path }}
<figure class="image">
<img
src="{{- $path | absURL -}}"
{{ with $caption }}title="{{- . -}}"{{ end }}
{{ with $alt }}alt="{{- . -}}"{{ end }}
/>
{{ with $caption }}
<figcaption>{{ . }}</figcaption>
{{ end }}
</figure>
{{ end }}
{{- if $path -}}
{{- $isRemote := strings.HasPrefix $path "http" -}}
{{- $image := cond $isRemote (resources.GetRemote $path) (resources.Get $path) -}}
{{- if $image -}}
<figure class="image gallery">
<a
href="{{- $image.RelPermalink -}}"
data-pswp-width="{{- $image.Width -}}"
data-pswp-height="{{- $image.Height -}}"
>
<img
src="{{- $image.RelPermalink -}}"
width="{{- $image.Width -}}"
height="{{- $image.Height -}}"
{{ with $caption }}title="{{- . -}}"{{ end }}
{{ with $alt }}alt="{{- . -}}"{{ end }}
/>
</a>
{{ with $caption }}
<figcaption>{{ . }}</figcaption>
{{ end }}
</figure>
{{- end -}}
{{- end -}}

View File

@@ -1,7 +1,7 @@
<div class="posts-list">
{{ range sort .Pages "Date" "desc" }}
{{ if not .Params.private }}
<a href="{{ .Permalink }}" class="post">
<a href="{{- .RelPermalink -}}" class="post">
<h1 class="title">{{ .Title }}</h1>
<div class="summary">{{ .Summary }}</div>
<div class="meta">