Compare commits

...
Sign in to create a new pull request.

9 commits

Author SHA1 Message Date
6dbf3da09b WIP carousel post for the Temple of Hephaestus 2023-04-06 08:48:57 -07:00
afb59e7862 Prepare thumbnails of all images in the photo post
Add <img> tags for all thumbnails to the photos list entry. I'm hoping with lazy
loading and some JavaScript this will not be terrible!

Break the logic for preparing a thumbnail into a separate helper partial
template so it can be reused.
2023-04-06 08:45:35 -07:00
da994bd6b7 Add a JavaScript file for the photos list 2023-04-06 08:45:35 -07:00
ad9f83ed39 Refactor carousel code so more of it is in the Carousel class
Load the script as a module
2023-04-06 08:45:35 -07:00
bb655bcf30 Move photo_carousel.js to assets/photos/carousel.js 2023-04-06 08:45:35 -07:00
af957c0150 Pull carousels array out into a variable and bail early if there are no carousels 2023-04-06 08:45:35 -07:00
00d2e9263d Add a badge to the photo list items that have multiple photos in them 2023-04-06 08:45:35 -07:00
baad04e7a4 Couple'a code formatting tweaks to section_css.thml and single_styles.html 2023-04-06 08:45:35 -07:00
81a5507e8f Implement a photo carousel
The layout of this is all CSS with scroll-snap. (Neat!) Also implement a
JavaScript scroll event listener that detects when photos scroll and
updates the caption and photo data.

Refactor a few parts of the photo_exif_table into partials so they can
be reused for the carousel items.
2023-04-06 08:45:35 -07:00
17 changed files with 693 additions and 28 deletions

View file

