initial commit

This commit is contained in:
2024-11-13 22:26:45 +01:00
commit fd466fc511
156 changed files with 13223 additions and 0 deletions

3
themes/gallery/.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: nicokaiser
ko_fi: nicokaiser
liberapay: nicokaiser

View File

@@ -0,0 +1,62 @@
name: Deploy to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.124.0
steps:
- name: Install Hugo
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v4
- name: Cache example images
id: cache-images
uses: actions/cache@v4
with:
path: ./exampleSite/content
key: images-${{ hashFiles('exampleSite/content/**') }}
- name: Download example images
if: steps.cache-images.outputs.cache-hit != 'true'
working-directory: ./exampleSite
run: ./pull-images.sh
- name: Setup Pages
id: pages
uses: actions/configure-pages@v4
- name: Build with Hugo
working-directory: ./exampleSite
env:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
run: hugo --gc --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./exampleSite/public
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

4
themes/gallery/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
package-lock.json
.hugo_build.lock
exampleSite/assets/jsconfig.json

View File

@@ -0,0 +1,6 @@
assets/jsconfig.json
assets/js/justified-layout/
assets/js/photoswipe/
assets/css/photoswipe/
exampleSite/resources/
exampleSite/public/

View File

@@ -0,0 +1,9 @@
{
"extends": ["stylelint-config-standard-scss"],
"rules": {
"alpha-value-notation": "number",
"color-function-notation": "legacy",
"media-feature-range-notation": "prefix",
"property-no-vendor-prefix": null
}
}

21
themes/gallery/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023-2024 Nico Kaiser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

250
themes/gallery/README.md Normal file
View File

