last one maybe
@ -6,10 +6,11 @@ layout = "photography"
|
||||
+++
|
||||
|
||||
{{< gallery
|
||||
match="*.jpg|*.jpeg|*.png|*.webp|*.gif"
|
||||
match="*.webp"
|
||||
sortOrder="desc"
|
||||
rowHeight="150"
|
||||
margins="5"
|
||||
thumbnailResizeOptions="600x600 q90 Lanczos"
|
||||
previewType="blur"
|
||||
>}}
|
||||
{{< /gallery >}}
|
||||
|
462
layouts/shortcodes/gallery.html
Normal file
@ -0,0 +1,462 @@
|
||||
<!-- Force jQuery to load first -->
|
||||
<script src="/shortcode-gallery/jquery-3.7.1.min.js"></script>
|
||||
<!-- Now load the gallery scripts (these will be loaded again by the shortcode, but this ensures jQuery is present) -->
|
||||
<script src="/shortcode-gallery/lazy/jquery.lazy.min.js"></script>
|
||||
<script src="/shortcode-gallery/swipebox/js/jquery.swipebox.min.js"></script>
|
||||
<script src="/shortcode-gallery/justified_gallery/jquery.justifiedGallery.min.js"></script>
|
||||
<link rel="stylesheet" href="/shortcode-gallery/swipebox/css/swipebox.min.css">
|
||||
<link rel="stylesheet" href="/shortcode-gallery/justified_gallery/justifiedGallery.min.css">
|
||||
|
||||
<!-- Render the original gallery content -->
|
||||
{{/* The following is a fallback: you may want to copy the full upstream gallery.html here for more advanced features. */}}
|
||||
<div>
|
||||
{{ .Inner }}
|
||||
</div>
|
||||
|
||||
{{ $currentPage := . }}
|
||||
|
||||
{{ $images := slice }}
|
||||
{{ $globalMatch := .Get "globalMatch" }}
|
||||
{{ $localMatch := .Get "match" }}
|
||||
|
||||
{{ if $localMatch }}
|
||||
{{ $images = (.Page.Resources.Match $localMatch )}}
|
||||
{{ else if $globalMatch }}
|
||||
{{ $images = (resources.Match (.Get "globalMatch")) }}
|
||||
{{ else }}
|
||||
{{ $images = (.Page.Resources.ByType "image") }}
|
||||
{{ end }}
|
||||
|
||||
{{ $filterOptions := .Get "filterOptions" | default (.Site.Params.galleryFilterOptions | default "[]") }}
|
||||
{{ if not $filterOptions }}
|
||||
{{ $filterOptions = "[]" }}
|
||||
{{ end }}
|
||||
|
||||
{{ $storeSelectedFilterInUrl := .Get "storeSelectedFilterInUrl" | default (.Site.Params.storeSelectedFilterInUrl | default false) }}
|
||||
|
||||
{{ $sortOrder := .Get "sortOrder" | default (.Site.Params.gallerySortOrder | default "asc") }}
|
||||
|
||||
{{ $rowHeight := .Get "rowHeight" | default (.Site.Params.galleryRowHeight | default 150) }}
|
||||
|
||||
{{ $margins := .Get "margins" | default (.Site.Params.galleryRowMargins | default 5) }}
|
||||
|
||||
{{ $randomize := .Get "randomize" | default (.Site.Params.galleryRandomize | default false) }}
|
||||
|
||||
{{ $thumbnailResizeOptions := .Get "thumbnailResizeOptions" | default (.Site.Params.galleryThumbnailResizeOptions | default "300x150 q85 Lanczos") }}
|
||||
|
||||
{{ $imageResizeOptions := .Get "imageResizeOptions" | default .Site.Params.galleryImageResizeOptions }}
|
||||
|
||||
{{ $loadJQuery := .Get "loadJQuery" | default (.Site.Params.galleryLoadJQuery | default false) }}
|
||||
|
||||
{{ $showExif := .Get "showExif" | default (.Site.Params.galleryShowExif | default false) }}
|
||||
|
||||
{{ $swipeboxParameters := .Get "swipeboxParameters" | default (.Site.Params.gallerySwipeboxParameters | default "") }}
|
||||
|
||||
{{ $justifiedGalleryParameters := .Get "justifiedGalleryParameters" | default (.Site.Params.galleryJustifiedGalleryParameters | default "") }}
|
||||
|
||||
{{ $previewType := .Get "previewType" | default (.Site.Params.galleryPreviewType | default "blur") }}
|
||||
|
||||
{{ $embedPreview := .Get "embedPreview" | default (.Site.Params.galleryEmbedPreview | default true) }}
|
||||
|
||||
{{ $thumbnailHoverEffect := .Get "thumbnailHoverEffect" | default (.Site.Params.galleryThumbnailHoverEffect | default "none") }}
|
||||
|
||||
<!-- hugos image processing saves images at resources/_gen/images, if the property resourceDir
|
||||
is changed in hugos config.toml file the images are save <resourceDir>/_gen/images.
|
||||
Because it is not possible to access the value of resourceDir, users who change resourceDir also have to change
|
||||
[params] resourceDir. -->
|
||||
{{ $thumbnailResourceDir := printf "%s%s" (.Site.Params.resourceDir | default "resources") "/_gen/images/" }}
|
||||
|
||||
<!-- Load jquery, jquery-lazy, swipebox and justified_gallery only once per page -->
|
||||
{{ if not (.Page.Scratch.Get "galleryLoaded") }}
|
||||
{{ .Page.Scratch.Set "galleryLoaded" true }}
|
||||
<!-- Use relURL to support hugo projects that run in a subdirectory (bug #18) -->
|
||||
|
||||
<!-- Always load jQuery first to fix dependency issues -->
|
||||
<script src="{{ "shortcode-gallery/jquery-3.7.1.min.js" | relURL }}"></script>
|
||||
|
||||
{{ if not (eq $previewType "none") }}
|
||||
<script src="{{ "shortcode-gallery/lazy/jquery.lazy.min.js" | relURL }}"></script>
|
||||
{{ end }}
|
||||
|
||||
<script src="{{ "shortcode-gallery/swipebox/js/jquery.swipebox.min.js" | relURL }}"></script>
|
||||
<link rel="stylesheet" href="{{ "shortcode-gallery/swipebox/css/swipebox.min.css"| relURL }}">
|
||||
|
||||
<script src="{{ "shortcode-gallery/justified_gallery/jquery.justifiedGallery.min.js"| relURL }}"></script>
|
||||
<link rel="stylesheet" href="{{ "shortcode-gallery/justified_gallery/justifiedGallery.min.css"| relURL }}"/>
|
||||
{{ end }}
|
||||
|
||||
<style>
|
||||
{{ if (eq $thumbnailHoverEffect "enlarge") }}
|
||||
.jg-entry img {
|
||||
transition: transform .25s ease-in-out !important;
|
||||
}
|
||||
|
||||
.jg-entry img:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ if not (eq $filterOptions "[]") }}
|
||||
/* inline css from assets folder */
|
||||
{{ (resources.Get "shortcode-gallery/filterbar.sass" | toCSS).Content | safeCSS }}
|
||||
{{ end }}
|
||||
</style>
|
||||
|
||||
<!--
|
||||
Ordinal increases every time this shortcode is used in a document
|
||||
Ordinal: {{ .Ordinal}}
|
||||
-->
|
||||
{{ $galleryId := (printf "gallery-%v-%v" .Page.File.UniqueID .Ordinal)}}
|
||||
{{ $galleryWrapperId := (printf "gallery-%v-%v-wrapper" .Page.File.UniqueID .Ordinal)}}
|
||||
|
||||
<div id="{{ $galleryWrapperId }}" class="gallery-wrapper">
|
||||
<div id="{{ $galleryId }}" class="justified-gallery">
|
||||
{{ range $original := sort $images "Name" $sortOrder}}
|
||||
{{ if eq $original.ResourceType "image" }}
|
||||
|
||||
{{/* Get metadata from sidecar file, if present. Else an empty dictionary is used. */}}
|
||||
{{ $metaFileName := print $original.Name ".meta"}}
|
||||
|
||||
{{ $metadata := slice }}
|
||||
{{ if and $globalMatch (not $localMatch) }}
|
||||
{{ $metadata = resources.GetMatch ($metaFileName) }}
|
||||
{{ else }}
|
||||
{{ $metadata = $currentPage.Page.Resources.GetMatch ($metaFileName) }}
|
||||
{{ end }}
|
||||
|
||||
{{ if $metadata }}
|
||||
{{ $metadata = $metadata.Content }}
|
||||
{{ $metadata = $metadata | unmarshal }}
|
||||
{{ else }}
|
||||
{{ $metadata = dict }}
|
||||
{{ end }}
|
||||
|
||||
{{/* If the image has EXIF informations, those are merged together with the metadata from the file */}}
|
||||
{{ if in "jpeg png tiff webp" $original.MediaType.SubType }}
|
||||
{{ with $original.Exif }}
|
||||
{{ $metadata = merge .Tags $metadata }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{/* Correct orientation of the image, this is needed for thumbnails (and downscaled gallery images)
|
||||
if the EXIF informations contains an orientation value. See issue #22. */}}
|
||||
{{ $rotated := $original.Filter images.AutoOrient }}
|
||||
|
||||
<div>
|
||||
{{ $full := "" }}
|
||||
{{ if $imageResizeOptions }}
|
||||
{{/* Downscale gallery image, rotate it if needed */}}
|
||||
{{ $full = ($rotated.Fit $imageResizeOptions) }}
|
||||
{{ else }}
|
||||
{{/* No need to use the rotated here, the browser will handle care of EXIF orientation */}}
|
||||
{{ $full = $original }}
|
||||
{{ end }}
|
||||
|
||||
{{ $thumbnail := "" }}
|
||||
{{ if eq $thumbnailResizeOptions "original" }}
|
||||
{{/* If thumbnailResizeOptions is set to 'original', use the original (unmodified) image for the thumbnail */}}
|
||||
{{ $thumbnail = $original }}
|
||||
{{ else if eq $thumbnailResizeOptions "full" }}
|
||||
{{/* If thumbnailResizeOptions is set to 'full', use the same image as the lightbox for the thumbnail */}}
|
||||
{{ $thumbnail = $full }}
|
||||
{{ else }}
|
||||
{{/* Create thumbnail, rotate it if needed */}}
|
||||
{{ $thumbnail = ($rotated.Fit $thumbnailResizeOptions) }}
|
||||
{{ end }}
|
||||
|
||||
<a href="{{ $full.RelPermalink }}"
|
||||
class="galleryImg"
|
||||
{{ with $metadata }}
|
||||
{{ if .Title }}
|
||||
title="{{ .Title }}"
|
||||
{{ else if .ImageDescription }}
|
||||
title="{{ .ImageDescription }}"
|
||||
{{ end }}
|
||||
|
||||
{{ if $showExif }}
|
||||
data-description="{{ .Model }}{{ if and .Model .LensModel }} + {{ end }}{{ .LensModel }}<br/>{{ if .FocalLength }}{{ .FocalLength }}mm {{ end }}{{ if .FNumber }}f/{{ printf "%.1f" .FNumber.Float64 }} {{ end }}{{ if .ExposureTime }}{{ .ExposureTime }}sec {{ end }} {{ if .ISO }}ISO {{ .ISO }}{{ end }}"
|
||||
{{ end }}
|
||||
|
||||
{{ if not (eq $filterOptions "[]") }}
|
||||
{{/* only include fields that are currently supported by the filter mechanism (in JS at the end of the file) */}}
|
||||
data-meta="{{ (dict "Tags" .Tags "Rating" .Rating "ColorLabels" .ColorLabels "ImageDescription" .ImageDescription) | jsonify }}"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
>
|
||||
<img
|
||||
width="{{ $thumbnail.Width }}" height="{{ $thumbnail.Height }}"
|
||||
|
||||
{{ if (eq $previewType "blur") }}
|
||||
{{ $preview_b := ($thumbnail.Fit ("32x32 q70 box jpg")) }}
|
||||
style="filter: blur(25px);"
|
||||
{{ if $embedPreview }}
|
||||
src="data:image/jpeg;base64,{{ $preview_b.Content | base64Encode }}"
|
||||
{{ else }}
|
||||
src="{{ $preview_b.RelPermalink }}"
|
||||
{{ end }}
|
||||
class="lazy"
|
||||
data-src="{{ $thumbnail.RelPermalink }}"
|
||||
{{ else if (eq $previewType "color") }}
|
||||
{{ $preview_1p := ($thumbnail.Resize ("1x1 box png")) }}
|
||||
{{ if $embedPreview }}
|
||||
src="data:image/png;base64,{{ $preview_1p.Content | base64Encode }}"
|
||||
{{ else }}
|
||||
src="{{ $preview_1p.RelPermalink }}"
|
||||
{{ end }}
|
||||
class="lazy"
|
||||
data-src="{{ $thumbnail.RelPermalink }}"
|
||||
{{ else }}
|
||||
src="{{ $thumbnail.RelPermalink }}"
|
||||
{{ end }}
|
||||
|
||||
{{ with $metadata }}
|
||||
{{ if .ImageDescription }}
|
||||
alt="{{ .ImageDescription }}"
|
||||
{{ else }}
|
||||
{{ if .Title}}
|
||||
alt="{{ .Title }}"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (!("HSCGjQuery" in window)) {
|
||||
if (!window.jQuery) {
|
||||
throw new Error("jQuery is not loaded, hugo-shortcode-gallery wont work without it!");
|
||||
}
|
||||
window.HSCGjQuery = window.jQuery.noConflict(true);
|
||||
}
|
||||
|
||||
// Isolate all our variables from other scripts.
|
||||
// See: https://www.nicoespeon.com/en/2013/05/properly-isolate-variables-in-javascript/
|
||||
// We also expect to get jQuery as a parameter, so that if a second instance of jQuery is loaded
|
||||
// after this script is executed (e.g. by a Hugo theme), we will still be referencing the previous
|
||||
// version of jQuery that has all our plugins loaded.
|
||||
;(function($) {
|
||||
|
||||
$( document ).ready(() => {
|
||||
const gallery = $("#{{ $galleryId }}");
|
||||
{{ $lastRowJustification := .Get "lastRow" | default (.Site.Params.galleryLastRow | default "justify") }}
|
||||
|
||||
// the instance of swipebox, it will be set once justifiedGallery is initialized
|
||||
let swipeboxInstance = null;
|
||||
|
||||
// before the gallery initialization the listener has to be added
|
||||
// else we can get a race condition and the listener is never called
|
||||
gallery.on('jg.complete', () => {
|
||||
{{ if or (eq $previewType "blur") (eq $previewType "color") }}
|
||||
// if there is already some low resolution image data loaded, then we will wait for loading´
|
||||
// the hi-res until the justified gallery has done the layout
|
||||
$(() => {
|
||||
$('.lazy').Lazy({
|
||||
visibleOnly: true,
|
||||
afterLoad: element => element.css({filter: "none", transition: "filter 1.0s ease-in-out"})
|
||||
});
|
||||
});
|
||||
{{ end }}
|
||||
|
||||
swipeboxInstance = $('.galleryImg').swipebox(
|
||||
$.extend({},
|
||||
{ {{ $swipeboxParameters | safeJS }} }
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// initialize the justified gallery
|
||||
gallery.justifiedGallery($.extend(
|
||||
{
|
||||
rowHeight : {{ $rowHeight }},
|
||||
margins : {{ $margins }},
|
||||
border : 0,
|
||||
randomize : {{ $randomize }},
|
||||
waitThumbnailsLoad : false,
|
||||
lastRow : {{ $lastRowJustification }},
|
||||
captions : false,
|
||||
// if there is at least one filter option
|
||||
{{ if not (eq $filterOptions "[]") }}
|
||||
// we first show no images at all
|
||||
// till the code way below selects a filter and applies it
|
||||
// this prevent creating the layout twice
|
||||
filter : () => false
|
||||
{{ end }}
|
||||
},
|
||||
{ {{ $justifiedGalleryParameters | safeJS }} }
|
||||
));
|
||||
|
||||
// only include JS code for filter options if there at least one filter option
|
||||
{{ if not (eq $filterOptions "[]") }}
|
||||
|
||||
// this function can be used to create a function that can be used by justifiedGallery
|
||||
// for filtering images by their metadata
|
||||
function createMetadataFilter(filterFunction) {
|
||||
return (entry, index, array) => {
|
||||
let meta = $(entry).find("a").attr("data-meta");
|
||||
meta = meta ? JSON.parse(meta) : {};
|
||||
|
||||
let include = filterFunction(meta);
|
||||
|
||||
// only those images visible in justified gallery should be displayed
|
||||
// in swipebox (only <a> with class galleryImg are displayed in swipebox)
|
||||
$(entry).find("a").toggleClass("galleryImg", include);
|
||||
|
||||
return include;
|
||||
}
|
||||
}
|
||||
|
||||
// this function returns a function that can be used by justifiedGallery
|
||||
// for filtering images by their tags
|
||||
function createTagFilter(tagsRegexString) {
|
||||
const tagsRegex = RegExp(tagsRegexString);
|
||||
return createMetadataFilter(meta => {
|
||||
let tags = meta.Tags;
|
||||
tags = tags ? tags : [];
|
||||
return tags.some(tag => tagsRegex.test(tag));
|
||||
});
|
||||
};
|
||||
|
||||
// this function returns a function that can be used by justifiedGallery
|
||||
// for filtering images by their description
|
||||
function createImageDescriptionFilter(descriptionRegexString) {
|
||||
const descriptionRegex = RegExp(descriptionRegexString);
|
||||
return createMetadataFilter(meta => {
|
||||
let imageDescription = meta.ImageDescription;
|
||||
return imageDescription !== null && descriptionRegex.test(imageDescription);
|
||||
});
|
||||
};
|
||||
|
||||
// this function returns a function that can be used by justifiedGallery
|
||||
// for filtering images by their star rating
|
||||
function createRatingFilter(min, max) {
|
||||
return createMetadataFilter(meta => {
|
||||
let rating = meta.Rating;
|
||||
if(rating === null){
|
||||
rating = -1;
|
||||
}
|
||||
return rating >= min && rating <= max;
|
||||
});
|
||||
};
|
||||
|
||||
// this function returns a function that can be used by justifiedGallery
|
||||
// for filtering images by their color labels
|
||||
function createColorLabelFilter(color) {
|
||||
color = color.charAt(0).toLowerCase()
|
||||
return createMetadataFilter(meta => {
|
||||
let colors = meta.ColorLabels;
|
||||
return colors && colors.includes(color);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const filterOptions = {{ $filterOptions | safeJS }};
|
||||
|
||||
// insert a div for inserting filter buttons before the gallery
|
||||
const filterBar = $("<div class='justified-gallery-filterbar'/>");
|
||||
gallery.before(filterBar);
|
||||
|
||||
var wrapper = document.getElementById("{{ $galleryWrapperId }}");
|
||||
|
||||
// inline svg icons
|
||||
const expandIcon = '{{ (resources.Get "shortcode-gallery/font-awesome/expand-alt-solid.svg").Content | safeHTML }}';
|
||||
const compressIcon = '{{ (resources.Get "shortcode-gallery/font-awesome/compress-alt-solid.svg").Content | safeHTML }}';
|
||||
|
||||
function setFulltab(activate) {
|
||||
if(activate == wrapper.classList.contains("fulltab")){
|
||||
return; // nothing to do, we are already in the right state
|
||||
}
|
||||
|
||||
wrapper.classList.toggle("fulltab");
|
||||
gallery.justifiedGallery({
|
||||
rowHeight : {{ $rowHeight }} * (activate ? 1.5 : 1.0),
|
||||
lastRow: (activate ? "nojustify": {{ $lastRowJustification }}),
|
||||
// force justifiedGallery to refresh
|
||||
refreshTime: 0,
|
||||
});
|
||||
// force justifiedGallery to refresh
|
||||
gallery.data('jg.controller').startImgAnalyzer();
|
||||
fullTabButton.html(wrapper.classList.contains("fulltab") ? compressIcon : expandIcon)
|
||||
};
|
||||
|
||||
const fullTabButton = $("<button/>");
|
||||
fullTabButton.html(expandIcon);
|
||||
fullTabButton.click(() => setFulltab(!wrapper.classList.contains("fulltab")));
|
||||
filterBar.append(fullTabButton);
|
||||
$(document).keyup(e => {
|
||||
// when ESC is pressed
|
||||
if (e.keyCode === 27){
|
||||
setFulltab(false);
|
||||
}
|
||||
});
|
||||
|
||||
function activateFilterButton(filterButton) {
|
||||
// activate associated filter
|
||||
gallery.justifiedGallery({filter : filterButton.filter});
|
||||
// remove select class from all other selected buttons
|
||||
filterBar.find('.selected').removeClass('selected');
|
||||
filterButton.addClass("selected");
|
||||
};
|
||||
|
||||
// check if the url contains an instruction to apply a specific filter
|
||||
// eg. example.com/images/#gallery-filter=Birds
|
||||
const params = new URLSearchParams(location.hash.replace(/^\#/,""));
|
||||
let activeFilter = params.get('gallery-filter');
|
||||
if (!activeFilter) {
|
||||
// default to first filter
|
||||
activeFilter = filterOptions[0].label;
|
||||
}
|
||||
|
||||
// create a button for each filter entry
|
||||
filterOptions.forEach(filterConfig => {
|
||||
let filter; // create a filter function based on the available attributes of filterConfig
|
||||
if(filterConfig.tags) {
|
||||
filter = createTagFilter(filterConfig.tags);
|
||||
} else if(filterConfig.rating){
|
||||
if(filterConfig.rating.includes("-")){
|
||||
minMax = filterConfig.rating.split("-"); // e.g. "3-5"
|
||||
} else {
|
||||
minMax = [filterConfig.rating, filterConfig.rating]; // e.g. "4"
|
||||
}
|
||||
filter = createRatingFilter(parseInt(minMax[0]), parseInt(minMax[1]));
|
||||
} else if(filterConfig.color_label){
|
||||
filter = createColorLabelFilter(filterConfig.color_label);
|
||||
} else if(filterConfig.description){
|
||||
filter = createImageDescriptionFilter(filterConfig.description);
|
||||
} else {
|
||||
// default to always true filter
|
||||
filter = createMetadataFilter(meta => true);
|
||||
}
|
||||
|
||||
const filterButton = $("<button/>");
|
||||
filterButton.text(filterConfig.label);
|
||||
filterButton.filter = filter;
|
||||
filterButton.click(() => {
|
||||
activateFilterButton(filterButton);
|
||||
|
||||
{{ if $storeSelectedFilterInUrl }}
|
||||
// save applied filter in browser url
|
||||
const params = new URLSearchParams(location.hash.replace(/^\#/,""));
|
||||
params.set('gallery-filter', filterConfig.label);
|
||||
window.history.replaceState("", "", location.pathname + location.search + "#" + params.toString());
|
||||
{{ end }}
|
||||
});
|
||||
filterBar.append(filterButton);
|
||||
|
||||
if(filterConfig.label.toLowerCase() === activeFilter.toLowerCase()) {
|
||||
activateFilterButton(filterButton);
|
||||
}
|
||||
});
|
||||
{{ end }}
|
||||
});
|
||||
|
||||
// End of our variable-isolating and self-executing anonymous function.
|
||||
// We call it with the one version of jQuery that was used to load our plugins.
|
||||
// See: http://blog.nemikor.com/2009/10/03/using-multiple-versions-of-jquery/
|
||||
})(window.HSCGjQuery)
|
||||
</script>
|
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 52 KiB |
@ -1,8 +1,256 @@
|
||||
<!doctype html><html lang=en><head><meta charset=UTF-8><meta http-equiv=X-UA-Compatible content="ie=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=author content><meta name=description content><meta name=keywords content><meta name=robots content="noodp"><meta name=theme-color content><link rel=canonical href=https://connerchu.com/tidbits/><title>Tidbits :: Conner Chu
|
||||
</title><link rel=stylesheet href=/main.min.b35466370c2f708ecabeb5eacc8b59840bcf4578d326a600a15107f3e9f1dc4e.css integrity="sha256-s1RmNwwvcI7KvrXqzItZhAvPRXjTJqYAoVEH8+nx3E4=" crossorigin=anonymous><link rel=apple-touch-icon sizes=180x180 href=/apple-touch-icon.png><link rel=icon type=image/png sizes=32x32 href=/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=/favicon-16x16.png><link rel=manifest href=/site.webmanifest><link rel=mask-icon href=/safari-pinned-tab.svg color><link rel="shortcut icon" href=/favicon.ico><meta name=msapplication-TileColor content><meta itemprop=name content="Tidbits"><meta itemprop=datePublished content="2024-09-14T23:30:59-07:00"><meta itemprop=dateModified content="2024-09-14T23:30:59-07:00"><meta name=twitter:card content="summary"><meta name=twitter:title content="Tidbits"><link rel=alternate type=application/rss+xml href=https://connerchu.com/tidbits/index.xml title="Conner Chu"></head><body><div class=container><header class=header><span class=header__inner><a href=/ style=text-decoration:none><div class=logo><span class=logo__mark></span><span class=logo__text>Home</span>
|
||||
<span class=logo__cursor style=visibility:hidden></span></div></a><span class=header__right><nav class=menu><ul class=menu__inner><li><a href=/photography/>Photography</a></li><li><a href=/research/>Research</a></li><li><a href=/resume/>Resume</a></li><li><a href=/tidbits/>Tidbits</a></li></ul></nav><span class=menu-trigger><svg viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg></span></span></span></header><div class=content><main class=posts><h1>Tidbits</h1><div class=posts-group><div class=post-year>2024</div><ul class=posts-list><li class=post-item><a href=https://connerchu.com/tidbits/cga/ class=post-item-inner><span class=post-title>California Great America</span>
|
||||
<span class=post-day>Sep 14</span></a></li><li class=post-item><a href=https://connerchu.com/tidbits/svd/ class=post-item-inner><span class=post-title>Singular Value Decomposition</span>
|
||||
<span class=post-day>Jul 29</span></a></li><li class=post-item><a href=https://connerchu.com/tidbits/sfft/ class=post-item-inner><span class=post-title>Six Flags Fiesta Texas</span>
|
||||
<span class=post-day>Jul 16</span></a></li></ul></div><div class=posts-group><div class=post-year>2023</div><ul class=posts-list><li class=post-item><a href=https://connerchu.com/tidbits/sfmm/ class=post-item-inner><span class=post-title>Six Flags Magic Mountain</span>
|
||||
<span class=post-day>Dec 15</span></a></li></ul></div><div class=posts-group><div class=post-year>2022</div><ul class=posts-list><li class=post-item><a href=https://connerchu.com/tidbits/airplane/ class=post-item-inner><span class=post-title>Earbuds and Airplane Toilets</span>
|
||||
<span class=post-day>Aug 3</span></a></li></ul></div><div class=pagination><div class=pagination__buttons></div></div></main></div><footer class=footer><div class=footer__inner><div class=footer__content><span>© Copyright 2025</span></div></div><div class=footer__inner><div class=footer__content><span>Powered by Hugo, Djordje, and Matze</span></div></div></footer></div><script type=text/javascript src=/bundle.min.e89fda0f29b95d33f6f4224dd9e5cf69d84aff3818be2b0d73e731689cc374261b016d17d46f8381962fb4a1577ba3017b1f23509d894f6e66431f988c00889e.js integrity="sha512-6J/aDym5XTP29CJN2eXPadhK/zgYvisNc+cxaJzDdCYbAW0X1G+DgZYvtKFXe6MBex8jUJ2JT25mQx+YjACIng=="></script><script>window.si=window.si||function(){(window.siq=window.siq||[]).push(arguments)}</script><script defer src=/_vercel/speed-insights/script.js></script><script>window.va=window.va||function(){(window.vaq=window.vaq||[]).push(arguments)}</script><script defer src=/_vercel/insights/script.js></script></body></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head><script src="/livereload.js?mindelay=10&v=2&port=44669&path=livereload" data-no-instant defer></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="author" content="">
|
||||
<meta name="description" content="" />
|
||||
<meta name="keywords" content="" />
|
||||
<meta name="robots" content="noodp" />
|
||||
<meta name="theme-color" content="" />
|
||||
<link rel="canonical" href="http://localhost:44669/tidbits/" />
|
||||
|
||||
|
||||
<title>
|
||||
|
||||
Tidbits :: Conner Chu
|
||||
|
||||
</title>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/main.min.b35466370c2f708ecabeb5eacc8b59840bcf4578d326a600a15107f3e9f1dc4e.css" integrity="sha256-s1RmNwwvcI7KvrXqzItZhAvPRXjTJqYAoVEH8+nx3E4=" crossorigin="anonymous">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="">
|
||||
<link rel="shortcut icon" href="/favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="">
|
||||
|
||||
|
||||
|
||||
<meta itemprop="name" content="Tidbits">
|
||||
<meta itemprop="datePublished" content="2024-09-14T23:30:59-07:00">
|
||||
<meta itemprop="dateModified" content="2024-09-14T23:30:59-07:00">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Tidbits">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" href="http://localhost:44669/tidbits/index.xml" title="Conner Chu" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<span class="header__inner">
|
||||
<a href="/" style="text-decoration: none;">
|
||||
<div class="logo">
|
||||
|
||||
<span class="logo__mark"> </span>
|
||||
<span class="logo__text ">
|
||||
Home</span>
|
||||
<span class="logo__cursor" style=
|
||||
"visibility:hidden;
|
||||
|
||||
">
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
<span class="header__right">
|
||||
<nav class="menu">
|
||||
<ul class="menu__inner"><li><a href="/photography/">Photography</a></li><li><a href="/research/">Research</a></li><li><a href="/resume/">Resume</a></li><li><a href="/tidbits/">Tidbits</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<span class="menu-trigger">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
|
||||
|
||||
<main class="posts">
|
||||
<h1>Tidbits</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="posts-group">
|
||||
<div class="post-year">2024</div>
|
||||
|
||||
<ul class="posts-list">
|
||||
|
||||
<li class="post-item">
|
||||
<a href="http://localhost:44669/tidbits/cga/" class="post-item-inner">
|
||||
<span class="post-title">California Great America</span>
|
||||
<span class="post-day">
|
||||
|
||||
Sep 14
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="post-item">
|
||||
<a href="http://localhost:44669/tidbits/svd/" class="post-item-inner">
|
||||
<span class="post-title">Singular Value Decomposition</span>
|
||||
<span class="post-day">
|
||||
|
||||
Jul 29
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="post-item">
|
||||
<a href="http://localhost:44669/tidbits/sfft/" class="post-item-inner">
|
||||
<span class="post-title">Six Flags Fiesta Texas</span>
|
||||
<span class="post-day">
|
||||
|
||||
Jul 16
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="posts-group">
|
||||
<div class="post-year">2023</div>
|
||||
|
||||
<ul class="posts-list">
|
||||
|
||||
<li class="post-item">
|
||||
<a href="http://localhost:44669/tidbits/sfmm/" class="post-item-inner">
|
||||
<span class="post-title">Six Flags Magic Mountain</span>
|
||||
<span class="post-day">
|
||||
|
||||
Dec 15
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="posts-group">
|
||||
<div class="post-year">2022</div>
|
||||
|
||||
<ul class="posts-list">
|
||||
|
||||
<li class="post-item">
|
||||
<a href="http://localhost:44669/tidbits/airplane/" class="post-item-inner">
|
||||
<span class="post-title">Earbuds and Airplane Toilets</span>
|
||||
<span class="post-day">
|
||||
|
||||
Aug 3
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="pagination">
|
||||
<div class="pagination__buttons">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<footer class="footer">
|
||||
|
||||
<div class="footer__inner">
|
||||
<div class="footer__content">
|
||||
|
||||
|
||||
<span>© Copyright 2025</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer__inner">
|
||||
<div class="footer__content">
|
||||
<span>Powered by Hugo, Djordje, and Matze</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="/bundle.min.e89fda0f29b95d33f6f4224dd9e5cf69d84aff3818be2b0d73e731689cc374261b016d17d46f8381962fb4a1577ba3017b1f23509d894f6e66431f988c00889e.js" integrity="sha512-6J/aDym5XTP29CJN2eXPadhK/zgYvisNc+cxaJzDdCYbAW0X1G+DgZYvtKFXe6MBex8jUJ2JT25mQx+YjACIng=="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.si = window.si || function () { (window.siq = window.siq || []).push(arguments); };
|
||||
</script>
|
||||
<script defer src="/_vercel/speed-insights/script.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };
|
||||
</script>
|
||||
<script defer src="/_vercel/insights/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,2 +1,10 @@
|
||||
<!doctype html><html lang=en-us><head><title>https://connerchu.com/tidbits/</title>
|
||||
<link rel=canonical href=https://connerchu.com/tidbits/><meta name=robots content="noindex"><meta charset=utf-8><meta http-equiv=refresh content="0; url=https://connerchu.com/tidbits/"></head></html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title>http://localhost:44669/tidbits/</title>
|
||||
<link rel="canonical" href="http://localhost:44669/tidbits/">
|
||||
<meta name="robots" content="noindex">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="refresh" content="0; url=http://localhost:44669/tidbits/">
|
||||
</head>
|
||||
</html>
|
||||
|