@ -0,0 +1,304 @@
// Eryn Wells <eryn@erynwells.me>
class Carousel {
carousel = null;
#items = null;
#caption = null;
constructor(element) {
this.carousel = element;
this.#caption = element.querySelector("figcaption");
}
get items() {
if (this.#items === null) {
const boundingRect = this.carousel.getBoundingClientRect();
this.#items = Array.from(this.carousel.querySelectorAll("ul > li")).map(item => {
let itemRect = item.getClientRects()[0];
let relativeX = itemRect.x - boundingRect.x;
return new CarouselItem(item, relativeX);
});
}
return this.#items;
}
get scrollContainer() {
return this.carousel.querySelector("ul");
}
setCaption(caption) {
if (!this.#caption) {
return;
}
this.#caption.innerHTML = caption;
}
scrollTo(index) {
this.scrollContainer.scrollTo(this.items[index].relativeX, 0);
}
setUpScrollEventListener(photoParametersTable) {
let previousScrollLeft = null;
let isScrollingLeft = true;
this.scrollContainer.addEventListener("scroll", event => {
const target = event.target;
const scrollLeft = target.scrollLeft;
const carouselRect = target.getBoundingClientRect();
const carouselRectHorizontalCenter = carouselRect.width / 2.0;
if (previousScrollLeft !== null) {
isScrollingLeft = scrollLeft > previousScrollLeft;
}
previousScrollLeft = scrollLeft;
this.items.forEach(item => {
const itemRelativeXCoordinate = isScrollingLeft ? item.relativeX - scrollLeft : item.relativeMaxX - scrollLeft;
const itemWasLeftOfContainerCenter = item.isLeftOfContainerCenter;
const itemIsLeftOfContainerCenter = itemRelativeXCoordinate < carouselRectHorizontalCenter;
item.isLeftOfContainerCenter = itemIsLeftOfContainerCenter;
if ( (isScrollingLeft && (!itemWasLeftOfContainerCenter && itemIsLeftOfContainerCenter))
|| (!isScrollingLeft && (itemWasLeftOfContainerCenter && !itemIsLeftOfContainerCenter))) {
this.setCaption(item.title);
photoParametersTable.setMakeModel(item.make, item.model);
photoParametersTable.setLocation(item.latitude, item.longitude);
photoParametersTable.setMegapixels(item.megapixels);
photoParametersTable.setSize(item.width, item.height);
photoParametersTable.setISO(item.iso);
photoParametersTable.setFocalLength(item.focalLength);
photoParametersTable.setFNumber(item.fNumber);
photoParametersTable.setExposureTime(item.exposureTime);
}
});
});
}
}
class CarouselItem {
element = null;
relativeX = 0;
isLeftOfContainerCenter = false;
constructor(element, relativeX) {
this.element = element;
this.relativeX = relativeX
}
get relativeMaxX() {
const rect = this.element.getBoundingClientRect();
return this.relativeX + rect.width;
}
get title() { return this.element.dataset.title; }
get latitude() { return this.element.dataset.latitude; }
get longitude() { return this.element.dataset.longitude; }
get megapixels() { return this.element.dataset.megapixels; }
get width() { return this.element.dataset.width; }
get height() { return this.element.dataset.height; }
get make() { return this.element.dataset.make; }
get model() { return this.element.dataset.model; }
get iso() { return this.element.dataset.iso; }
get focalLength() { return this.element.dataset.focalLength; }
get fNumber() { return this.element.dataset.fNumber; }
get exposureTime() { return this.element.dataset.exposureTime; }
}
class PhotoParametersTable {
tableElement;
#makeModelElement;
#latitudeElement;
#longitudeElement;
#megapixelsElement;
#widthElement;
#heightElement;
#isoElement;
#focalLengthElement;
#fNumberElement;
#exposureTimeElement;
constructor(element) {
this.tableElement = element;
}
setMakeModel(make, model) {
if (!this.#makeModelElement) {
this.#makeModelElement = this.#getElementWithQuerySelector("td.make-model");
}
if (!this.#makeModelElement) {
return;
}
let makeModel = "";
if (make && model) {
return `${make} ${model}`;
} else if (make) {
return make;
} else if (model) {
return model;
} else {
return "";
}
this.#makeModelElement.innerHTML = makeModel;
}
setLocation(latitude, longitude) {
let latitudeElement = this.#latitudeElement;
if (!latitudeElement) {
latitudeElement = this.#getElementWithQuerySelector("td.location > data.latitude");
this.#latitudeElement = latitudeElement;
}
let longitudeElement = this.#longitudeElement;
if (!longitudeElement) {
longitudeElement = this.#getElementWithQuerySelector("td.location > data.longitude");
this.#longitudeElement = longitudeElement;
}
if (!latitudeElement || !longitudeElement) {
return;
}
latitudeElement.innerHTML = latitude;
longitudeElement.innerHTML = longitude;
}
setMegapixels(megapixels) {
let megapixelsElement = this.#megapixelsElement;
if (!megapixelsElement) {
megapixelsElement = this.#getElementWithQuerySelector("td.size > data.megapixels");
this.#megapixelsElement = megapixelsElement;
}
if (!megapixelsElement) {
return;
}
megapixelsElement.innerHTML = megapixels;
}
setSize(width, height) {
let widthElement = this.#widthElement;
if (!widthElement) {
widthElement = this.#getElementWithQuerySelector("td.size > data.width");
this.#widthElement = widthElement;
}
let heightElement = this.#heightElement;
if (heightElement) {
heightElement = this.#getElementWithQuerySelector("td.size > data.height");
this.#heightElement = heightElement;
}
if (!widthElement || !heightElement) {
return;
}
widthElement.innerHTML = width;
heightElement.innerHTML = height;
}
setISO(iso) {
let isoElement = this.#isoElement;
if (!isoElement) {
isoElement = this.#getElementWithQuerySelector("td.iso");
this.#isoElement = isoElement;
}
if (!isoElement) {
return;
}
isoElement.innerHTML = `ISO ${iso}`;
}
setFocalLength(focalLength) {
let focalLengthElement = this.#focalLengthElement;
if (!focalLengthElement) {
focalLengthElement = this.#getElementWithQuerySelector("td.focal-length");
this.#focalLengthElement = focalLengthElement;
}
if (!focalLengthElement) {
return;
}
focalLengthElement.innerHTML = `${focalLength} mm`;
}
setFNumber(f) {
let fNumberElement = this.#fNumberElement;
if (!fNumberElement) {
fNumberElement = this.#getElementWithQuerySelector("td.f-number");
this.#fNumberElement = fNumberElement;
}
if (!fNumberElement) {
return;
}
fNumberElement.innerHTML = `ƒ${f}`;
}
setExposureTime(exposureTime) {
let exposureTimeElement = this.#exposureTimeElement;
if (!exposureTimeElement) {
exposureTimeElement = this.#getElementWithQuerySelector("td.exposure-time");
this.#exposureTimeElement = exposureTimeElement;
}
if (!exposureTimeElement) {
return;
}
exposureTimeElement.innerHTML = `${exposureTime} s`;
}
#getElementWithQuerySelector(query) {
const element = this.tableElement.querySelector(query);
if (!element) {
return null;
}
return element;
}
}
document.addEventListener("DOMContentLoaded", (event) => {
const carouselElements = document.querySelectorAll("figure.carousel");
if (carouselElements.length === 0) {
return;
}
const allCarousels = Array.from(carouselElements).sort((a, b) => {
const aRect = a.getBoundingClientRect();
const bRect = a.getBoundingClientRect();
return aRect.top - bRect.top;
}).map(elem => new Carousel(elem));
let photoParametersTable = null;
const photoParamsTableElement = document.querySelector(".photo-params table");
if (photoParamsTableElement) {
photoParametersTable = new PhotoParametersTable(photoParamsTableElement);
}
allCarousels.forEach(carousel => {
carousel.setUpScrollEventListener(photoParametersTable);
});
const url = new URL(window.location);
let photoGetParameter = url.searchParams.get("photo");
if (photoGetParameter) {
try {
photoGetParameter = parseInt(photoGetParameter);
allCarousels[0].scrollTo(photoGetParameter);
} catch (e) {
console.error("Unable to parse 'photo' GET parameter as integer:", e);
}
}
});