@@ -0,0 +1,250 @@
# Hugo Gallery Theme
A very simple and opinionated photo gallery theme for Hugo.
- [Demo](https://nicokaiser.github.io/hugo-theme-gallery/)
- [Example site source](https://github.com/nicokaiser/hugo-theme-gallery/tree/main/exampleSite)
---
![Screenshot](https://github.com/nicokaiser/hugo-theme-gallery/raw/main/images/screenshot.jpg)
---
## Features
- Responsive design
- Dark color scheme (can be set per page)
- Private albums
- Justified album views with [Flickr's Justified Layout](https://github.com/flickr/justified-layout)
- Lightbox with [PhotoSwipe](https://photoswipe.com/)
- SEO with Open Graph tags
- Automatic (or manual) selection of feature/cover images
## Installation
This theme requires Hugo Extended >= 0.121.2. Dependencies are bundled, so no Node.js/NPM and PostCSS is needed.
### As a Hugo Module
Requires the Go binary installed.
```sh
hugo mod init github.com/<your_user>/<your_project>
```
Then add the theme to your `hugo.toml`:
```toml
[module]
[[module.imports]]
path = "github.com/nicokaiser/hugo-theme-gallery/v4"
```
### As Git Submodule
```sh
git submodule add --depth=1 https://github.com/nicokaiser/hugo-theme-gallery.git themes/gallery
```
## Usage
Page bundles which contain at least one image are listed as album or gallery:
```plain
content/
├── _index.md
├── about.md <-- not listed in album list
├── animals/
│ ├── _index.md
│ ├── cats/
│ | ├── index.md
│ | ├── cat1.jpg
│ | └── feature.jpg <-- album thumbnail
│ ├── dogs/
│ | ├── index.md
│ | ├── dog1.jpg <-- album thumbnail
│ | └── dog2.jpg
│ └── feature.jpg
├── bridge.jpg <-- site thumbnail (OpenGraph, etc.)
└── nature/
├── index.md <-- contains `featured_image: images/tree.jpg`
├── images/
| └── tree.jpg <-- album thumbnail
├── nature1.jpg
└── nature2.jpg
```
- `/about.md` is not a Page Bundle and does not have image resources. It is not displayed in the album list.
- `/nature` is a Leaf Bundle (has `index.md` and no children) => displayed as gallery (`single` layout).
- `/animals` is a Branch Bundle (has `_index.md` and has children) => displayed as album list (`list` layout).
- The image resource with `*feature*` in its name or the first image found is used as thumbnail image for album lists.
- Albums without an image are not shown.
### Front matter
- `title` -- title of the album, shown in the album list and on the album page.
- `date` -- album date, used for sorting (newest first).
- `description` -- description shown on the album page.
- `featured_image` -- name of the image file used for the album thumbnail. If not set, the first image which contains `feature` in its filename is used, otherwise the first image in the album.
- `weight` -- can be used to adjust sort order.
- `private` -- if set to `true`, this album is not shown in the album overview and is excluded from RSS feeds.
- `featured` -- if set to `true`, this album is featured on the homepage (even if private).
- `sort_by` -- property used for sorting images in an album. Default is `Name` (filename), but can also be `Date`.
- `sort_order` -- sort order. Default is `asc`.
- `params.theme` -- color theme for this page. Defaults to `defaultTheme` from configuration.
### Album Cover / Featured Image
By default, the cover image of an album is the first image in its folder. To select a specific image (which must be part of the album), use the `featured_image` frontmatter:
```plain
---
featured_image: img_1234.jpg
title: Cats
---
```
### Image Metadata
Image titles for the lightbox view are either taken from the `ImageDescription` EXIF tag, or the `title` in the resource metadata.
EXIF tags can be written using software like Adobe Lightroom or by using command line tools like exiftool:
```sh
exiftool -ImageDescription="A closeup of a gray cat's face" cat-4.jpg
```
Alternatively, the image title can be set in the front matter:
```plain
---
date: 2024-02-18T14:12:44+0100
title: Cats
resources:
- src: cat-1.jpg
title: Brown tabby cat on white stairs
params:
date: 2024-02-18T13:04:30+0100
- src: cat-4.jpg
title: A closeup of a gray cat's face
---
```
### Categories
If you use categories in your albums, the homepage displays a list of categories.
Make sure `term` is not included in `disabledKinds` in the site config.
content/dogs/index.md:
```plain
---
date: 2023-01-12
featured_image: dogs-title-image.jpg
title: Dogs
categories: ["animals", "nature"]
---
```
Categories can also have custom titles and descriptions (by default, the "animals" category will have "Animals" as title and no description). Just create a `content/categories/<category>/_index.md`:
content/categories/animals/\_index.md:
```plain
---
title: Cute Animals
description: This is the description text of the "animals" category.
---
```
#### List of Categories
To enable a list of categories, each category must at least have an image in the `content/categores/<category>/` folder. Also, `taxonomy` must _not_ be included in the `disableKinds` in the site config.
Then, `/categories` displays a list of categories, with their featured image.
#### Other Taxonomies
You can also use other taxonomies like `series`. Note that only `categories` and `tags` are enabled by Hugo's default settings. Using `series` as additional taxonomy is left as an exercise for the reader.
### Featured Content on the Homepage
Albums (and als taxonomy pages like categories) can be marked as "featured":
```plain
---
title: Featured Album
featured: true
---
```
When used in combination with `private: true` this album is only shown as featured album on the homepage, and not in any album list.
Note that also categories or any other taxonomy term can be marked as featured, so you can feature a whole category, series, etc.
By default, the homepage displays
- the site title,
- links to all categories (if categories are enabled and used)
- the most recent featured content (even if private)
- all non-private top-level albums
This can easily be adjusted by using a local version of `layouts/_default/home.html`.
### Related Content
If related content is available for your site (e.g. when keywords or tags are used), related albums are shown below each gallery. Read more about this in the [Hugo Docs](https://gohugo.io/content-management/related/#configure-related-content).
Here is an example section in `config/_default/hugo.toml` to enable related content:
```toml
[related]
includeNewer = true
threshold = 10
toLower = false
[[related.indices]]
applyFilter = false
cardinalityThreshold = 0
name = 'categories'
pattern = ''
toLower = false
type = 'basic'
weight = 10
[[related.indices]]
applyFilter = false
cardinalityThreshold = 0
name = 'keywords'
pattern = ''
toLower = false
type = 'basic'
weight = 50
```
### Social Icons
Use the `socialIcons` configuration key to add social icons on the bottom of each page:
```toml
[params]
...
[params.socialIcons]
facebook = "https://www.facebook.com/"
instagram = "https://www.instagram.com/"
github = "https://github.com/nicokaiser/hugo-theme-gallery/"
youtube = "https://www.youtube.com/"
email = "mailto:user@example.com"
linkedin = "https://linkedin.com/"
```
### Custom CSS
CSS is generated with Hugo Pipes, so you can add additional CSS in `assets/css/custom.css` (see example in `exampleSite`).
### Custom JavaScript
You can add additional JavaScript in `assets/js/custom.js`.
## Author
- [Nico Kaiser](https://kaiser.me/)

View File

@@ -0,0 +1,43 @@
:root {
color-scheme: light dark;
--surface-1-light: #fff;
--surface-2-light: #e5e5e5;
--text-1-light: #0a0a0a;
--text-2-light: #737373;
--surface-1-dark: #171717;
--surface-2-dark: #404040;
--text-1-dark: #fafafa;
--text-2-dark: #a3a3a3;
--surface-1: var(--surface-1-light);
--surface-2: var(--surface-2-light);
--text-1: var(--text-1-light);
--text-2: var(--text-2-light);
}
@media (prefers-color-scheme: dark) {
:root {
--surface-1: var(--surface-1-dark);
--surface-2: var(--surface-2-dark);
--text-1: var(--text-1-dark);
--text-2: var(--text-2-dark);
}
}
html.light {
color-scheme: light;
--surface-1: var(--surface-1-light);
--surface-2: var(--surface-2-light);
--text-1: var(--text-1-light);
--text-2: var(--text-2-light);
}
html.dark {
color-scheme: dark;
--surface-1: var(--surface-1-dark);
--surface-2: var(--surface-2-dark);
--text-1: var(--text-1-dark);
--text-2: var(--text-2-dark);
}

View File

@@ -0,0 +1,99 @@
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
}
html {
line-height: 1.5;
font-family: ui-sans-serif, system-ui, sans-serif;
-webkit-text-size-adjust: 100%;
-moz-tab-size: 4;
tab-size: 4;
}
body {
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: inherit;
font-size: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
b,
strong {
font-weight: bolder;
}
button,
input {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
color: inherit;
margin: 0;
padding: 0;
}
button,
select {
text-transform: none;
}
button {
-webkit-appearance: button;
background-image: none;
background-color: transparent;
cursor: pointer;
}
:disabled {
cursor: default;
}
:-moz-focusring {
outline: 1px dotted ButtonText;
}
h1,
h2,
h3,
figure,
p {
margin: 0;
}
ul,
menu {
margin: 0;
padding: 0;
list-style: none;
}
img,
svg,
video {
display: block;
vertical-align: middle;
}
img,
video {
max-width: 100%;
height: auto;
}

View File

@@ -0,0 +1,428 @@
html {
height: 100%;
}
body {
display: flex;
flex-direction: column;
width: 100%;
min-height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--surface-1);
color: var(--text-1);
}
body > header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
width: 100%;
min-height: 4rem;
ul {
display: inline-flex;
}
.btn {
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 0.5rem;
padding-right: 1rem;
padding-left: 1rem;
height: 3rem;
font-weight: 600;
font-size: 1.25rem;
user-select: none;
}
.btn-square {
padding: 0;
width: 3rem;
}
}
body > menu {
margin: 3rem auto 4rem;
padding-right: 1.5rem;
padding-left: 1.5rem;
width: 100%;
max-width: 768px;
color: var(--text-2);
font-weight: 600;
font-size: 1.125rem;
line-height: 1.75rem;
user-select: none;
text-align: center;
a {
display: block;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
&:hover,
&[aria-current="true"] {
color: var(--text-1);
}
}
}
body > main {
flex-grow: 1;
width: 100%;
height: 100%;
}
main > section {
margin: 3rem auto 4rem;
padding-right: 1.5rem;
padding-left: 1.5rem;
}
body > footer {
margin-right: auto;
margin-left: auto;
color: var(--text-2);
font-size: 0.875rem;
line-height: 1.25rem;
section:last-of-type {
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: 1rem;
padding: 2.5rem;
a:hover {
text-decoration-line: underline;
}
}
}
hgroup {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
margin: 3rem auto 4rem;
padding-right: 1.5rem;
padding-left: 1.5rem;
max-width: 1024px;
text-align: center;
h1 {
font-weight: 700;
font-size: 1.875rem;
line-height: 2.25rem;
}
h2 {
font-weight: 700;
font-size: 1.5rem;
line-height: 2rem;
}
p {
color: var(--text-2);
text-wrap: balance;
}
@media (min-width: 640px) {
width: 83.3333%;
}
@media (min-width: 768px) {
h1 {
font-size: 2.25rem;
line-height: 2.5rem;
}
h2 {
font-size: 1.875rem;
line-height: 2.25rem;
}
p {
font-size: 1.125rem;
line-height: 1.75rem;
}
}
}
section.galleries {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 2rem 1.5rem;
max-width: 1280px;
@media (min-width: 640px) {
grid-template-columns: repeat(2, minmax(0, 1fr));
row-gap: 3rem;
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
section.gallery {
padding-right: 0;
padding-left: 0;
max-width: 1536px;
@media (min-width: 640px) {
padding-right: 1.5rem /* 24px */;
padding-left: 1.5rem /* 24px */;
}
}
.prose {
max-width: 768px;
color: var(--text-1);
font-size: 1rem;
line-height: 1.75;
a {
color: var(--text-1);
font-weight: 500;
text-decoration: underline;
}
p {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
img {
margin-top: 2em;
margin-bottom: 2em;
}
h3 {
margin-top: 1.6em;
margin-bottom: 0.6em;
color: var(--text-1);
font-weight: 600;
font-size: 1.25em;
line-height: 1.6;
}
ul {
margin-top: 1.25em;
margin-bottom: 1.25em;
padding-left: 1.625em;
list-style-type: disc;
& > li {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding-left: 0.375em;
}
li::marker {
color: var(--text-2);
font-variant-numeric: tabular-nums;
unicode-bidi: isolate;
text-align: start !important;
text-align-last: start !important;
text-indent: 0 !important;
text-transform: none;
}
}
h3 + * {
margin-top: 0;
}
figure {
margin-top: 2em;
margin-bottom: 2em;
}
figure > * {
margin-top: 0;
margin-bottom: 0;
}
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
.card {
display: flex;
flex-direction: column;
border-radius: 1rem;
& > figure {
aspect-ratio: 3/2;
width: 100%;
border-radius: 1rem;
}
& > img, & figure > img {
transition-duration: 150ms;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
border-radius: 1rem;
aspect-ratio: 3/2;
width: 100%;
overflow: hidden;
object-fit: cover;
&:hover {
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -4px rgba(0, 0, 0, 0.1);
}
}
& > div {
display: flex;
flex: 1 1 auto;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
& > h2 {
font-weight: 600;
font-size: 1.125rem;
line-height: 1.375;
}
& > p {
color: var(--text-2);
font-size: 0.875rem;
line-height: 1.25rem;
}
}
}
.gallery-item {
cursor: zoom-in;
}
.hidden {
display: none;
}
.group[aria-expanded="true"] {
.group-aria-expanded\:block {
display: block;
}
.group-aria-expanded\:hidden {
display: none;
}
}
section.social-icons {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
margin-top: 2rem;
padding-right: 1.5rem;
padding-left: 1.5rem;
}
section.featured {
margin: 3rem auto 4rem;
padding-right: 1.5rem;
padding-left: 1.5rem;
max-width: 1280px;
color: var(--text-1-dark);
}
.featured-card {
display: flex;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
border-radius: 1rem;
background-position: center;
background-size: cover;
aspect-ratio: 1 / 1;
width: 100%;
overflow: hidden;
transition-duration: 150ms;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@media (min-width: 640px) {
aspect-ratio: 16/9;
}
& > div {
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 1rem;
background-image: linear-gradient(to top, RGB(0 0 0 / 0.8) 10%, transparent 50%);
padding: 1.5rem;
width: 100%;
height: 100%;
& > h2 {
font-weight: 700;
font-size: 1.5rem;
line-height: 1.25;
@media (min-width: 768px) {
font-size: 1.875rem;
line-height: 2.25rem;
}
}
& > p {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
&:hover {
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -4px rgba(0, 0, 0, 0.1);
}
}
nav.categories {
padding-right: 1.5rem;
padding-left: 1.5rem;
margin-top: 2rem;
& > ul {
display: flex;
flex-direction: row;
justify-content: center;
gap: 0.75rem;
flex-wrap: wrap;
li {
max-width: 100%;
& > a {
display: block;
padding: 0.5rem 0.75rem;
border-radius: 9999px;
border: 1px solid var(--text-2);
font-size: 0.875rem;
line-height: 1.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media (min-width: 640px) {
padding: 0.75rem 1rem;
font-size: 1rem;
}
}
}
}
}

View File

@@ -0,0 +1 @@
/* custom.css */

View File

@@ -0,0 +1,20 @@
@import "normalize";
@import "colors";
@import "styles";
@import "photoswipe/photoswipe";
@import "photoswipe/photoswipe-dynamic-caption-plugin";
@import "custom";
.lazyload,
.lazyloading {
opacity: 0;
}
.lazyloaded {
opacity: 1;
transition: opacity 300ms;
}
img.lazyload:not([src]) {
visibility: hidden;
}

View File

@@ -0,0 +1,52 @@
.pswp__dynamic-caption {
color: #fff;
position: absolute;
width: 100%;
left: 0;
top: 0;
transition: opacity 120ms linear !important; /* override default */
}
.pswp-caption-content {
display: none;
}
.pswp__dynamic-caption a {
color: #fff;
}
.pswp__dynamic-caption--faded {
opacity: 0 !important;
}
.pswp__dynamic-caption--aside {
width: auto;
max-width: 300px;
padding: 20px 15px 20px 20px;
margin-top: 70px;
}
.pswp__dynamic-caption--below {
width: auto;
max-width: 700px;
padding: 15px 0 0;
}
.pswp__dynamic-caption--on-hor-edge {
padding-left: 15px;
padding-right: 15px;
}
.pswp__dynamic-caption--mobile {
width: 100%;
background: rgba(0,0,0,0.5);
padding: 10px 15px;
right: 0;
bottom: 0;
/* override styles that were set via JS.
as they interfere with size measurement */
top: auto !important;
left: 0 !important;
}

View File

@@ -0,0 +1,420 @@
/*! 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;
}

View File

@@ -0,0 +1 @@
/* custom.js */

View File

@@ -0,0 +1,52 @@
import justifiedLayout from "./justified-layout/index.js";
import * as params from "@params";
const gallery = document.getElementById("gallery");
if (gallery) {
let containerWidth = 0;
const items = gallery.querySelectorAll(".gallery-item");
const input = Array.from(items).map((item) => {
const img = item.querySelector("img");
img.style.width = "100%";
img.style.height = "auto";
return {
width: parseFloat(img.getAttribute("width")),
height: parseFloat(img.getAttribute("height")),
};
});
function updateGallery() {
if (containerWidth === gallery.getBoundingClientRect().width) return;
containerWidth = gallery.getBoundingClientRect().width;
const geometry = justifiedLayout(input, {
containerWidth,
containerPadding: 0,
boxSpacing: Number.isInteger(params.boxSpacing) ? params.boxSpacing : 10,
targetRowHeight: params.targetRowHeight || 288,
targetRowHeightTolerance: Number.isInteger(params.targetRowHeightTolerance) ? params.targetRowHeightTolerance : 0.25,
});
items.forEach((item, i) => {
const { width, height, top, left } = geometry.boxes[i];
item.style.position = "absolute";
item.style.width = width + "px";
item.style.height = height + "px";
item.style.top = top + "px";
item.style.left = left + "px";
item.style.overflow = "hidden";
});
gallery.style.position = "relative";
gallery.style.height = geometry.containerHeight + "px";
gallery.style.visibility = "";
}
window.addEventListener("resize", updateGallery);
window.addEventListener("orientationchange", updateGallery);
updateGallery();
updateGallery();
}

View File

@@ -0,0 +1,248 @@
/*!
* Copyright 2019 SmugMug, Inc.
* Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
* @license
*/
'use strict';
var Row = require('./row');
/**
* Create a new, empty row.
*
* @method createNewRow
* @param layoutConfig {Object} The layout configuration
* @param layoutData {Object} The current state of the layout
* @return A new, empty row of the type specified by this layout.
*/
function createNewRow(layoutConfig, layoutData) {
var isBreakoutRow;
// Work out if this is a full width breakout row
if (layoutConfig.fullWidthBreakoutRowCadence !== false) {
if (((layoutData._rows.length + 1) % layoutConfig.fullWidthBreakoutRowCadence) === 0) {
isBreakoutRow = true;
}
}
return new Row({
top: layoutData._containerHeight,
left: layoutConfig.containerPadding.left,
width: layoutConfig.containerWidth - layoutConfig.containerPadding.left - layoutConfig.containerPadding.right,
spacing: layoutConfig.boxSpacing.horizontal,
targetRowHeight: layoutConfig.targetRowHeight,
targetRowHeightTolerance: layoutConfig.targetRowHeightTolerance,
edgeCaseMinRowHeight: 0.5 * layoutConfig.targetRowHeight,
edgeCaseMaxRowHeight: 2 * layoutConfig.targetRowHeight,
rightToLeft: false,
isBreakoutRow: isBreakoutRow,
widowLayoutStyle: layoutConfig.widowLayoutStyle
});
}
/**
* Add a completed row to the layout.
* Note: the row must have already been completed.
*
* @method addRow
* @param layoutConfig {Object} The layout configuration
* @param layoutData {Object} The current state of the layout
* @param row {Row} The row to add.
* @return {Array} Each item added to the row.
*/
function addRow(layoutConfig, layoutData, row) {
layoutData._rows.push(row);
layoutData._layoutItems = layoutData._layoutItems.concat(row.getItems());
// Increment the container height
layoutData._containerHeight += row.height + layoutConfig.boxSpacing.vertical;
return row.items;
}
/**
* Calculate the current layout for all items in the list that require layout.
* "Layout" means geometry: position within container and size
*
* @method computeLayout
* @param layoutConfig {Object} The layout configuration
* @param layoutData {Object} The current state of the layout
* @param itemLayoutData {Array} Array of items to lay out, with data required to lay out each item
* @return {Object} The newly-calculated layout, containing the new container height, and lists of layout items
*/
function computeLayout(layoutConfig, layoutData, itemLayoutData) {
var laidOutItems = [],
itemAdded,
currentRow,
nextToLastRowHeight;
// Apply forced aspect ratio if specified, and set a flag.
if (layoutConfig.forceAspectRatio) {
itemLayoutData.forEach(function (itemData) {
itemData.forcedAspectRatio = true;
itemData.aspectRatio = layoutConfig.forceAspectRatio;
});
}
// Loop through the items
itemLayoutData.some(function (itemData, i) {
if (isNaN(itemData.aspectRatio)) {
throw new Error("Item " + i + " has an invalid aspect ratio");
}
// If not currently building up a row, make a new one.
if (!currentRow) {
currentRow = createNewRow(layoutConfig, layoutData);
}
// Attempt to add item to the current row.
itemAdded = currentRow.addItem(itemData);
if (currentRow.isLayoutComplete()) {
// Row is filled; add it and start a new one
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
if (layoutData._rows.length >= layoutConfig.maxNumRows) {
currentRow = null;
return true;
}
currentRow = createNewRow(layoutConfig, layoutData);
// Item was rejected; add it to its own row
if (!itemAdded) {
itemAdded = currentRow.addItem(itemData);
if (currentRow.isLayoutComplete()) {
// If the rejected item fills a row on its own, add the row and start another new one
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
if (layoutData._rows.length >= layoutConfig.maxNumRows) {
currentRow = null;
return true;
}
currentRow = createNewRow(layoutConfig, layoutData);
}
}
}
});
// Handle any leftover content (orphans) depending on where they lie
// in this layout update, and in the total content set.
if (currentRow && currentRow.getItems().length && layoutConfig.showWidows) {
// Last page of all content or orphan suppression is suppressed; lay out orphans.
if (layoutData._rows.length) {
// Only Match previous row's height if it exists and it isn't a breakout row
if (layoutData._rows[layoutData._rows.length - 1].isBreakoutRow) {
nextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].targetRowHeight;
} else {
nextToLastRowHeight = layoutData._rows[layoutData._rows.length - 1].height;
}
currentRow.forceComplete(false, nextToLastRowHeight);
} else {
// ...else use target height if there is no other row height to reference.
currentRow.forceComplete(false);
}
laidOutItems = laidOutItems.concat(addRow(layoutConfig, layoutData, currentRow));
layoutConfig._widowCount = currentRow.getItems().length;
}
// We need to clean up the bottom container padding
// First remove the height added for box spacing
layoutData._containerHeight = layoutData._containerHeight - layoutConfig.boxSpacing.vertical;
// Then add our bottom container padding
layoutData._containerHeight = layoutData._containerHeight + layoutConfig.containerPadding.bottom;
return {
containerHeight: layoutData._containerHeight,
widowCount: layoutConfig._widowCount,
boxes: layoutData._layoutItems
};
}
/**
* Takes in a bunch of box data and config. Returns
* geometry to lay them out in a justified view.
*
* @method covertSizesToAspectRatios
* @param sizes {Array} Array of objects with widths and heights
* @return {Array} A list of aspect ratios
*/
module.exports = function (input, config) {
var layoutConfig = {};
var layoutData = {};
// Defaults
var defaults = {
containerWidth: 1060,
containerPadding: 10,
boxSpacing: 10,
targetRowHeight: 320,
targetRowHeightTolerance: 0.25,
maxNumRows: Number.POSITIVE_INFINITY,
forceAspectRatio: false,
showWidows: true,
fullWidthBreakoutRowCadence: false,
widowLayoutStyle: 'left'
};
var containerPadding = {};
var boxSpacing = {};
config = config || {};
// Merge defaults and config passed in
layoutConfig = Object.assign(defaults, config);
// Sort out padding and spacing values
containerPadding.top = (!isNaN(parseFloat(layoutConfig.containerPadding.top))) ? layoutConfig.containerPadding.top : layoutConfig.containerPadding;
containerPadding.right = (!isNaN(parseFloat(layoutConfig.containerPadding.right))) ? layoutConfig.containerPadding.right : layoutConfig.containerPadding;
containerPadding.bottom = (!isNaN(parseFloat(layoutConfig.containerPadding.bottom))) ? layoutConfig.containerPadding.bottom : layoutConfig.containerPadding;
containerPadding.left = (!isNaN(parseFloat(layoutConfig.containerPadding.left))) ? layoutConfig.containerPadding.left : layoutConfig.containerPadding;
boxSpacing.horizontal = (!isNaN(parseFloat(layoutConfig.boxSpacing.horizontal))) ? layoutConfig.boxSpacing.horizontal : layoutConfig.boxSpacing;
boxSpacing.vertical = (!isNaN(parseFloat(layoutConfig.boxSpacing.vertical))) ? layoutConfig.boxSpacing.vertical : layoutConfig.boxSpacing;
layoutConfig.containerPadding = containerPadding;
layoutConfig.boxSpacing = boxSpacing;
// Local
layoutData._layoutItems = [];
layoutData._awakeItems = [];
layoutData._inViewportItems = [];
layoutData._leadingOrphans = [];
layoutData._trailingOrphans = [];
layoutData._containerHeight = layoutConfig.containerPadding.top;
layoutData._rows = [];
layoutData._orphans = [];
layoutConfig._widowCount = 0;
// Convert widths and heights to aspect ratios if we need to
return computeLayout(layoutConfig, layoutData, input.map(function (item) {
if (item.width && item.height) {
return { aspectRatio: item.width / item.height };
} else {
return { aspectRatio: item };
}
}));
};

View File

@@ -0,0 +1,333 @@
/*!
* Copyright 2019 SmugMug, Inc.
* Licensed under the terms of the MIT license. Please see LICENSE file in the project root for terms.
* @license
*/
/**
* Row
* Wrapper for each row in a justified layout.
* Stores relevant values and provides methods for calculating layout of individual rows.
*
* @param {Object} layoutConfig - The same as that passed
* @param {Object} Initialization parameters. The following are all required:
* @param params.top {Number} Top of row, relative to container
* @param params.left {Number} Left side of row relative to container (equal to container left padding)
* @param params.width {Number} Width of row, not including container padding
* @param params.spacing {Number} Horizontal spacing between items
* @param params.targetRowHeight {Number} Layout algorithm will aim for this row height
* @param params.targetRowHeightTolerance {Number} Row heights may vary +/- (`targetRowHeight` x `targetRowHeightTolerance`)
* @param params.edgeCaseMinRowHeight {Number} Absolute minimum row height for edge cases that cannot be resolved within tolerance.
* @param params.edgeCaseMaxRowHeight {Number} Absolute maximum row height for edge cases that cannot be resolved within tolerance.
* @param params.isBreakoutRow {Boolean} Is this row in particular one of those breakout rows? Always false if it's not that kind of photo list
* @param params.widowLayoutStyle {String} If widows are visible, how should they be laid out?
* @constructor
*/
var Row = module.exports = function (params) {
// Top of row, relative to container
this.top = params.top;
// Left side of row relative to container (equal to container left padding)
this.left = params.left;
// Width of row, not including container padding
this.width = params.width;
// Horizontal spacing between items
this.spacing = params.spacing;
// Row height calculation values
this.targetRowHeight = params.targetRowHeight;
this.targetRowHeightTolerance = params.targetRowHeightTolerance;
this.minAspectRatio = this.width / params.targetRowHeight * (1 - params.targetRowHeightTolerance);
this.maxAspectRatio = this.width / params.targetRowHeight * (1 + params.targetRowHeightTolerance);
// Edge case row height minimum/maximum
this.edgeCaseMinRowHeight = params.edgeCaseMinRowHeight;
this.edgeCaseMaxRowHeight = params.edgeCaseMaxRowHeight;
// Widow layout direction
this.widowLayoutStyle = params.widowLayoutStyle;
// Full width breakout rows
this.isBreakoutRow = params.isBreakoutRow;
// Store layout data for each item in row
this.items = [];
// Height remains at 0 until it's been calculated
this.height = 0;
};
Row.prototype = {
/**
* Attempt to add a single item to the row.
* This is the heart of the justified algorithm.
* This method is direction-agnostic; it deals only with sizes, not positions.
*
* If the item fits in the row, without pushing row height beyond min/max tolerance,
* the item is added and the method returns true.
*
* If the item leaves row height too high, there may be room to scale it down and add another item.
* In this case, the item is added and the method returns true, but the row is incomplete.
*
* If the item leaves row height too short, there are too many items to fit within tolerance.
* The method will either accept or reject the new item, favoring the resulting row height closest to within tolerance.
* If the item is rejected, left/right padding will be required to fit the row height within tolerance;
* if the item is accepted, top/bottom cropping will be required to fit the row height within tolerance.
*
* @method addItem
* @param itemData {Object} Item layout data, containing item aspect ratio.
* @return {Boolean} True if successfully added; false if rejected.
*/
addItem: function (itemData) {
var newItems = this.items.concat(itemData),
// Calculate aspect ratios for items only; exclude spacing
rowWidthWithoutSpacing = this.width - (newItems.length - 1) * this.spacing,
newAspectRatio = newItems.reduce(function (sum, item) {
return sum + item.aspectRatio;
}, 0),
targetAspectRatio = rowWidthWithoutSpacing / this.targetRowHeight,
previousRowWidthWithoutSpacing,
previousAspectRatio,
previousTargetAspectRatio;
// Handle big full-width breakout photos if we're doing them
if (this.isBreakoutRow) {
// Only do it if there's no other items in this row
if (this.items.length === 0) {
// Only go full width if this photo is a square or landscape
if (itemData.aspectRatio >= 1) {
// Close out the row with a full width photo
this.items.push(itemData);
this.completeLayout(rowWidthWithoutSpacing / itemData.aspectRatio, 'justify');
return true;
}
}
}
if (newAspectRatio < this.minAspectRatio) {
// New aspect ratio is too narrow / scaled row height is too tall.
// Accept this item and leave row open for more items.
this.items.push(Object.assign({}, itemData));
return true;
} else if (newAspectRatio > this.maxAspectRatio) {
// New aspect ratio is too wide / scaled row height will be too short.
// Accept item if the resulting aspect ratio is closer to target than it would be without the item.
// NOTE: Any row that falls into this block will require cropping/padding on individual items.
if (this.items.length === 0) {
// When there are no existing items, force acceptance of the new item and complete the layout.
// This is the pano special case.
this.items.push(Object.assign({}, itemData));
this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
return true;
}
// Calculate width/aspect ratio for row before adding new item
previousRowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing;
previousAspectRatio = this.items.reduce(function (sum, item) {
return sum + item.aspectRatio;
}, 0);
previousTargetAspectRatio = previousRowWidthWithoutSpacing / this.targetRowHeight;
if (Math.abs(newAspectRatio - targetAspectRatio) > Math.abs(previousAspectRatio - previousTargetAspectRatio)) {
// Row with new item is us farther away from target than row without; complete layout and reject item.
this.completeLayout(previousRowWidthWithoutSpacing / previousAspectRatio, 'justify');
return false;
} else {
// Row with new item is us closer to target than row without;
// accept the new item and complete the row layout.
this.items.push(Object.assign({}, itemData));
this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
return true;
}
} else {
// New aspect ratio / scaled row height is within tolerance;
// accept the new item and complete the row layout.
this.items.push(Object.assign({}, itemData));
this.completeLayout(rowWidthWithoutSpacing / newAspectRatio, 'justify');
return true;
}
},
/**
* Check if a row has completed its layout.
*
* @method isLayoutComplete
* @return {Boolean} True if complete; false if not.
*/
isLayoutComplete: function () {
return this.height > 0;
},
/**
* Set row height and compute item geometry from that height.
* Will justify items within the row unless instructed not to.
*
* @method completeLayout
* @param newHeight {Number} Set row height to this value.
* @param widowLayoutStyle {String} How should widows display? Supported: left | justify | center
*/
completeLayout: function (newHeight, widowLayoutStyle) {
var itemWidthSum = this.left,
rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
clampedToNativeRatio,
clampedHeight,
errorWidthPerItem,
roundedCumulativeErrors,
singleItemGeometry,
centerOffset;
// Justify unless explicitly specified otherwise.
if (typeof widowLayoutStyle === 'undefined' || ['justify', 'center', 'left'].indexOf(widowLayoutStyle) < 0) {
widowLayoutStyle = 'left';
}
// Clamp row height to edge case minimum/maximum.
clampedHeight = Math.max(this.edgeCaseMinRowHeight, Math.min(newHeight, this.edgeCaseMaxRowHeight));
if (newHeight !== clampedHeight) {
// If row height was clamped, the resulting row/item aspect ratio will be off,
// so force it to fit the width (recalculate aspectRatio to match clamped height).
// NOTE: this will result in cropping/padding commensurate to the amount of clamping.
this.height = clampedHeight;
clampedToNativeRatio = (rowWidthWithoutSpacing / clampedHeight) / (rowWidthWithoutSpacing / newHeight);
} else {
// If not clamped, leave ratio at 1.0.
this.height = newHeight;
clampedToNativeRatio = 1.0;
}
// Compute item geometry based on newHeight.
this.items.forEach(function (item) {
item.top = this.top;
item.width = item.aspectRatio * this.height * clampedToNativeRatio;
item.height = this.height;
// Left-to-right.
// TODO right to left
// item.left = this.width - itemWidthSum - item.width;
item.left = itemWidthSum;
// Increment width.
itemWidthSum += item.width + this.spacing;
}, this);
// If specified, ensure items fill row and distribute error
// caused by rounding width and height across all items.
if (widowLayoutStyle === 'justify') {
itemWidthSum -= (this.spacing + this.left);
errorWidthPerItem = (itemWidthSum - this.width) / this.items.length;
roundedCumulativeErrors = this.items.map(function (item, i) {
return Math.round((i + 1) * errorWidthPerItem);
});
if (this.items.length === 1) {
// For rows with only one item, adjust item width to fill row.
singleItemGeometry = this.items[0];
singleItemGeometry.width -= Math.round(errorWidthPerItem);
} else {
// For rows with multiple items, adjust item width and shift items to fill the row,
// while maintaining equal spacing between items in the row.
this.items.forEach(function (item, i) {
if (i > 0) {
item.left -= roundedCumulativeErrors[i - 1];
item.width -= (roundedCumulativeErrors[i] - roundedCumulativeErrors[i - 1]);
} else {
item.width -= roundedCumulativeErrors[i];
}
});
}
} else if (widowLayoutStyle === 'center') {
// Center widows
centerOffset = (this.width - itemWidthSum) / 2;
this.items.forEach(function (item) {
item.left += centerOffset + this.spacing;
}, this);
}
},
/**
* Force completion of row layout with current items.
*
* @method forceComplete
* @param fitToWidth {Boolean} Stretch current items to fill the row width.
* This will likely result in padding.
* @param fitToWidth {Number}
*/
forceComplete: function (fitToWidth, rowHeight) {
// TODO Handle fitting to width
// var rowWidthWithoutSpacing = this.width - (this.items.length - 1) * this.spacing,
// currentAspectRatio = this.items.reduce(function (sum, item) {
// return sum + item.aspectRatio;
// }, 0);
if (typeof rowHeight === 'number') {
this.completeLayout(rowHeight, this.widowLayoutStyle);
} else {
// Complete using target row height.
this.completeLayout(this.targetRowHeight, this.widowLayoutStyle);
}
},
/**
* Return layout data for items within row.
* Note: returns actual list, not a copy.
*
* @method getItems
* @return Layout data for items within row.
*/
getItems: function () {
return this.items;
}
};

View File

@@ -0,0 +1,805 @@
(function (window, factory) {
var lazySizes = factory(window, window.document, Date);
window.lazySizes = lazySizes;
if (typeof module == "object" && module.exports) {
module.exports = lazySizes;
}
})(
typeof window != "undefined" ? window : {},
/**
* import("./types/global")
* @typedef { import("./types/lazysizes-config").LazySizesConfigPartial } LazySizesConfigPartial
*/
function l(window, document, Date) {
// Pass in the window Date function also for SSR because the Date class can be lost
"use strict";
/*jshint eqnull:true */
var lazysizes,
/**
* @type { LazySizesConfigPartial }
*/
lazySizesCfg;
(function () {
var prop;
var lazySizesDefaults = {
lazyClass: "lazyload",
loadedClass: "lazyloaded",
loadingClass: "lazyloading",
preloadClass: "lazypreload",
errorClass: "lazyerror",
//strictClass: 'lazystrict',
autosizesClass: "lazyautosizes",
fastLoadedClass: "ls-is-cached",
iframeLoadMode: 0,
srcAttr: "data-src",
srcsetAttr: "data-srcset",
sizesAttr: "data-sizes",
//preloadAfterLoad: false,
minSize: 40,
customMedia: {},
init: true,
expFactor: 1.5,
hFac: 0.8,
loadMode: 2,
loadHidden: true,
ricTimeout: 0,
throttleDelay: 125,
};
lazySizesCfg = window.lazySizesConfig || window.lazysizesConfig || {};
for (prop in lazySizesDefaults) {
if (!(prop in lazySizesCfg)) {
lazySizesCfg[prop] = lazySizesDefaults[prop];
}
}
})();
if (!document || !document.getElementsByClassName) {
return {
init: function () {},
/**
* @type { LazySizesConfigPartial }
*/
cfg: lazySizesCfg,
/**
* @type { true }
*/
noSupport: true,
};
}
var docElem = document.documentElement;
var supportPicture = window.HTMLPictureElement;
var _addEventListener = "addEventListener";
var _getAttribute = "getAttribute";
/**
* Update to bind to window because 'this' becomes null during SSR
* builds.
*/
var addEventListener = window[_addEventListener].bind(window);
var setTimeout = window.setTimeout;
var requestAnimationFrame = window.requestAnimationFrame || setTimeout;
var requestIdleCallback = window.requestIdleCallback;
var regPicture = /^picture$/i;
var loadEvents = ["load", "error", "lazyincluded", "_lazyloaded"];
var regClassCache = {};
var forEach = Array.prototype.forEach;
/**
* @param ele {Element}
* @param cls {string}
*/
var hasClass = function (ele, cls) {
if (!regClassCache[cls]) {
regClassCache[cls] = new RegExp("(\\s|^)" + cls + "(\\s|$)");
}
return regClassCache[cls].test(ele[_getAttribute]("class") || "") && regClassCache[cls];
};
/**
* @param ele {Element}
* @param cls {string}
*/
var addClass = function (ele, cls) {
if (!hasClass(ele, cls)) {
ele.setAttribute("class", (ele[_getAttribute]("class") || "").trim() + " " + cls);
}
};
/**
* @param ele {Element}
* @param cls {string}
*/
var removeClass = function (ele, cls) {
var reg;
if ((reg = hasClass(ele, cls))) {
ele.setAttribute("class", (ele[_getAttribute]("class") || "").replace(reg, " "));
}
};
var addRemoveLoadEvents = function (dom, fn, add) {
var action = add ? _addEventListener : "removeEventListener";
if (add) {
addRemoveLoadEvents(dom, fn);
}
loadEvents.forEach(function (evt) {
dom[action](evt, fn);
});
};
/**
* @param elem { Element }
* @param name { string }
* @param detail { any }
* @param noBubbles { boolean }
* @param noCancelable { boolean }
* @returns { CustomEvent }
*/
var triggerEvent = function (elem, name, detail, noBubbles, noCancelable) {
var event = document.createEvent("Event");
if (!detail) {
detail = {};
}
detail.instance = lazysizes;
event.initEvent(name, !noBubbles, !noCancelable);
event.detail = detail;
elem.dispatchEvent(event);
return event;
};
var updatePolyfill = function (el, full) {
var polyfill;
if (!supportPicture && (polyfill = window.picturefill || lazySizesCfg.pf)) {
if (full && full.src && !el[_getAttribute]("srcset")) {
el.setAttribute("srcset", full.src);
}
polyfill({ reevaluate: true, elements: [el] });
} else if (full && full.src) {
el.src = full.src;
}
};
var getCSS = function (elem, style) {
return (getComputedStyle(elem, null) || {})[style];
};
/**
*
* @param elem { Element }
* @param parent { Element }
* @param [width] {number}
* @returns {number}
*/
var getWidth = function (elem, parent, width) {
width = width || elem.offsetWidth;
while (width < lazySizesCfg.minSize && parent && !elem._lazysizesWidth) {
width = parent.offsetWidth;
parent = parent.parentNode;
}
return width;
};
var rAF = (function () {
var running, waiting;
var firstFns = [];
var secondFns = [];
var fns = firstFns;
var run = function () {
var runFns = fns;
fns = firstFns.length ? secondFns : firstFns;
running = true;
waiting = false;
while (runFns.length) {
runFns.shift()();
}
running = false;
};
var rafBatch = function (fn, queue) {
if (running && !queue) {
fn.apply(this, arguments);
} else {
fns.push(fn);
if (!waiting) {
waiting = true;
(document.hidden ? setTimeout : requestAnimationFrame)(run);
}
}
};
rafBatch._lsFlush = run;
return rafBatch;
})();
var rAFIt = function (fn, simple) {
return simple
? function () {
rAF(fn);
}
: function () {
var that = this;
var args = arguments;
rAF(function () {
fn.apply(that, args);
});
};
};
var throttle = function (fn) {
var running;
var lastTime = 0;
var gDelay = lazySizesCfg.throttleDelay;
var rICTimeout = lazySizesCfg.ricTimeout;
var run = function () {
running = false;
lastTime = Date.now();
fn();
};
var idleCallback =
requestIdleCallback && rICTimeout > 49
? function () {
requestIdleCallback(run, { timeout: rICTimeout });
if (rICTimeout !== lazySizesCfg.ricTimeout) {
rICTimeout = lazySizesCfg.ricTimeout;
}
}
: rAFIt(function () {
setTimeout(run);
}, true);
return function (isPriority) {
var delay;
if ((isPriority = isPriority === true)) {
rICTimeout = 33;
}
if (running) {
return;
}
running = true;
delay = gDelay - (Date.now() - lastTime);
if (delay < 0) {
delay = 0;
}
if (isPriority || delay < 9) {
idleCallback();
} else {
setTimeout(idleCallback, delay);
}
};
};
//based on http://modernjavascript.blogspot.de/2013/08/building-better-debounce.html
var debounce = function (func) {
var timeout, timestamp;
var wait = 99;
var run = function () {
timeout = null;
func();
};
var later = function () {
var last = Date.now() - timestamp;
if (last < wait) {
setTimeout(later, wait - last);
} else {
(requestIdleCallback || run)(run);
}
};
return function () {
timestamp = Date.now();
if (!timeout) {
timeout = setTimeout(later, wait);
}
};
};
var loader = (function () {
var preloadElems, isCompleted, resetPreloadingTimer, loadMode, started;
var eLvW, elvH, eLtop, eLleft, eLright, eLbottom, isBodyHidden;
var regImg = /^img$/i;
var regIframe = /^iframe$/i;
var supportScroll = "onscroll" in window && !/(gle|ing)bot/.test(navigator.userAgent);
var shrinkExpand = 0;
var currentExpand = 0;
var isLoading = 0;
var lowRuns = -1;
var resetPreloading = function (e) {
isLoading--;
if (!e || isLoading < 0 || !e.target) {
isLoading = 0;
}
};
var isVisible = function (elem) {
if (isBodyHidden == null) {
isBodyHidden = getCSS(document.body, "visibility") == "hidden";
}
return isBodyHidden || !(getCSS(elem.parentNode, "visibility") == "hidden" && getCSS(elem, "visibility") == "hidden");
};
var isNestedVisible = function (elem, elemExpand) {
var outerRect;
var parent = elem;
var visible = isVisible(elem);
eLtop -= elemExpand;
eLbottom += elemExpand;
eLleft -= elemExpand;
eLright += elemExpand;
while (visible && (parent = parent.offsetParent) && parent != document.body && parent != docElem) {
visible = (getCSS(parent, "opacity") || 1) > 0;
if (visible && getCSS(parent, "overflow") != "visible") {
outerRect = parent.getBoundingClientRect();
visible = eLright > outerRect.left && eLleft < outerRect.right && eLbottom > outerRect.top - 1 && eLtop < outerRect.bottom + 1;
}
}
return visible;
};
var checkElements = function () {
var eLlen, i, rect, autoLoadElem, loadedSomething, elemExpand, elemNegativeExpand, elemExpandVal, beforeExpandVal, defaultExpand, preloadExpand, hFac;
var lazyloadElems = lazysizes.elements;
if ((loadMode = lazySizesCfg.loadMode) && isLoading < 8 && (eLlen = lazyloadElems.length)) {
i = 0;
lowRuns++;
for (; i < eLlen; i++) {
if (!lazyloadElems[i] || lazyloadElems[i]._lazyRace) {
continue;
}
if (!supportScroll || (lazysizes.prematureUnveil && lazysizes.prematureUnveil(lazyloadElems[i]))) {
unveilElement(lazyloadElems[i]);
continue;
}
if (!(elemExpandVal = lazyloadElems[i][_getAttribute]("data-expand")) || !(elemExpand = elemExpandVal * 1)) {
elemExpand = currentExpand;
}
if (!defaultExpand) {
defaultExpand = !lazySizesCfg.expand || lazySizesCfg.expand < 1 ? (docElem.clientHeight > 500 && docElem.clientWidth > 500 ? 500 : 370) : lazySizesCfg.expand;
lazysizes._defEx = defaultExpand;
preloadExpand = defaultExpand * lazySizesCfg.expFactor;
hFac = lazySizesCfg.hFac;
isBodyHidden = null;
if (currentExpand < preloadExpand && isLoading < 1 && lowRuns > 2 && loadMode > 2 && !document.hidden) {
currentExpand = preloadExpand;
lowRuns = 0;
} else if (loadMode > 1 && lowRuns > 1 && isLoading < 6) {
currentExpand = defaultExpand;
} else {
currentExpand = shrinkExpand;
}
}
if (beforeExpandVal !== elemExpand) {
eLvW = innerWidth + elemExpand * hFac;
elvH = innerHeight + elemExpand;
elemNegativeExpand = elemExpand * -1;
beforeExpandVal = elemExpand;
}
rect = lazyloadElems[i].getBoundingClientRect();
if ((eLbottom = rect.bottom) >= elemNegativeExpand && (eLtop = rect.top) <= elvH && (eLright = rect.right) >= elemNegativeExpand * hFac && (eLleft = rect.left) <= eLvW && (eLbottom || eLright || eLleft || eLtop) && (lazySizesCfg.loadHidden || isVisible(lazyloadElems[i])) && ((isCompleted && isLoading < 3 && !elemExpandVal && (loadMode < 3 || lowRuns < 4)) || isNestedVisible(lazyloadElems[i], elemExpand))) {
unveilElement(lazyloadElems[i]);
loadedSomething = true;
if (isLoading > 9) {
break;
}
} else if (!loadedSomething && isCompleted && !autoLoadElem && isLoading < 4 && lowRuns < 4 && loadMode > 2 && (preloadElems[0] || lazySizesCfg.preloadAfterLoad) && (preloadElems[0] || (!elemExpandVal && (eLbottom || eLright || eLleft || eLtop || lazyloadElems[i][_getAttribute](lazySizesCfg.sizesAttr) != "auto")))) {
autoLoadElem = preloadElems[0] || lazyloadElems[i];
}
}
if (autoLoadElem && !loadedSomething) {
unveilElement(autoLoadElem);
}
}
};
var throttledCheckElements = throttle(checkElements);
var switchLoadingClass = function (e) {
var elem = e.target;
if (elem._lazyCache) {
delete elem._lazyCache;
return;
}
resetPreloading(e);
addClass(elem, lazySizesCfg.loadedClass);
removeClass(elem, lazySizesCfg.loadingClass);
addRemoveLoadEvents(elem, rafSwitchLoadingClass);
triggerEvent(elem, "lazyloaded");
};
var rafedSwitchLoadingClass = rAFIt(switchLoadingClass);
var rafSwitchLoadingClass = function (e) {
rafedSwitchLoadingClass({ target: e.target });
};
var changeIframeSrc = function (elem, src) {
var loadMode = elem.getAttribute("data-load-mode") || lazySizesCfg.iframeLoadMode;
// loadMode can be also a string!
if (loadMode == 0) {
elem.contentWindow.location.replace(src);
} else if (loadMode == 1) {
elem.src = src;
}
};
var handleSources = function (source) {
var customMedia;
var sourceSrcset = source[_getAttribute](lazySizesCfg.srcsetAttr);
if ((customMedia = lazySizesCfg.customMedia[source[_getAttribute]("data-media") || source[_getAttribute]("media")])) {
source.setAttribute("media", customMedia);
}
if (sourceSrcset) {
source.setAttribute("srcset", sourceSrcset);
}
};
var lazyUnveil = rAFIt(function (elem, detail, isAuto, sizes, isImg) {
var src, srcset, parent, isPicture, event, firesLoad;
if (!(event = triggerEvent(elem, "lazybeforeunveil", detail)).defaultPrevented) {
if (sizes) {
if (isAuto) {
addClass(elem, lazySizesCfg.autosizesClass);
} else {
elem.setAttribute("sizes", sizes);
}
}
srcset = elem[_getAttribute](lazySizesCfg.srcsetAttr);
src = elem[_getAttribute](lazySizesCfg.srcAttr);
if (isImg) {
parent = elem.parentNode;
isPicture = parent && regPicture.test(parent.nodeName || "");
}
firesLoad = detail.firesLoad || ("src" in elem && (srcset || src || isPicture));
event = { target: elem };
addClass(elem, lazySizesCfg.loadingClass);
if (firesLoad) {
clearTimeout(resetPreloadingTimer);
resetPreloadingTimer = setTimeout(resetPreloading, 2500);
addRemoveLoadEvents(elem, rafSwitchLoadingClass, true);
}
if (isPicture) {
forEach.call(parent.getElementsByTagName("source"), handleSources);
}
if (srcset) {
elem.setAttribute("srcset", srcset);
} else if (src && !isPicture) {
if (regIframe.test(elem.nodeName)) {
changeIframeSrc(elem, src);
} else {
elem.src = src;
}
}
if (isImg && (srcset || isPicture)) {
updatePolyfill(elem, { src: src });
}
}
if (elem._lazyRace) {
delete elem._lazyRace;
}
removeClass(elem, lazySizesCfg.lazyClass);
rAF(function () {
// Part of this can be removed as soon as this fix is older: https://bugs.chromium.org/p/chromium/issues/detail?id=7731 (2015)
var isLoaded = elem.complete && elem.naturalWidth > 1;
if (!firesLoad || isLoaded) {
if (isLoaded) {
addClass(elem, lazySizesCfg.fastLoadedClass);
}
switchLoadingClass(event);
elem._lazyCache = true;
setTimeout(function () {
if ("_lazyCache" in elem) {
delete elem._lazyCache;
}
}, 9);
}
if (elem.loading == "lazy") {
isLoading--;
}
}, true);
});
/**
*
* @param elem { Element }
*/
var unveilElement = function (elem) {
if (elem._lazyRace) {
return;
}
var detail;
var isImg = regImg.test(elem.nodeName);
//allow using sizes="auto", but don't use. it's invalid. Use data-sizes="auto" or a valid value for sizes instead (i.e.: sizes="80vw")
var sizes = isImg && (elem[_getAttribute](lazySizesCfg.sizesAttr) || elem[_getAttribute]("sizes"));
var isAuto = sizes == "auto";
if ((isAuto || !isCompleted) && isImg && (elem[_getAttribute]("src") || elem.srcset) && !elem.complete && !hasClass(elem, lazySizesCfg.errorClass) && hasClass(elem, lazySizesCfg.lazyClass)) {
return;
}
detail = triggerEvent(elem, "lazyunveilread").detail;
if (isAuto) {
autoSizer.updateElem(elem, true, elem.offsetWidth);
}
elem._lazyRace = true;
isLoading++;
lazyUnveil(elem, detail, isAuto, sizes, isImg);
};
var afterScroll = debounce(function () {
lazySizesCfg.loadMode = 3;
throttledCheckElements();
});
var altLoadmodeScrollListner = function () {
if (lazySizesCfg.loadMode == 3) {
lazySizesCfg.loadMode = 2;
}
afterScroll();
};
var onload = function () {
if (isCompleted) {
return;
}
if (Date.now() - started < 999) {
setTimeout(onload, 999);
return;
}
isCompleted = true;
lazySizesCfg.loadMode = 3;
throttledCheckElements();
addEventListener("scroll", altLoadmodeScrollListner, true);
};
return {
_: function () {
started = Date.now();
lazysizes.elements = document.getElementsByClassName(lazySizesCfg.lazyClass);
preloadElems = document.getElementsByClassName(lazySizesCfg.lazyClass + " " + lazySizesCfg.preloadClass);
addEventListener("scroll", throttledCheckElements, true);
addEventListener("resize", throttledCheckElements, true);
addEventListener("pageshow", function (e) {
if (e.persisted) {
var loadingElements = document.querySelectorAll("." + lazySizesCfg.loadingClass);
if (loadingElements.length && loadingElements.forEach) {
requestAnimationFrame(function () {
loadingElements.forEach(function (img) {
if (img.complete) {
unveilElement(img);
}
});
});
}
}
});
if (window.MutationObserver) {
new MutationObserver(throttledCheckElements).observe(docElem, { childList: true, subtree: true, attributes: true });
} else {
docElem[_addEventListener]("DOMNodeInserted", throttledCheckElements, true);
docElem[_addEventListener]("DOMAttrModified", throttledCheckElements, true);
setInterval(throttledCheckElements, 999);
}
addEventListener("hashchange", throttledCheckElements, true);
//, 'fullscreenchange'
["focus", "mouseover", "click", "load", "transitionend", "animationend"].forEach(function (name) {
document[_addEventListener](name, throttledCheckElements, true);
});
if (/d$|^c/.test(document.readyState)) {
onload();
} else {
addEventListener("load", onload);
document[_addEventListener]("DOMContentLoaded", throttledCheckElements);
setTimeout(onload, 20000);
}
if (lazysizes.elements.length) {
checkElements();
rAF._lsFlush();
} else {
throttledCheckElements();
}
},
checkElems: throttledCheckElements,
unveil: unveilElement,
_aLSL: altLoadmodeScrollListner,
};
})();
var autoSizer = (function () {
var autosizesElems;
var sizeElement = rAFIt(function (elem, parent, event, width) {
var sources, i, len;
elem._lazysizesWidth = width;
width += "px";
elem.setAttribute("sizes", width);
if (regPicture.test(parent.nodeName || "")) {
sources = parent.getElementsByTagName("source");
for (i = 0, len = sources.length; i < len; i++) {
sources[i].setAttribute("sizes", width);
}
}
if (!event.detail.dataAttr) {
updatePolyfill(elem, event.detail);
}
});
/**
*
* @param elem {Element}
* @param dataAttr
* @param [width] { number }
*/
var getSizeElement = function (elem, dataAttr, width) {
var event;
var parent = elem.parentNode;
if (parent) {
width = getWidth(elem, parent, width);
event = triggerEvent(elem, "lazybeforesizes", { width: width, dataAttr: !!dataAttr });
if (!event.defaultPrevented) {
width = event.detail.width;
if (width && width !== elem._lazysizesWidth) {
sizeElement(elem, parent, event, width);
}
}
}
};
var updateElementsSizes = function () {
var i;
var len = autosizesElems.length;
if (len) {
i = 0;
for (; i < len; i++) {
getSizeElement(autosizesElems[i]);
}
}
};
var debouncedUpdateElementsSizes = debounce(updateElementsSizes);
return {
_: function () {
autosizesElems = document.getElementsByClassName(lazySizesCfg.autosizesClass);
addEventListener("resize", debouncedUpdateElementsSizes);
},
checkElems: debouncedUpdateElementsSizes,
updateElem: getSizeElement,
};
})();
var init = function () {
if (!init.i && document.getElementsByClassName) {
init.i = true;
autoSizer._();
loader._();
}
};
setTimeout(function () {
if (lazySizesCfg.init) {
init();
}
});
lazysizes = {
/**
* @type { LazySizesConfigPartial }
*/
cfg: lazySizesCfg,
autoSizer: autoSizer,
loader: loader,
init: init,
uP: updatePolyfill,
aC: addClass,
rC: removeClass,
hC: hasClass,
fire: triggerEvent,
gW: getWidth,
rAF: rAF,
};
return lazysizes;
},
);

View File

@@ -0,0 +1,83 @@
import PhotoSwipeLightbox from "./photoswipe/photoswipe-lightbox.esm.js";
import PhotoSwipe from "./photoswipe/photoswipe.esm.js";
import PhotoSwipeDynamicCaption from "./photoswipe/photoswipe-dynamic-caption-plugin.esm.min.js";
import * as params from "@params";
const gallery = document.getElementById("gallery");
if (gallery) {
const lightbox = new PhotoSwipeLightbox({
gallery,
children: ".gallery-item",
showHideAnimationType: "zoom",
bgOpacity: 1,
pswpModule: PhotoSwipe,
imageClickAction: "close",
paddingFn: (viewportSize) => {
return viewportSize.x < 700
? {
top: 0,
bottom: 0,
left: 0,
right: 0,
}
: {
top: 30,
bottom: 30,
left: 0,
right: 0,
};
},
closeTitle: params.closeTitle,
zoomTitle: params.zoomTitle,
arrowPrevTitle: params.arrowPrevTitle,
arrowNextTitle: params.arrowNextTitle,
errorMsg: params.errorMsg,
});
lightbox.on("uiRegister", () => {
lightbox.pswp.ui.registerElement({
name: "download-button",
order: 8,
isButton: true,
tagName: "a",
html: {
isCustomSVG: true,
inner: '<path d="M20.5 14.3 17.1 18V10h-2.2v7.9l-3.4-3.6L10 16l6 6.1 6-6.1ZM23 23H9v2h14Z" id="pswp__icn-download"/>',
outlineID: "pswp__icn-download",
},
onInit: (el, pswp) => {
el.setAttribute("download", "");
el.setAttribute("target", "_blank");
el.setAttribute("rel", "noopener");
el.setAttribute("title", params.downloadTitle || "Download");
pswp.on("change", () => {
el.href = pswp.currSlide.data.element.href;
});
},
});
});
lightbox.on("change", () => {
history.replaceState("", document.title, "#" + lightbox.pswp.currSlide.index);
});
lightbox.on("close", () => {
history.replaceState("", document.title, window.location.pathname);
});
new PhotoSwipeDynamicCaption(lightbox, {
mobileLayoutBreakpoint: 700,
type: "auto",
mobileCaptionOverlapRatio: 1,
});
lightbox.init();
if (window.location.hash.substring(1).length > 0) {
const index = parseInt(window.location.hash.substring(1), 10);
if (!Number.isNaN(index) && index >= 0 && index < gallery.querySelectorAll("a").length) {
lightbox.loadAndOpen(index, { gallery });
}
}
}

View File

@@ -0,0 +1,5 @@
import "./menu.js";
import "./gallery.js";
import "./lazysizes.js";
import "./lightbox.js";
import "./custom.js";

View File

@@ -0,0 +1,9 @@
const el = document.getElementById("menu-toggle");
if (el) {
el.addEventListener("click", (event) => {
event.preventDefault();
const target = document.getElementById("menu");
el.ariaExpanded = target.classList.contains("hidden");
target.classList.toggle("hidden");
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*"
]
}
}
}

6
themes/gallery/exampleSite/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules/
public/
resources/
.hugo_build.lock
hugo_stats.json
content/**/*.jpg

View File

@@ -0,0 +1,11 @@
# Example site for hugo-theme-gallery
## Installation
```
# Install Hugo module
hugo mod get
# Pull example images from Unsplash
./pull-images.sh
```

View File

@@ -0,0 +1,10 @@
:root {
--surface-1-light: #ffffff;
--surface-2-light: #f0f0f0; /* gray-3 */
--text-1-light: #202020; /* gray-12 */
--text-2-light: #646464; /* gray-11 */
--surface-1-dark: #111111; /* gray-1-dark */
--surface-2-dark: #222222; /* gray-3-dark */
--text-1-dark: #eeeeee; /* gray-12-dark */
--text-2-dark: #b4b4b4; /* gray-11-dark */
}

View File

@@ -0,0 +1,58 @@
copyright = "© Your Name"
defaultContentLanguage = "en"
disableKinds = ["taxonomy"]
enableRobotsTXT = true
languageCode = "en"
timeZone = "Europe/Berlin"
timeout = "120s"
title = "Gallery"
[params]
defaultTheme = "dark"
description = "Example site for hugo-theme-gallery"
title = "My Gallery"
[params.author]
email = "user@example.com"
name = "Your Name"
[params.socialIcons]
facebook = "https://www.facebook.com/"
instagram = "https://www.instagram.com/"
github = "https://github.com/nicokaiser/hugo-theme-gallery/"
youtube = "https://www.youtube.com/"
email = "mailto:user@example.com"
#website = "https://example.com"
#mastodon = "https://example.com"
#pixelfed = "https://example.com"
[params.gallery]
#boxSpacing = 10
#targetRowHeight = 288
#targetRowHeightTolerance = 0.25
[outputs]
home = ["HTML", "RSS"]
page = ["HTML"]
section = ["HTML"]
[imaging]
quality = 75
resampleFilter = "CatmullRom"
[imaging.exif]
disableDate = false
disableLatLong = true
includeFields = "ImageDescription|Orientation"
[module]
[module.hugoVersion]
min = "0.121.2"
[[module.imports]]
path = "github.com/nicokaiser/hugo-theme-gallery/v4"
[menu]
[[menu.footer]]
name = "GitHub"
url = "https://github.com/nicokaiser/hugo-theme-gallery/"
weight = 3
[services]
[services.rss]
limit = 100

View File

@@ -0,0 +1,12 @@
---
description: An example site for hugo-theme-gallery. Images from Unsplash.
#lastmod: 2023-07-05
title: Hugo Gallery
featured_image: martin-martz-wRuhOOaG-Z4-unsplash.jpg # default: first image in this directory
# featured_image on the home page is used for OpenGraph cards, etc.
menus:
main:
name: Home
weight: -1
# sub-galleries on list pages are sorted by date and weight (descending)
---

View File

@@ -0,0 +1,10 @@
---
layout: page
rss_ignore: true
title: About
menu:
main:
weight: 90
---
This is a demonstration site for the Hugo Gallery theme.

View File

@@ -0,0 +1,9 @@
---
description: This is the "Animals" album. It has two sub-albums.
featured_image: janis-ringli-UC1pzyJFyvs-unsplash.jpg
keywords: [Animals, Photos, Cats, Dogs]
title: Animals
weight: 1
menus: "main"
# list pages require at least one image to be displayed.
---

View File

@@ -0,0 +1,17 @@
---
date: 2023-04-01
featured_image: manja-vitolic-gKXKBY-C-Dk-unsplash.jpg
title: Cats
#type: gallery
sort_by: Name
categories: ["animals", "nature"]
resources:
- src: alexander-london-mJaD10XeD7w-unsplash.jpg
title: Brown tabby cat on white stairs by Alexander London
- src: amber-kipp-75715CVEJhI-unsplash.jpg
title: Selective focus photography of orange and white cat on brown table by Amber Kipp
- src: manja-vitolic-gKXKBY-C-Dk-unsplash.jpg
title: "Gipsy the Cat was sitting on a bookshelf one afternoon and just stared right at me, kinda saying: “Will you take a picture already?”"
- src: michael-sum-LEpfefQf4rU-unsplash.jpg
title: This is the cutest and loveliest cat I have ever met in my life. He is BU BU, a cat with 6 fingers, which is unusual, but in fact, smarter than any cat. He meows every time he sees me, and jumps to my bed and sits with me.
---

View File

@@ -0,0 +1,7 @@
---
date: 2023-01-12
featured_image: milli-2l0CWTpcChI-unsplash.jpg
title: Dogs
categories: ["animals", "nature"]
#type: gallery
---

View File

@@ -0,0 +1,4 @@
---
title: Animals
description: Categories can also have custom titles and descriptions. The description of the Animals category lives in `content/categories/animals/_index.md`.
---

View File

@@ -0,0 +1,11 @@
---
description: Fashion and Beauty are a powerful form of self-expression. This category documents style through inspiring shots of street fashion, skincare products, avant-garde editorial photographs, and more.
menus: "main"
title: Fashion & Beauty
#type: gallery
weight: 2
featured_image: mina-rad-V94CguEmeos-unsplash.jpg
categories: ["beauty", "fashion"]
params:
theme: light
---

View File

@@ -0,0 +1,8 @@
---
date: 2023-01-12
featured_image: milli-2l0CWTpcChI-unsplash.jpg
title: Featured Album
featured: true
private: true # do not show in list, only as feature
description: This is a featured album. It is private, so it is only shown on the homepage.
---

View File

@@ -0,0 +1,10 @@
---
title: Imprint
rss_ignore: true
layout: page
menu:
footer:
weight: 1
---
(Put your imprint here)

View File

@@ -0,0 +1,13 @@
---
description: Through photography, the beauty of Mother Nature can be frozen in time. This category celebrates the magic of our planet and beyond — from the immensity of the great outdoors, to miraculous moments in your own backyard.
featured_image: azzedine-rouichi-ZS_XuDZmxpM-unsplash.jpg
menus: "main"
sort_by: Name # Exif.Date
sort_order: desc
title: Nature
#type: gallery
categories: ["nature"]
weight: 3
params:
theme: dark
---

View File

@@ -0,0 +1,6 @@
---
description: A private gallery that is only available by direct link.
private: true # This gallery does not show in lists, RSS, sitemaps, etc. On list pages, use cascade to hide descendants.
title: Private
#type: gallery
---

View File

@@ -0,0 +1,7 @@
module github.com/nicokaiser/hugo-gallery-starter
go 1.20
require github.com/nicokaiser/hugo-theme-gallery/v4 v4.0.0 // indirect
replace github.com/nicokaiser/hugo-theme-gallery/v4 => ../

View File

@@ -0,0 +1 @@
<!-- Custom <head> tags (e.g. analytics code) -->

View File

@@ -0,0 +1,7 @@
{
"private": "true",
"scripts": {
"build": "hugo --gc --minify",
"dev": "hugo server"
}
}

View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Home
wget --no-clobber --content-disposition --directory-prefix=content "https://unsplash.com/photos/wRuhOOaG-Z4/download?&force=true&w=1920"
# Animals
wget --no-clobber --content-disposition --directory-prefix=content/animals "https://unsplash.com/photos/UC1pzyJFyvs/download?&force=true&w=1920"
# Animals/Cats
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/gKXKBY-C-Dk/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/75715CVEJhI/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/mJaD10XeD7w/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/CEx86maLUSc/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/yMSecCHsIBc/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/rW-I87aPY5Y/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/p6yH8VmGqxo/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/LEpfefQf4rU/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/cats "https://unsplash.com/photos/nKC772R_qog/download?&force=true&w=1920"
exiftool -ImageDescription="Brown tabby cat on white stairs by Alexander London" content/animals/cats/alexander-london-mJaD10XeD7w-unsplash.jpg
exiftool -ImageDescription="selective focus photography of orange and white cat on brown table by Amber Kipp" content/animals/cats/amber-kipp-75715CVEJhI-unsplash.jpg
# Animals/Dogs
wget --no-clobber --content-disposition --directory-prefix=content/animals/dogs "https://unsplash.com/photos/Sg3XwuEpybU/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/dogs "https://unsplash.com/photos/Mv9hjnEUHR4/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/dogs "https://unsplash.com/photos/2l0CWTpcChI/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/animals/dogs "https://unsplash.com/photos/WX4i1Jq_o0Y/download?&force=true&w=1920"
# Fashion & Beauty
wget --no-clobber --content-disposition --directory-prefix=content/fashion-beauty "https://unsplash.com/photos/FkxXePJJH5g/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/fashion-beauty "https://unsplash.com/photos/63obHScZWZw/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/fashion-beauty "https://unsplash.com/photos/7gqnlnCTvlg/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/fashion-beauty "https://unsplash.com/photos/V94CguEmeos/download?&force=true&w=1920"
# Nature
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/ZS_XuDZmxpM/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/U7BG3FOT5r8/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/TUzsO59UFpo/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/P45gR9kH0SM/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/k_PbdrEs79g/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/4f8u5mFGKjw/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/f_C_lFxqThI/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/x7CyIC2jUaE/download?&force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/nature "https://unsplash.com/photos/RuaRTaKi-D4/download?force=true&w=1920"
# Private
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/gRknIewfaBs/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/QQ0naV2n-mg/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/_lQgFjsTgEs/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/7pYqHNp37Pg/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/6dH1__uRYtg/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/private "https://unsplash.com/photos/t2WImwH1Fa0/download?force=true&w=1920"
# Featured
wget --no-clobber --content-disposition --directory-prefix=content/featured-album "https://unsplash.com/photos/fcwZsjyqp0s/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/featured-album "https://unsplash.com/photos/IFlBNNOLHUo/download?force=true&w=1920"
wget --no-clobber --content-disposition --directory-prefix=content/featured-album "https://unsplash.com/photos/pjszS6Q2g_Y/download?force=true&w=1920"

3
themes/gallery/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/nicokaiser/hugo-theme-gallery/v4
go 1.20

View File

@@ -0,0 +1,17 @@
menu: Menu
home: Hjem
pageNotFound: Siden blev ikke fundet
toHomepage: Til forsiden
closeTitle: Luk
zoomTitle: Zoom
arrowPrevTitle: Forrige
arrowNextTitle: Næste
errorMsg: Billedet kunne ikke indlæses
downloadTitle: Download
relatedAlbums: Lignende albums
photoCount:
one: 1 billede
other: "{{ . }} billeder"
albumCount:
one: "{{ .photoCount }} i {{ .count }} album"
other: "{{ .photoCount }} i {{ .count }} albummer"

View File

@@ -0,0 +1,17 @@
menu: Menü
home: Home
pageNotFound: Seite nicht gefunden
toHomepage: Zur Startseite
closeTitle: Schließen
zoomTitle: Vergrößern
arrowPrevTitle: Zurück
arrowNextTitle: Weiter
errorMsg: Das Bild konnte nicht geladen werden
downloadTitle: Download
relatedAlbums: Ähnliche Alben
photoCount:
one: 1 Foto
other: "{{ . }} Fotos"
albumCount:
one: "{{ .photoCount }} in {{ .count }} Album"
other: "{{ .photoCount }} in {{ .count }} Alben"

View File

@@ -0,0 +1,17 @@
menu: Menu
home: Home
pageNotFound: Page not found
toHomepage: Go to homepage
closeTitle: Close
zoomTitle: Zoom
arrowPrevTitle: Previous
arrowNextTitle: Next
errorMsg: The photo cannot be loaded
downloadTitle: Download
relatedAlbums: Related albums
photoCount:
one: 1 photo
other: "{{ . }} photos"
albumCount:
one: "{{ .photoCount }} in {{ .count }} album"
other: "{{ .photoCount }} in {{ .count }} albums"

View File

@@ -0,0 +1,17 @@
menu: Menu
home: Accueil
pageNotFound: Page non trouvée
toHomepage: Aller à l'accueil
closeTitle: Fermer
zoomTitle: Zoom
arrowPrevTitle: Retour
arrowNextTitle: Suivant
errorMsg: L'image n'a pas pu être chargée
downloadTitle: Télécharger
relatedAlbums: Albums similaires
photoCount:
one: 1 photo
other: "{{ . }} photos"
albumCount:
one: "{{ .photoCount }} dans {{ .count }} album"
other: "{{ .photoCount }} dans {{ .count }} albums"

View File

@@ -0,0 +1,17 @@
menu: Menu
home: Home
pageNotFound: Pagina non trovata
toHomepage: Vai alla home
closeTitle: Chiudi
zoomTitle: Zoom
arrowPrevTitle: Indietro
arrowNextTitle: Avanti
errorMsg: La foto non può essere scaricata
downloadTitle: Download
relatedAlbums: Album correlati
photoCount:
one: 1 foto
other: "{{ . }} foto"
albumCount:
one: "{{ .photoCount }} in {{ .count }} album"
other: "{{ .photoCount }} in {{ .count }} album"

View File

@@ -0,0 +1,15 @@
menu: メニュー
home: ホーム
pageNotFound: ページが見つかりません
toHomepage: ホームに戻る
closeTitle: 閉める
zoomTitle: ズーム
arrowPrevTitle:
arrowNextTitle:
errorMsg: 写真を読み込めませんでした
downloadTitle: ダウンロード
relatedAlbums: 似たようなアルバム
photoCount:
other: "写真{{ . }}枚"
albumCount:
other: "{{ .count }}枚のアルバムに{{ .photoCount }}"

View File

@@ -0,0 +1,17 @@
menu: Meny
home: Start
pageNotFound: Side ikke funnt
toHomepage: Gå til start
closeTitle: Steng
zoomTitle: Zoom
arrowPrevTitle: Forrige
arrowNextTitle: Neste
errorMsg: Bildet kan ikke vises
downloadTitle: Last ned
relatedAlbums: Lignende album
photoCount:
one: 1 photo
other: "{{ . }} fotos"
albumCount:
one: "{{ .photoCount }} i {{ .count }} album"
other: "{{ .photoCount }} i {{ .count }} album"

View File

@@ -0,0 +1,17 @@
menu: Меню
home: Головна
pageNotFound: Сторінку не знайдено
toHomepage: Перейти на головну сторінку
closeTitle: Закрити
zoomTitle: Масштабувати
arrowPrevTitle: Попередній
arrowNextTitle: Наступний
errorMsg: Фото не вдалося завантажити
downloadTitle: Завантажити
relatedAlbums: Схожі альбоми
photoCount:
one: 1 фото
other: "{{ . }} фото"
albumCount:
one: "{{ .photoCount }} в {{ .count }} альбомі"
other: "{{ .photoCount }} в {{ .count }} альбомах"

View File

@@ -0,0 +1,15 @@
menu: 菜单
home: 主页
pageNotFound: 页面不存在
toHomepage: 返回主页
closeTitle: 关闭
zoomTitle: 缩放
arrowPrevTitle: 前一张
arrowNextTitle: 后一张
errorMsg: 照片无法加载
downloadTitle: 下载
relatedAlbums: 相关相册
photoCount:
other: "{{ . }} 张照片"
albumCount:
other: "{{ .count }} 个相册,共计 {{ .photoCount }}"

Binary file not shown.

BIN
themes/gallery/images/tn.jpg LFS Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
{{ define "main" }}
<hgroup>
<h1>404</h1>
<p>{{ T "pageNotFound" }}</p>
</hgroup>
{{ end }}

View File

@@ -0,0 +1,12 @@
<!doctype html>
{{- $theme := .Params.theme | default .Site.Params.defaultTheme }}
<html class="{{- if (eq $theme "light") -}}light{{- else if (eq $theme "dark") }}dark{{ end }}" lang="{{- site.LanguageCode | default "en" -}}">
{{ partial "head.html" . }}
<body>
{{ block "header" . }}{{ partial "header.html" . }}{{ end }}
<main>
{{ block "main" . }}{{ end }}
</main>
{{ block "footer" . }}{{ partialCached "footer.html" . }}{{ end }}
</body>
</html>

View File

@@ -0,0 +1,10 @@
{{ define "main" }}
{{ partial "title.html" . }}
{{ partial "categories.html" }}
{{ partial "featured.html" . }}
<section class="galleries">
{{ range where .Pages "Params.private" "ne" true }}
{{ partial "album-card.html" . }}
{{ end }}
</section>
{{ end }}

View File

@@ -0,0 +1,8 @@
{{ define "main" }}
{{ partial "title.html" . }}
<section class="galleries">
{{ range where .Pages "Params.private" "ne" true }}
{{ partial "album-card.html" . }}
{{ end }}
</section>
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "main" }}
{{ partial "title.html" . }}
<section class="prose">
{{ .Content }}
</section>
{{ end }}

View File

@@ -0,0 +1,54 @@
{{- $authorEmail := site.Params.author.email -}}
{{- $authorName := site.Params.author.name }}
{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = where $pctx.RegularPages "Params.private" "ne" true }}
{{- else -}}
{{- $pages = where $pctx.Pages "Params.private" "ne" true }}
{{- end -}}
{{- $pages = where $pages "Params.rss_ignore" "ne" true -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with site.Params.author.email }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}}
{{- range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
{{- $images := .Resources.ByType "image" -}}
{{- if gt (len $images) 0 -}}
{{- $featured := ($images.GetMatch (.Params.featured_image | default "*feature*")) | default (index $images 0) -}}
{{- $thumbnail := $featured.Fill "900x600" -}}
<media:content url="{{ $thumbnail.Permalink }}" type="image/jpeg"/>
<description>
&lt;img src=&quot;{{ $thumbnail.Permalink }}&quot; /&gt;
&lt;p&gt;{{ .Params.description | html }}&lt;/p&gt;
</description>
{{- else -}}
<description>{{ .Params.description | html }}</description>
{{- end -}}
</item>
{{- end }}
</channel>
</rss>

View File

@@ -0,0 +1,5 @@
{{ define "main" }}
{{ partial "title.html" . }}
{{ partial "gallery.html" . }}
{{ partial "related.html" . }}
{{ end }}

View File

@@ -0,0 +1,24 @@
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{{ range where .Data.Pages "Params.private" "ne" true }}
{{- if .Permalink -}}
<url>
<loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.LanguageCode }}"
href="{{ .Permalink }}"
/>{{ end }}
<xhtml:link
rel="alternate"
hreflang="{{ .Language.LanguageCode }}"
href="{{ .Permalink }}"
/>{{ end }}
</url>
{{- end -}}
{{ end }}
</urlset>

View File

@@ -0,0 +1,18 @@
{{ $gallery := partial "get-gallery.html" . }}
{{ with $gallery }}
<a class="card" href="{{ .page.RelPermalink }}" title="{{ .page.Title }}">
<figure style="background-color: {{ .color }}">
<img class="lazyload" width="{{ .thumbnail.Width }}" height="{{ .thumbnail.Height }}" data-src="{{ .thumbnail.RelPermalink }}" alt="{{ .page.Title }}" />
</figure>
<div>
<h2>{{ .page.Title }}</h2>
<p>
{{ if gt .albumCount 0 }}
{{ T "albumCount" (dict "count" (.albumCount | lang.FormatNumber 0) "photoCount" (.imageCount | lang.FormatNumber 0 | lang.Translate "photoCount")) }}
{{ else }}
{{ T "photoCount" (.imageCount | lang.FormatNumber 0) }}
{{ end }}
</p>
</div>
</a>
{{ end }}

View File

@@ -0,0 +1,9 @@
{{ with site.Taxonomies.categories }}
<nav class="categories">
<ul>
{{ range . }}
<li><a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a></li>
{{ end }}
</ul>
</nav>
{{ end }}

View File

@@ -0,0 +1,23 @@
{{ range where site.Pages.ByDate.Reverse "Params.featured" "=" true | first 1 }}
{{ $gallery := partial "get-gallery.html" . }}
{{ if $gallery }}
{{ $images := .Resources.ByType "image" }}
{{ $featured := ($images.GetMatch (.Params.featured_image | default "*feature*")) | default (index $images 0) }}
{{ $thumbnail := $featured.Filter (slice images.AutoOrient (images.Process "fit 1600x1600")) }}
{{ $color := index $thumbnail.Colors 0 | default "transparent" }}
<section class="featured">
<a class="featured-card" href="{{ .RelPermalink }}" style="background-color: {{ $color }}; background-image: url({{ $thumbnail.RelPermalink }})">
<div>
<h2>{{ .Title }}</h2>
<p>
{{ if gt $gallery.albumCount 0 }}
{{ T "albumCount" (dict "count" ($gallery.albumCount | lang.FormatNumber 0) "photoCount" ($gallery.imageCount | lang.FormatNumber 0 | lang.Translate "photoCount")) }}
{{ else }}
{{ T "photoCount" ($gallery.imageCount | lang.FormatNumber 0) }}
{{ end }}
</p>
</div>
</a>
</section>
{{ end }}
{{ end }}

View File

@@ -0,0 +1,11 @@
<footer>
{{ partialCached "social-icons.html" . }}
<section>
{{ .Site.Copyright }}
{{ range .Site.Menus.footer }}
<a {{ with .Params.rel -}}rel="{{ . }}"{{ end -}} href="{{ .URL }}">
{{ .Name }}
</a>
{{ end }}
</section>
</footer>

View File

@@ -0,0 +1,44 @@
<section class="gallery">
<div id="gallery" style="visibility: hidden; height: 1px; overflow: hidden">
{{ $images := slice }}
{{ range $image := .Resources.ByType "image" }}
{{ $title := "" }}
{{ $date := "" }}
{{ with $image.Exif }}
{{ $date = .Date }}
{{ with .Tags.ImageDescription }}
{{/* Title from EXIF ImageDescription */}}
{{ $title = . }}
{{ end }}
{{ end }}
{{ if ne $image.Title $image.Name }}
{{/* Title from front matter */}}
{{ $title = $image.Title }}
{{ end }}
{{ $images = $images | append (dict
"Name" $image.Name
"Title" $title
"Date" $date
"image" $image
)
}}
{{ end }}
{{ range sort $images (.Params.sort_by | default "Name") (.Params.sort_order | default "asc") }}
{{ $image := .image }}
{{ $thumbnail := $image.Filter (slice images.AutoOrient (images.Process "fit 600x600")) }}
{{ $full := $image.Filter (slice images.AutoOrient (images.Process "fit 1600x1600")) }}
{{ $color := index $thumbnail.Colors 0 | default "transparent" }}
<a class="gallery-item" href="{{ $image.RelPermalink }}" data-pswp-src="{{ $full.RelPermalink }}" data-pswp-width="{{ $full.Width }}" data-pswp-height="{{ $full.Height }}" title="{{ .Title }}" itemscope itemtype="https://schema.org/ImageObject" style="aspect-ratio: {{ $thumbnail.Width }} / {{ $thumbnail.Height }}">
<figure style="background-color: {{ $color }}; aspect-ratio: {{ $thumbnail.Width }} / {{ $thumbnail.Height }}">
<img class="lazyload" width="{{ $thumbnail.Width }}" height="{{ $thumbnail.Height }}" data-src="{{ $thumbnail.RelPermalink }}" alt="{{ .Title }}" />
</figure>
<meta itemprop="contentUrl" content="{{ $image.RelPermalink }}" />
{{ with site.Params.Author }}
<span itemprop="creator" itemtype="https://schema.org/Person" itemscope>
<meta itemprop="name" content="{{ site.Params.Author.name }}" />
</span>
{{ end }}
</a>
{{ end }}
</div>
</section>

View File

@@ -0,0 +1,26 @@
{{ $gallery := "" }}
{{ $images := .Resources.ByType "image" }}
{{ if gt (len $images) 0 }}
{{ $featured := ($images.GetMatch (.Params.featured_image | default "*feature*")) | default (index $images 0) }}
{{ $thumbnail := $featured.Filter (slice images.AutoOrient (images.Process "fit 600x600")) }}
{{ $color := index $thumbnail.Colors 0 | default "transparent" }}
{{ $imageCount := 0 }}
{{ $albumCount := 0 }}
{{ if .IsPage }}
{{ $imageCount = len $images }}
{{ else }}
{{ range where .RegularPagesRecursive "Params.private" "ne" true }}
{{ $albumCount = add $albumCount 1 }}
{{ $imageCount = add $imageCount (len (.Resources.ByType "image")) }}
{{ end }}
{{ end }}
{{ $gallery = dict
"page" $
"images" $images
"thumbnail" $thumbnail
"color" $color
"albumCount" $albumCount
"imageCount" $imageCount
}}
{{ end }}
{{ return $gallery }}

View File

@@ -0,0 +1,38 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ with .Title }}{{ . }} -{{ end }} {{ .Site.Title }}</title>
<link rel="canonical" href="{{ .Permalink }}" />
<link rel="icon" type="image/svg+xml" href="{{ "images/favicon.svg" | relURL }}" />
<link rel="icon" type="image/png" href="{{ "images/favicon.png" | relURL }}" />
<link rel="apple-touch-icon" sizes="180x180" href="{{ "images/apple-touch-icon.png" | relURL }}" />
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}" />
{{ if .Params.keywords }}
<meta name="keywords" content="{{ delimit .Params.keywords `, ` }}" />
{{ end }}
{{ if .Params.private }}
<meta name="robots" content="noindex, nofollow" />
{{ else }}
<meta name="robots" content="index, follow" />
{{ end }}
{{ range .AlternativeOutputFormats -}}
{{ printf `<link rel="%s" type="%s" href="%s" title="%s" />` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }}
{{ end -}}
{{ partial "opengraph.html" . }}
{{ $css := resources.Get "/css/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}" />
{{ $params := dict
"closeTitle" (i18n "closeTitle")
"zoomTitle" (i18n "zoomTitle")
"arrowPrevTitle" (i18n "arrowPrevTitle")
"arrowNextTitle" (i18n "arrowNextTitle")
"errorMsg" (i18n "errorMsg")
"downloadTitle" (i18n "downloadTitle")
"boxSpacing" (default 10 .Site.Params.gallery.boxSpacing)
"targetRowHeight" (default 288 .Site.Params.gallery.targetRowHeight)
"targetRowHeightTolerance" (default 0.25 .Site.Params.gallery.targetRowHeightTolerance)
}}
{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" true "params" $params) | resources.Fingerprint }}
<script src="{{ $js.RelPermalink }}" defer></script>
{{- partial "head-custom.html" . -}}
</head>

View File

@@ -0,0 +1,28 @@
<header>
{{ with .Parent }}
<a class="btn btn-square" href="{{ .RelPermalink | default .Site.Home.RelPermalink }}" title="{{ .Title }}">
<svg width="24" height="24" data-slot="icon" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path clip-rule="evenodd" fill-rule="evenodd" d="M11.03 3.97a.75.75 0 0 1 0 1.06l-6.22 6.22H21a.75.75 0 0 1 0 1.5H4.81l6.22 6.22a.75.75 0 1 1-1.06 1.06l-7.5-7.5a.75.75 0 0 1 0-1.06l7.5-7.5a.75.75 0 0 1 1.06 0Z"></path>
</svg>
</a>
{{ else }}
<a class="btn" href="{{ .Site.Home.RelPermalink }}">
{{ .Site.Title }}
</a>
{{ end }}
{{ if site.Menus.main }}
<ul>
<li>
<button class="btn btn-square group" id="menu-toggle" aria-expanded="false" type="button" title="{{ T "menu" }}">
<svg class="group-aria-expanded:hidden" width="24" height="24" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path clip-rule="evenodd" fill-rule="evenodd" d="M3 6.75A.75.75 0 0 1 3.75 6h16.5a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 6.75ZM3 12a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 12Zm0 5.25a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z"></path>
</svg>
<svg class="hidden group-aria-expanded:block" width="24" height="24" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path clip-rule="evenodd" fill-rule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
</li>
</ul>
{{ end }}
</header>
{{ partial "menu.html" . }}

View File

@@ -0,0 +1,11 @@
{{ with site.Menus.main }}
<menu class="hidden" id="menu">
{{ range . }}
<li itemscope itemtype="http://www.schema.org/SiteNavigationElement">
<a aria-current="{{ or (page.IsMenuCurrent .Menu .) (page.HasMenuCurrent .Menu .) }}" href="{{ .URL }}" itemprop="url">
<span itemprop="name">{{ .Name }}</span>
</a>
</li>
{{ end }}
</menu>
{{ end }}

View File

@@ -0,0 +1,20 @@
<meta property="og:title" content="{{ if .IsHome }}{{ with .Site.Params.title }}{{ . }}{{ end }}{{ else }}{{ .Title }}{{ end }}" />
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ if .IsPage }}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta property="og:url" content="{{ .Permalink }}" />
{{- $images := $.Resources.ByType "image" -}}
{{ $featured := ($images.GetMatch (.Params.featured_image | default "*feature*")) | default (index $images 0) }}
{{- with $featured -}}
<meta property="og:image" content="{{ $featured.Permalink }}" />
{{- end -}}
{{- if .IsPage }}
{{- $iso8601 := "2006-01-02T15:04:05-07:00" -}}
<meta property="article:section" content="{{ .Section }}" />
{{ with .PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{ with .Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }} />{{ end }}
{{- end -}}
{{- with .Params.locale }}<meta property="og:locale" content="{{ . }}" />{{ end }}
{{- with .Site.Params.title }}<meta property="og:site_name" content="{{ . }}" />{{ end }}

View File

@@ -0,0 +1,11 @@
{{ $related := where (site.RegularPages.Related .) "Params.private" "ne" true | first 3 }}
{{ with $related }}
<hgroup style="margin-top: 8rem">
<h2>{{ T "relatedAlbums" }}</h2>
</hgroup>
<section class="galleries">
{{ range . }}
{{ partial "album-card.html" . }}
{{ end }}
</section>
{{ end }}

View File

@@ -0,0 +1,79 @@
{{ with .Site.Params.socialIcons }}
<section class="social-icons">
{{ with .website }}
<a target="_blank" rel="noopener" title="Website" href="{{ . }}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<path fill="currentColor" d="M8.904 16.5h6.192C14.476 19.773 13.235 22 12 22c-1.197 0-2.4-2.094-3.038-5.204zh6.192zm-5.838.001h4.306c.364 2.082.983 3.854 1.792 5.093a10.029 10.029 0 0 1-5.952-4.814zm13.563 0h4.305a10.028 10.028 0 0 1-6.097 5.093c.755-1.158 1.344-2.778 1.715-4.681zh4.305zm.302-6.5h4.87a10.047 10.047 0 0 1-.257 5H16.84a28.676 28.676 0 0 0 .13-4.344zh4.87zM2.2 10h4.87a28.211 28.211 0 0 0 .033 4.42l.057.58H2.456a10.047 10.047 0 0 1-.258-5m6.377 0h6.849a25.838 25.838 0 0 1-.037 4.425l-.062.575H8.674a25.987 25.987 0 0 1-.132-4.512zh6.849zm6.368-7.424l-.108-.17A10.027 10.027 0 0 1 21.373 8.5h-4.59c-.316-2.416-.957-4.492-1.838-5.923l-.108-.17zm-5.902-.133l.122-.037c-.88 1.351-1.535 3.33-1.883 5.654l-.062.44H2.63a10.028 10.028 0 0 1 6.413-6.057l.122-.037zM12 2.002c1.319 0 2.646 2.542 3.214 6.183l.047.315H8.739C9.28 4.691 10.644 2.002 12 2.002" />
</svg>
</a>
{{ end }}
{{ with .mastodon }}
<a rel="me" target="_blank" rel="noopener" title="Mastodon" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" aria-hidden="true">
<path d="M433 179.1c0-97.2-63.7-125.7-63.7-125.7-62.5-28.7-228.6-28.4-290.5 0 0 0-63.7 28.5-63.7 125.7 0 115.7-6.6 259.4 105.6 289.1 40.5 10.7 75.3 13 103.3 11.4 50.8-2.8 79.3-18.1 79.3-18.1l-1.7-36.9s-36.3 11.4-77.1 10.1c-40.4-1.4-83-4.4-89.6-54a102.5 102.5 0 0 1 -.9-13.9c85.6 20.9 158.7 9.1 178.8 6.7 56.1-6.7 105-41.3 111.2-72.9 9.8-49.8 9-121.5 9-121.5zm-75.1 125.2h-46.6v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.3V197c0-58.5-64-56.6-64-6.9v114.2H90.2c0-122.1-5.2-147.9 18.4-175 25.9-28.9 79.8-30.8 103.8 6.1l11.6 19.5 11.6-19.5c24.1-37.1 78.1-34.8 103.8-6.1 23.7 27.3 18.4 53 18.4 175z" />
</svg>
</a>
{{ end }}
{{ with .pixelfed }}
<a rel="me" target="_blank" rel="noopener" title="Pixelfed" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 80 1034 1024">
<path fill="currentColor" d="M500 176q-115 0 -215 58q-96 57 -152 153q-58 99 -58 214.5t58 214.5q56 96 152 152q100 58 215 58t215 -58q96 -56 152 -152q58 -99 58 -214.5t-58 -214.5q-56 -96 -152 -153q-100 -58 -215 -58zM432 435h112q36 0 66.5 17.5t48.5 47t18 65t-18 65t-48.5 47t-66.5 17.5 h-78l-111 106v-290q0 -31 22.5 -53t54.5 -22z" />
</svg>
</a>
{{ end }}
{{ with .facebook }}
<a target="_blank" rel="noopener" title="Facebook" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd" d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z" clip-rule="evenodd"></path>
</svg>
</a>
{{ end }}
{{ with .instagram }}
<a target="_blank" rel="noopener" title="Instagram" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
fill-rule="evenodd"
d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"
clip-rule="evenodd"
></path>
</svg>
</a>
{{ end }}
{{ with .github }}
<a target="_blank" rel="noopener" title="GitHub" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path>
</svg>
</a>
{{ end }}
{{ with .twitter }}
<a target="_blank" rel="noopener" title="Twitter/X" href="{{ . }}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true">
<path fill="currentColor" d="M10.488 14.651L15.25 21h7l-7.858-10.478L20.93 3h-2.65l-5.117 5.886L8.75 3h-7l7.51 10.015L2.32 21h2.65zM16.25 19L5.75 5h2l10.5 14z" />
</svg>
</a>
{{ end }}
{{ with .youtube }}
<a target="_blank" rel="noopener" title="YouTube" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd" d="M19.812 5.418c.861.23 1.538.907 1.768 1.768C21.998 8.746 22 12 22 12s0 3.255-.418 4.814a2.504 2.504 0 0 1-1.768 1.768c-1.56.419-7.814.419-7.814.419s-6.255 0-7.814-.419a2.505 2.505 0 0 1-1.768-1.768C2 15.255 2 12 2 12s0-3.255.417-4.814a2.507 2.507 0 0 1 1.768-1.768C5.744 5 11.998 5 11.998 5s6.255 0 7.814.418ZM15.194 12 10 15V9l5.194 3Z" clip-rule="evenodd"></path>
</svg>
</a>
{{ end }}
{{ with .linkedin }}
<a target="_blank" rel="noopener" title="LinkedIn" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
</svg>
</a>
{{ end }}
{{ with .email }}
<a title="E-Mail" href="{{ . }}">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"></path>
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"></path>
</svg>
</a>
{{ end }}
</section>
{{ end }}

View File

@@ -0,0 +1,8 @@
{{ if .Title }}
<hgroup>
<h1>{{ .Title }}</h1>
{{ with .Params.Description }}
<p>{{ . }}</p>
{{ end }}
</hgroup>
{{ end }}

View File

@@ -0,0 +1,16 @@
{
"devDependencies": {
"prettier": "^3.3.3",
"prettier-plugin-css-order": "^2.1.2",
"prettier-plugin-go-template": "^0.0.15",
"prettier-plugin-organize-attributes": "^1.0.0",
"stylelint": "^16.10.0",
"stylelint-config-standard-scss": "^13.1.0"
},
"private": "true",
"scripts": {
"prettier": "prettier . --write",
"lint": "stylelint assets/css/*.{css,scss}"
},
"version": "4.3.3"
}

View File

@@ -0,0 +1,15 @@
/** @type {import('prettier').Config} */
module.exports = {
plugins: ["prettier-plugin-go-template", "prettier-plugin-organize-attributes", "prettier-plugin-css-order"],
printWidth: 1000,
tabWidth: 2,
overrides: [
{
files: ["*.html"],
options: {
parser: "go-template",
},
},
],
cssDeclarationSorterCustomOrder: ["concentric-css"],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="24" height="24"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path d="M12 9a3.75 3.75 0 100 7.5A3.75 3.75 0 0012 9z"></path>
<path fill-rule="evenodd" d="M9.344 3.071a49.52 49.52 0 015.312 0c.967.052 1.83.585 2.332 1.39l.821 1.317c.24.383.645.643 1.11.71.386.054.77.113 1.152.177 1.432.239 2.429 1.493 2.429 2.909V18a3 3 0 01-3 3h-15a3 3 0 01-3-3V9.574c0-1.416.997-2.67 2.429-2.909.382-.064.766-.123 1.151-.178a1.56 1.56 0 001.11-.71l.822-1.315a2.942 2.942 0 012.332-1.39zM6.75 12.75a5.25 5.25 0 1110.5 0 5.25 5.25 0 01-10.5 0zm12-1.5a.75.75 0 100-1.5.75.75 0 000 1.5z" clip-rule="evenodd"></path>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: invert(100%); } }
</style></svg>

After

Width:  |  Height:  |  Size: 959 B

12
themes/gallery/theme.toml Normal file
View File

@@ -0,0 +1,12 @@
name = "Gallery"
license = "MIT"
licenselink = "https://github.com/nicokaiser/hugo-theme-gallery/blob/main/LICENSE"
description = "A very simple and opinionated photo gallery theme for Hugo"
homepage = "https://github.com/nicokaiser/hugo-theme-gallery"
demosite = "https://nicokaiser.github.io/hugo-theme-gallery/"
tags = ["gallery", "responsive", "minimal", "dark"]
min_version = "0.121.2"
[author]
name = "Nico Kaiser"
homepage = "https://kaiser.me"