initial commit
This commit is contained in:
3
themes/gallery/.github/FUNDING.yml
vendored
Normal file
3
themes/gallery/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github: nicokaiser
|
||||
ko_fi: nicokaiser
|
||||
liberapay: nicokaiser
|
||||
62
themes/gallery/.github/workflows/hugo.yml
vendored
Normal file
62
themes/gallery/.github/workflows/hugo.yml
vendored
Normal 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
4
themes/gallery/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.hugo_build.lock
|
||||
exampleSite/assets/jsconfig.json
|
||||
6
themes/gallery/.prettierignore
Normal file
6
themes/gallery/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
assets/jsconfig.json
|
||||
assets/js/justified-layout/
|
||||
assets/js/photoswipe/
|
||||
assets/css/photoswipe/
|
||||
exampleSite/resources/
|
||||
exampleSite/public/
|
||||
9
themes/gallery/.stylelintrc.json
Normal file
9
themes/gallery/.stylelintrc.json
Normal 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
21
themes/gallery/LICENSE
Normal 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
250
themes/gallery/README.md
Normal 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)
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 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/)
|
||||
43
themes/gallery/assets/css/_colors.scss
Normal file
43
themes/gallery/assets/css/_colors.scss
Normal 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);
|
||||
}
|
||||
99
themes/gallery/assets/css/_normalize.scss
Normal file
99
themes/gallery/assets/css/_normalize.scss
Normal 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;
|
||||
}
|
||||
428
themes/gallery/assets/css/_styles.scss
Normal file
428
themes/gallery/assets/css/_styles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
themes/gallery/assets/css/custom.css
Normal file
1
themes/gallery/assets/css/custom.css
Normal file
@@ -0,0 +1 @@
|
||||
/* custom.css */
|
||||
20
themes/gallery/assets/css/main.scss
Normal file
20
themes/gallery/assets/css/main.scss
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
420
themes/gallery/assets/css/photoswipe/photoswipe.css
Normal file
420
themes/gallery/assets/css/photoswipe/photoswipe.css
Normal 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;
|
||||
}
|
||||
1
themes/gallery/assets/js/custom.js
Normal file
1
themes/gallery/assets/js/custom.js
Normal file
@@ -0,0 +1 @@
|
||||
/* custom.js */
|
||||
52
themes/gallery/assets/js/gallery.js
Normal file
52
themes/gallery/assets/js/gallery.js
Normal 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();
|
||||
}
|
||||
248
themes/gallery/assets/js/justified-layout/index.js
Normal file
248
themes/gallery/assets/js/justified-layout/index.js
Normal 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 };
|
||||
}
|
||||
}));
|
||||
};
|
||||
333
themes/gallery/assets/js/justified-layout/row.js
Normal file
333
themes/gallery/assets/js/justified-layout/row.js
Normal 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;
|
||||
}
|
||||
|
||||
};
|
||||
805
themes/gallery/assets/js/lazysizes.js
Normal file
805
themes/gallery/assets/js/lazysizes.js
Normal 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;
|
||||
},
|
||||
);
|
||||
83
themes/gallery/assets/js/lightbox.js
Normal file
83
themes/gallery/assets/js/lightbox.js
Normal 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
5
themes/gallery/assets/js/main.js
Normal file
5
themes/gallery/assets/js/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import "./menu.js";
|
||||
import "./gallery.js";
|
||||
import "./lazysizes.js";
|
||||
import "./lightbox.js";
|
||||
import "./custom.js";
|
||||
9
themes/gallery/assets/js/menu.js
Normal file
9
themes/gallery/assets/js/menu.js
Normal 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");
|
||||
});
|
||||
}
|
||||
5
themes/gallery/assets/js/photoswipe/photoswipe-dynamic-caption-plugin.esm.min.js
vendored
Normal file
5
themes/gallery/assets/js/photoswipe/photoswipe-dynamic-caption-plugin.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1960
themes/gallery/assets/js/photoswipe/photoswipe-lightbox.esm.js
Normal file
1960
themes/gallery/assets/js/photoswipe/photoswipe-lightbox.esm.js
Normal file
File diff suppressed because it is too large
Load Diff
7081
themes/gallery/assets/js/photoswipe/photoswipe.esm.js
Normal file
7081
themes/gallery/assets/js/photoswipe/photoswipe.esm.js
Normal file
File diff suppressed because it is too large
Load Diff
10
themes/gallery/assets/jsconfig.json
Normal file
10
themes/gallery/assets/jsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
themes/gallery/exampleSite/.gitignore
vendored
Normal file
6
themes/gallery/exampleSite/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
public/
|
||||
resources/
|
||||
.hugo_build.lock
|
||||
hugo_stats.json
|
||||
content/**/*.jpg
|
||||
11
themes/gallery/exampleSite/README.md
Normal file
11
themes/gallery/exampleSite/README.md
Normal 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
|
||||
```
|
||||
10
themes/gallery/exampleSite/assets/css/custom.css
Normal file
10
themes/gallery/exampleSite/assets/css/custom.css
Normal 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 */
|
||||
}
|
||||
58
themes/gallery/exampleSite/config/_default/hugo.toml
Normal file
58
themes/gallery/exampleSite/config/_default/hugo.toml
Normal 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
|
||||
12
themes/gallery/exampleSite/content/_index.md
Normal file
12
themes/gallery/exampleSite/content/_index.md
Normal 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)
|
||||
---
|
||||
10
themes/gallery/exampleSite/content/about.md
Normal file
10
themes/gallery/exampleSite/content/about.md
Normal 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.
|
||||
9
themes/gallery/exampleSite/content/animals/_index.md
Normal file
9
themes/gallery/exampleSite/content/animals/_index.md
Normal 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.
|
||||
---
|
||||
17
themes/gallery/exampleSite/content/animals/cats/index.md
Normal file
17
themes/gallery/exampleSite/content/animals/cats/index.md
Normal 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.
|
||||
---
|
||||
7
themes/gallery/exampleSite/content/animals/dogs/index.md
Normal file
7
themes/gallery/exampleSite/content/animals/dogs/index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
date: 2023-01-12
|
||||
featured_image: milli-2l0CWTpcChI-unsplash.jpg
|
||||
title: Dogs
|
||||
categories: ["animals", "nature"]
|
||||
#type: gallery
|
||||
---
|
||||
@@ -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`.
|
||||
---
|
||||
11
themes/gallery/exampleSite/content/fashion-beauty/index.md
Normal file
11
themes/gallery/exampleSite/content/fashion-beauty/index.md
Normal 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
|
||||
---
|
||||
@@ -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.
|
||||
---
|
||||
10
themes/gallery/exampleSite/content/imprint.md
Normal file
10
themes/gallery/exampleSite/content/imprint.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Imprint
|
||||
rss_ignore: true
|
||||
layout: page
|
||||
menu:
|
||||
footer:
|
||||
weight: 1
|
||||
---
|
||||
|
||||
(Put your imprint here)
|
||||
13
themes/gallery/exampleSite/content/nature/index.md
Normal file
13
themes/gallery/exampleSite/content/nature/index.md
Normal 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
|
||||
---
|
||||
6
themes/gallery/exampleSite/content/private/index.md
Normal file
6
themes/gallery/exampleSite/content/private/index.md
Normal 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
|
||||
---
|
||||
7
themes/gallery/exampleSite/go.mod
Normal file
7
themes/gallery/exampleSite/go.mod
Normal 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 => ../
|
||||
@@ -0,0 +1 @@
|
||||
<!-- Custom <head> tags (e.g. analytics code) -->
|
||||
7
themes/gallery/exampleSite/package.json
Normal file
7
themes/gallery/exampleSite/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"build": "hugo --gc --minify",
|
||||
"dev": "hugo server"
|
||||
}
|
||||
}
|
||||
57
themes/gallery/exampleSite/pull-images.sh
Executable file
57
themes/gallery/exampleSite/pull-images.sh
Executable 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
3
themes/gallery/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/nicokaiser/hugo-theme-gallery/v4
|
||||
|
||||
go 1.20
|
||||
17
themes/gallery/i18n/da.yaml
Normal file
17
themes/gallery/i18n/da.yaml
Normal 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"
|
||||
17
themes/gallery/i18n/de.yaml
Normal file
17
themes/gallery/i18n/de.yaml
Normal 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"
|
||||
17
themes/gallery/i18n/en.yaml
Normal file
17
themes/gallery/i18n/en.yaml
Normal 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"
|
||||
17
themes/gallery/i18n/fr.yaml
Normal file
17
themes/gallery/i18n/fr.yaml
Normal 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"
|
||||
17
themes/gallery/i18n/it.yaml
Normal file
17
themes/gallery/i18n/it.yaml
Normal 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"
|
||||
15
themes/gallery/i18n/ja.yaml
Normal file
15
themes/gallery/i18n/ja.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
menu: メニュー
|
||||
home: ホーム
|
||||
pageNotFound: ページが見つかりません
|
||||
toHomepage: ホームに戻る
|
||||
closeTitle: 閉める
|
||||
zoomTitle: ズーム
|
||||
arrowPrevTitle: 前
|
||||
arrowNextTitle: 次
|
||||
errorMsg: 写真を読み込めませんでした
|
||||
downloadTitle: ダウンロード
|
||||
relatedAlbums: 似たようなアルバム
|
||||
photoCount:
|
||||
other: "写真{{ . }}枚"
|
||||
albumCount:
|
||||
other: "{{ .count }}枚のアルバムに{{ .photoCount }}"
|
||||
17
themes/gallery/i18n/nb.yaml
Normal file
17
themes/gallery/i18n/nb.yaml
Normal 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"
|
||||
17
themes/gallery/i18n/uk.yaml
Normal file
17
themes/gallery/i18n/uk.yaml
Normal 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 }} альбомах"
|
||||
15
themes/gallery/i18n/zh.yaml
Normal file
15
themes/gallery/i18n/zh.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
menu: 菜单
|
||||
home: 主页
|
||||
pageNotFound: 页面不存在
|
||||
toHomepage: 返回主页
|
||||
closeTitle: 关闭
|
||||
zoomTitle: 缩放
|
||||
arrowPrevTitle: 前一张
|
||||
arrowNextTitle: 后一张
|
||||
errorMsg: 照片无法加载
|
||||
downloadTitle: 下载
|
||||
relatedAlbums: 相关相册
|
||||
photoCount:
|
||||
other: "{{ . }} 张照片"
|
||||
albumCount:
|
||||
other: "{{ .count }} 个相册,共计 {{ .photoCount }}"
|
||||
BIN
themes/gallery/images/screenshot.jpg
LFS
Normal file
BIN
themes/gallery/images/screenshot.jpg
LFS
Normal file
Binary file not shown.
BIN
themes/gallery/images/tn.jpg
LFS
Normal file
BIN
themes/gallery/images/tn.jpg
LFS
Normal file
Binary file not shown.
6
themes/gallery/layouts/404.html
Normal file
6
themes/gallery/layouts/404.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<hgroup>
|
||||
<h1>404</h1>
|
||||
<p>{{ T "pageNotFound" }}</p>
|
||||
</hgroup>
|
||||
{{ end }}
|
||||
12
themes/gallery/layouts/_default/baseof.html
Normal file
12
themes/gallery/layouts/_default/baseof.html
Normal 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>
|
||||
10
themes/gallery/layouts/_default/home.html
Normal file
10
themes/gallery/layouts/_default/home.html
Normal 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 }}
|
||||
8
themes/gallery/layouts/_default/list.html
Normal file
8
themes/gallery/layouts/_default/list.html
Normal 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 }}
|
||||
6
themes/gallery/layouts/_default/page.html
Normal file
6
themes/gallery/layouts/_default/page.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "title.html" . }}
|
||||
<section class="prose">
|
||||
{{ .Content }}
|
||||
</section>
|
||||
{{ end }}
|
||||
54
themes/gallery/layouts/_default/rss.xml
Normal file
54
themes/gallery/layouts/_default/rss.xml
Normal 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>
|
||||
<img src="{{ $thumbnail.Permalink }}" />
|
||||
<p>{{ .Params.description | html }}</p>
|
||||
</description>
|
||||
{{- else -}}
|
||||
<description>{{ .Params.description | html }}</description>
|
||||
{{- end -}}
|
||||
</item>
|
||||
{{- end }}
|
||||
</channel>
|
||||
</rss>
|
||||
5
themes/gallery/layouts/_default/single.html
Normal file
5
themes/gallery/layouts/_default/single.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "title.html" . }}
|
||||
{{ partial "gallery.html" . }}
|
||||
{{ partial "related.html" . }}
|
||||
{{ end }}
|
||||
24
themes/gallery/layouts/_default/sitemap.xml
Normal file
24
themes/gallery/layouts/_default/sitemap.xml
Normal 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>
|
||||
18
themes/gallery/layouts/partials/album-card.html
Normal file
18
themes/gallery/layouts/partials/album-card.html
Normal 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 }}
|
||||
9
themes/gallery/layouts/partials/categories.html
Normal file
9
themes/gallery/layouts/partials/categories.html
Normal 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 }}
|
||||
23
themes/gallery/layouts/partials/featured.html
Normal file
23
themes/gallery/layouts/partials/featured.html
Normal 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 }}
|
||||
11
themes/gallery/layouts/partials/footer.html
Normal file
11
themes/gallery/layouts/partials/footer.html
Normal 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>
|
||||
44
themes/gallery/layouts/partials/gallery.html
Normal file
44
themes/gallery/layouts/partials/gallery.html
Normal 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>
|
||||
26
themes/gallery/layouts/partials/get-gallery.html
Normal file
26
themes/gallery/layouts/partials/get-gallery.html
Normal 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 }}
|
||||
0
themes/gallery/layouts/partials/head-custom.html
Normal file
0
themes/gallery/layouts/partials/head-custom.html
Normal file
38
themes/gallery/layouts/partials/head.html
Normal file
38
themes/gallery/layouts/partials/head.html
Normal 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>
|
||||
28
themes/gallery/layouts/partials/header.html
Normal file
28
themes/gallery/layouts/partials/header.html
Normal 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" . }}
|
||||
11
themes/gallery/layouts/partials/menu.html
Normal file
11
themes/gallery/layouts/partials/menu.html
Normal 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 }}
|
||||
20
themes/gallery/layouts/partials/opengraph.html
Normal file
20
themes/gallery/layouts/partials/opengraph.html
Normal 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 }}
|
||||
11
themes/gallery/layouts/partials/related.html
Normal file
11
themes/gallery/layouts/partials/related.html
Normal 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 }}
|
||||
79
themes/gallery/layouts/partials/social-icons.html
Normal file
79
themes/gallery/layouts/partials/social-icons.html
Normal 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 }}
|
||||
8
themes/gallery/layouts/partials/title.html
Normal file
8
themes/gallery/layouts/partials/title.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{{ if .Title }}
|
||||
<hgroup>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ with .Params.Description }}
|
||||
<p>{{ . }}</p>
|
||||
{{ end }}
|
||||
</hgroup>
|
||||
{{ end }}
|
||||
16
themes/gallery/package.json
Normal file
16
themes/gallery/package.json
Normal 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"
|
||||
}
|
||||
15
themes/gallery/prettier.config.js
Normal file
15
themes/gallery/prettier.config.js
Normal 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"],
|
||||
};
|
||||
BIN
themes/gallery/static/images/apple-touch-icon.png
Normal file
BIN
themes/gallery/static/images/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
themes/gallery/static/images/favicon.png
Normal file
BIN
themes/gallery/static/images/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 B |
6
themes/gallery/static/images/favicon.svg
Normal file
6
themes/gallery/static/images/favicon.svg
Normal 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
12
themes/gallery/theme.toml
Normal 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"
|
||||
Reference in New Issue
Block a user