View file

@ -0,0 +1,111 @@
// Eryn Wells <eryn@erynwells.me>
class Item {
static allItems = [];
element;
#mouseoverRegionOffsets;
#currentRegion = 0;
constructor(element) {
this.element = element;
this.setUpEventHandlers();
element._item = this;
}
get title() {
return this.element.getAttribute("title");
}
get numberOfImages() {
const numberOfImages = this.element.dataset.numberOfImages;
if (typeof numberOfImages === "undefined" || numberOfImages === null) {
return 1;
}
try {
return parseInt(numberOfImages);
} catch (e) {
console.error("Unable to parse data-number-of-images attribute:", numberOfImages);
return 1;
}
}
get mouseoverRegionOffsets() {
let offsets = this.#mouseoverRegionOffsets;
if (!offsets) {
const numberOfImages = this.numberOfImages;
offsets = new Array(numberOfImages);
const rect = this.element.getBoundingClientRect();
const widthOfRegion = rect.width / numberOfImages;
for (let i = 0; i < numberOfImages; i++) {
offsets[i] = i * widthOfRegion;
}
this.#mouseoverRegionOffsets = offsets;
}
return offsets;
}
setUpEventHandlers() {
if (this.numberOfImages <= 1) {
return;
}
this.element.addEventListener("mousemove", event => {
const offsetX = event.offsetX;
let indexOfRegion = -1;
for (let regionOffset of this.mouseoverRegionOffsets) {
if (regionOffset > offsetX) {
break;
}
indexOfRegion += 1;
}
if (indexOfRegion !== this.#currentRegion) {
console.debug(`Mouse moved at ${offsetX}, ${event.offsetY} in cell for '${this.title}' in region ${indexOfRegion}`);
// TODO: Set image to images[indexOfRegion]
this.#currentRegion = indexOfRegion;
}
});
this.element.addEventListener("mouseout", event => {
console.debug(`Mouse left cell for '${this.title}'`);
// TODO: Reset to the first image.
});
}
addToIntersectionObserver(intersectionObserver) {
intersectionObserver.observe(this.element);
}
handleIntersectionObservation(entry, observer) {
if (entry.isIntersecting) {
console.debug(`Cell for ${this.title} entered the viewport`);
} else {
console.debug(`Cell for ${this.title} left the viewport`);
}
}
}
let intersectionObserver;
document.addEventListener("DOMContentLoaded", event => {
intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
entry.target._item.handleIntersectionObservation(entry, observer);
});
}, { threshold: 0.5 });
Item.allItems = Array.from(document.querySelectorAll(".photos.list > a")).map(cell => {
const item = new Item(cell)
item.addToIntersectionObserver(intersectionObserver);
return item;
});
});

View file

@ -1,10 +1,14 @@
:root {
--date-item-background-color: rgb(var(--lt-gray));
--photo-grid-item-size: 200px;
--photo-params-background-color: rgb(var(--lt-gray));
--photo-params-container-background-color: rgb(var(--super-lt-gray));
--photo-params-color: rgb(var(--sub-dk-gray));
--photo-params-border-color: rgb(var(--super-lt-gray));
--carousel-gradient-full-opaque: var(--background);
}
@media (prefers-color-scheme: dark) {
:root {
@ -26,7 +30,7 @@
.photos.list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(var(--photo-grid-item-size), 1fr));
gap: 4px;
}
@ -34,6 +38,10 @@
margin-block-end: 0;
}
.photos.list > :has(img) {
aspect-ratio: 1;
}
.photos.page > nav {
margin-block-end: var(--body-item-spacing);
}
@ -41,11 +49,37 @@
.photos.list > a {
display: block;
line-height: 0;
position: relative;
}
.photos.list > a:hover {
text-decoration: none;
}
.photos.list > a > img {
border-radius: 3px;
image-orientation: from-image;
position: absolute;
top: 0;
left: 0;
}
.photos.list > a > .badge {
align-items: center;
aspect-ratio: 1;
background: rgba(255, 255, 255, 0.6);
border-radius: 4px;
color: rgb(var(--mid-gray));
display: flex;
left: 4px;
line-height: 0;
padding: 1rem;
position: relative;
top: 4px;
width: max-content;
z-index: 1;
}
.photos.list > a > .badge:hover {
text-decoration: none;
}
.photos.list > div {
@ -80,7 +114,7 @@
content: "⏵︎";
font-size: 80%;
}
@media (max-width: calc(24px + 400px + 4px)) {
@media (max-width: calc(24px + 400px + 9px)) {
.photos.list > div > h6 > span {
width: max-content;
padding-block: calc(0.25 * var(--body-item-spacing));
@ -90,6 +124,62 @@
}
}
.photos.page > figure.carousel {
position: relative;
max-width: 100%;
width: 100%;
}
.photos.page > figure.carousel > ul {
align-items: stretch;
display: flex;
flex-flow: row nowrap;
gap: 0 calc(0.5 * var(--body-item-spacing));
margin: 0;
overflow-x: scroll;
scroll-snap-type: x mandatory;
padding: 0;
padding-inline: var(--body-item-spacing);
}
.photos.page > figure.carousel > ul::-webkit-scrollbar {
background: transparent;
width: 0;
}
.photos.page > figure.carousel > .shadow {
position: absolute;
top: 0;
height: 100%;
width: var(--body-item-spacing);
z-index: 10;
}
.photos.page > figure.carousel > .shadow.left {
background: linear-gradient(
to right,
rgba(var(--carousel-gradient-full-opaque), 1),
rgba(var(--carousel-gradient-full-opaque), 0));
left: 0;
}
.photos.page > figure.carousel > .shadow.right {
background: linear-gradient(
to left,
rgba(var(--carousel-gradient-full-opaque), 1),
rgba(var(--carousel-gradient-full-opaque), 0));
right: 0;
}
.photos.page > figure.carousel > ul > li {
scroll-snap-align: center;
margin: 0;
width: var(--content-width);
flex-shrink: 0;
list-style: none;
padding: 0;
}
.photo-params {
width: 100%;
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24f36ad3a467713f846f7816429e646ca433eadf7132c628e34a04dcaa839757
size 4278990

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0fbece7eb21b370ed75658d5b82501bd6f49737cd5a5db4988cceecf829083b
size 4152181

View file

@ -0,0 +1,14 @@
---
title: "Temple of Hephaestus"
date: 2022-07-30T04:30:43-07:00
series: Greece
categories: Travel
resources:
- title: The Temple of Hephaestus from the Agora
src: IMG_3771.jpeg
- title: The front of the Temple of Hephaestus
src: IMG_3794.jpeg
---
This temple is the best preserved building on the site of the Ancient Agora of
Athens, {{< lang gr >}}Αρχαία Αγορά της Αθήνας{{< /lang >}}.

View file

@ -9,10 +9,8 @@
<tr>
{{ if and .Lat .Long }}
<td colspan="2" class="location">
{{ $lat := float .Lat }}{{ $latDir := cond (eq $lat 0) "" (cond (gt $lat 0) "N" "S") }}
<data class="latitude" value="{{ $lat }}">{{ .Lat | lang.FormatNumber (cond (ne $lat 0) 3 0) }}º{{ $latDir }}</data>,
{{ $long := float .Long }}{{ $longDir := cond (eq $long 0) "" (cond (gt $long 0) "E" "W") }}
<data class="longitude" value="{{ $long }}">{{ .Long | lang.FormatNumber (cond (ne $long 0) 3 0) }}º{{ $longDir }}</data>
<data class="latitude">{{ partial "photos/latitude.html" . }}</data>,
<data class="longitude">{{ partial "photos/longitude.html" . }}</data>
</td>
{{ end }}
{{ if and .Tags.PixelXDimension .Tags.PixelYDimension }}
@ -20,8 +18,8 @@
{{ $widthpx := .Tags.PixelXDimension }}
{{ $heightpx := .Tags.PixelYDimension }}
{{ if and (gt $widthpx 0) (gt $heightpx 0) }}
{{ $megapixels := div (mul $widthpx $heightpx) 1e6 }}
<data value="{{ $megapixels }}">{{ $megapixels | lang.FormatNumber 0 }} MP</data> • {{ $widthpx }} × {{ $heightpx }}
<data class="megapixels">{{ partial "photos/megapixels.html" .Tags }}</data><data class="width">{{
$widthpx }}</data> × <data class="height">{{ $heightpx }}</data>
{{ end }}
</td>
{{ end }}

View file

@ -0,0 +1,4 @@
{{ $lat := float .Lat }}
{{ $latDir := cond (eq $lat 0) "" (cond (gt $lat 0) "N" "S") }}
{{ $formattedLat := printf "%sº%s" (.Lat | lang.FormatNumber (cond (ne $lat 0) 3 0)) $latDir }}
{{ return $formattedLat }}

View file

@ -0,0 +1,4 @@
{{ $long := float .Long }}
{{ $longDir := cond (eq $long 0) "" (cond (gt $long 0) "E" "W") }}
{{ $formattedLong := printf "%sº%s" (.Long | lang.FormatNumber (cond (ne $long 0) 3 0)) $longDir }}
{{ return $formattedLong }}

View file

@ -0,0 +1,8 @@
{{ $widthpx := .PixelXDimension }}
{{ $heightpx := .PixelYDimension }}
{{ $megapixels := 0 }}
{{ if and (gt $widthpx 0) (gt $heightpx 0) }}
{{ $megapixels = div (mul $widthpx $heightpx) 1e6 }}
{{ end }}
{{ $formattedMegapixels := printf "%.0f MP" $megapixels }}
{{ return $formattedMegapixels }}

View file

@ -0,0 +1,28 @@
{{ $thumbnailResource := .Image }}
{{ $orientation := partial "images/orientation_angle.html" $thumbnailResource }}
{{ $targetWidth := 0 }}
{{ if isset . "Width" }}
{{ $targetWidth = .Width }}
{{ else }}
{{ $targetWidth = $thumbnailResource.Width }}
{{ end }}
{{ $targetHeight := 0 }}
{{ if isset . "Height" }}
{{ $targetHeight = .Height }}
{{ else }}
{{ $targetHeight = $thumbnailResource.Height }}
{{ end }}
{{ $thumbnail := false }}
{{ if not (and (eq $orientation 0)
(eq $targetWidth $thumbnailResource.Width)
(eq $targetHeight $thumbnailResource.Height)) }}
{{ $thumbnail = $thumbnailResource.Fit (printf "%dx%d r%d" $targetWidth $targetHeight (sub 360 $orientation)) }}
{{ else }}
{{ $thumbnail = $thumbnailResource }}
{{ end }}
{{ return $thumbnail }}

View file

@ -0,0 +1,51 @@
{{ $page := .Page }}
{{ $thumbnails := slice }}
{{ with $thumbnailNamesList := $page.Params "thumbnails" }}
{{/* Specify a list of thumbnails to use with the page "thumbnails" param */}}
{{ range $thumbnailNamesList }}
{{ with $page.Resources.GetMatch . }}
{{ $thumbnails = $thumbnails | append . }}
{{ else }}
{{ errorf "No image resources available for %s from thumbnails param" . }}
{{ end }}
{{ end }}
{{ else }}
{{/* Get a list of all the image resources and see if any of them specify
thumbnails for themselves. If they do, use those, otherwise use the image
resource itself. */}}
{{ range $img := $page.Resources.ByType "image" }}
{{ with $thumbnailResourceName := $img.params.thumbnail }}
{{ with $page.Resources.GetMatch $thumbnailResourceName }}
{{ $thumbnails = $thumbnails | append . }}
{{ else }}
{{ errorf "No image resources available for '%s' from thumbnail param for image resources %s"
$thumbnailResourceName
$img.Name }}
{{ end }}
{{ else }}
{{/* TODO: Look for a named image that indicates it's a thumbnail of
another resources. Something like ${imgBasename}_thumbnail.${ext}.
I don't know how easy Hugo makes filename processing, so this could
be painful. */}}
{{ $thumbnails = $thumbnails | append $img }}
{{ end }}
{{ end }}
{{ end }}
{{ if eq (len $thumbnails) 0 }}
{{ errorf "Couldn't find any thumbnails for %s" $page.Permalink }}
{{ end }}
{{ $thumbnailOptions := dict }}
{{ if isset . "Width" }}{{ $thumbnailOptions = $thumbnailOptions | merge (dict "Width" .Width) }}{{ end }}
{{ if isset . "Height" }}{{ $thumbnailOptions = $thumbnailOptions | merge (dict "Height" .Height) }}{{ end }}
{{ $processedThumbnails := slice }}
{{ range $thumbnails }}
{{ $options := $thumbnailOptions | merge "Image" . }}
{{ $processedThumbnail := partial "photos/thumbnail_for_list.html" $options }}
{{ $processedThumbnails = $processedThumbnails | append $processedThumbnail }}
{{ end }}
{{ return $processedThumbnails }}

View file

@ -5,7 +5,7 @@
{{ else }}
{{ $sectionStylesheet = printf "styles/%s.css" .Section }}
{{ end }}
{{ with resources.Get $sectionStylesheet }}
{{ with resources.Get $sectionStylesheet }}
{{ $sectionCSS = . | fingerprint "md5" }}
{{ end }}
{{ return $sectionCSS }}

View file

@ -1,4 +1,4 @@
{{- range .Resources.Match "*.css" -}}
{{- $stylesheet := . | fingerprint "md5" }}
<link rel="stylesheet" href="{{ $stylesheet.RelPermalink }}"></script>
<link rel="stylesheet" href="{{ $stylesheet.RelPermalink }}">
{{- end -}}

View file

@ -1,6 +1,11 @@
{{- $thumbnail := partial "photos/thumbnail.html" (dict "Page" . "Width" 600 "Height" 600) -}}
{{- $thumbnail = $thumbnail.Crop "600x600" -}}
{{- $altText := $thumbnail.Params.alt -}}
<a href="{{ .RelPermalink }}" title="{{ .Title }}">
<img src="{{ $thumbnail.RelPermalink }}"{{ with $altText }} alt="{{ . }}"{{ end }}>
{{- $numberOfImages := len (partial "photos/list.html" .) -}}
{{- $hasMultipleImages := gt $numberOfImages 1 -}}
<a href="{{ .RelPermalink }}" title="{{ .Title }}" data-number-of-images="{{ $numberOfImages }}">
{{ if $hasMultipleImages }}<div class="badge">{{ $numberOfImages }}</div>{{ end }}
{{ range partial "photos/thumbnails.html" (dict "Page" . "Height" 600 "Width" 600) -}}
{{- $altText := .Params.alt -}}
<img src="{{ .RelPermalink }}"{{ with $altText }} alt="{{ . }}"{{ end }} loading="lazy">
{{- end }}
</a>

View file

@ -17,3 +17,9 @@
{{ define "footer" }}
{{ partial "footer.html" . }}
{{ end }}
{{ define "scripts" }}
{{- with resources.Get "scripts/photos/list.js" | fingerprint "md5" -}}
<script src="{{ .RelPermalink }}"></script>
{{- end -}}
{{ end }}

View file

@ -12,29 +12,56 @@
{{ errorf "Missing photo from photos page %q" .Path }}
{{ end }}
{{- $firstImage := index $photos 0 -}}
{{ if eq (len $photos) 1 }}
{{- $img := index $photos 0 -}}
<figure><img src="{{ $img.RelPermalink }}"{{ with $img.Params.alt }} alt="{{ . }}"{{ end }}></figure>
{{ .Content }}
{{- if .Params.photo_details | default true -}}
{{- partial "photo_exif_table.html" $img.Exif -}}
{{- if in ($.Site.BaseURL | string) "localhost" -}}
{{- partial "development/photo_exif_table.html" $img.Exif -}}
{{- end -}}
{{- end -}}
{{ else }}
<figure>
<ul class="carousel">
<img src="{{ $firstImage.RelPermalink }}"{{ with $firstImage.Params.alt }} alt="{{ . }}"{{ end }}>
</figure>
{{ else }}
<figure class="carousel">
<div class="shadow left"></div>
<div class="shadow right"></div>
<ul>
{{- range $photos -}}
<li>{{ . }}</li>
<li data-title="{{ .Title }}"
{{- with .Exif -}}
data-latitude="{{ partial "photos/latitude.html" . }}"
data-longitude="{{ partial "photos/longitude.html" . }}"
{{- with .Tags -}}
data-make="{{ .Make }}"
data-model="{{ .Model }}"
data-iso="{{ .ISOSpeedRatings }}"
data-focal-length="{{ .FocalLengthIn35mmFilm }}"
data-f-number="{{ .FNumber }}"
data-exposure-time="{{ .ExposureTime }}"
data-megapixels="{{ partial "photos/megapixels.html" . }}"
data-width="{{ .PixelXDimension }}"
data-height="{{ .PixelYDimension }}"
{{- end -}}
{{- end -}}
>
<img src="{{ .RelPermalink }}"{{ with .Params.alt }} alt="{{ . }}"{{ end }}>
</li>
{{- end -}}
</ul>
<figcaption>
{{ $firstImage.Title }}
</figcaption>
</figure>
{{ end }}
<section id="content">
{{ .Content }}
</section>
{{- if .Params.photo_details | default true -}}
{{- partial "photo_exif_table.html" $firstImage.Exif -}}
{{- if in ($.Site.BaseURL | string) "localhost" -}}
{{- partial "development/photo_exif_table.html" $firstImage.Exif -}}
{{- end -}}
{{- end -}}
<footer>
{{ partial "footer_tags.html" . }}
</footer>
@ -43,3 +70,12 @@
{{ define "footer" }}
{{ partial "footer.html" . }}
{{ end }}
{{ define "scripts" }}
{{- $photos := partial "photos/list.html" . -}}
{{ if gt (len $photos) 1 -}}
{{- with resources.Get "scripts/photos/carousel.js" | fingerprint "md5" -}}
<script type="module" src="{{ .RelPermalink }}"></script>
{{- end -}}
{{- end }}
{{ end }}