initial commit
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
github: nicokaiser
|
||||
ko_fi: nicokaiser
|
||||
liberapay: nicokaiser
|
||||
+62
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.hugo_build.lock
|
||||
exampleSite/assets/jsconfig.json
|
||||
@@ -0,0 +1,6 @@
|
||||
assets/jsconfig.json
|
||||
assets/js/justified-layout/
|
||||
assets/js/photoswipe/
|
||||
assets/css/photoswipe/
|
||||
exampleSite/resources/
|
||||
exampleSite/public/
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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/)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* custom.css */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* custom.js */
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}));
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
},
|
||||
);
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import "./menu.js";
|
||||
import "./gallery.js";
|
||||
import "./lazysizes.js";
|
||||
import "./lightbox.js";
|
||||
import "./custom.js";
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
public/
|
||||
resources/
|
||||
.hugo_build.lock
|
||||
hugo_stats.json
|
||||
content/**/*.jpg
|
||||
@@ -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
|
||||
```
|
||||
@@ -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 */
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
---
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
---
|
||||
@@ -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.
|
||||
---
|
||||
@@ -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`.
|
||||
---
|
||||
@@ -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.
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Imprint
|
||||
rss_ignore: true
|
||||
layout: page
|
||||
menu:
|
||||
footer:
|
||||
weight: 1
|
||||
---
|
||||
|
||||
(Put your imprint here)
|
||||
@@ -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
|
||||
---
|
||||
@@ -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
|
||||
---
|
||||
@@ -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) -->
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"build": "hugo --gc --minify",
|
||||
"dev": "hugo server"
|
||||
}
|
||||
}
|
||||
Executable
+57
@@ -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"
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/nicokaiser/hugo-theme-gallery/v4
|
||||
|
||||
go 1.20
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,15 @@
|
||||
menu: メニュー
|
||||
home: ホーム
|
||||
pageNotFound: ページが見つかりません
|
||||
toHomepage: ホームに戻る
|
||||
closeTitle: 閉める
|
||||
zoomTitle: ズーム
|
||||
arrowPrevTitle: 前
|
||||
arrowNextTitle: 次
|
||||
errorMsg: 写真を読み込めませんでした
|
||||
downloadTitle: ダウンロード
|
||||
relatedAlbums: 似たようなアルバム
|
||||
photoCount:
|
||||
other: "写真{{ . }}枚"
|
||||
albumCount:
|
||||
other: "{{ .count }}枚のアルバムに{{ .photoCount }}"
|
||||
@@ -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"
|
||||
@@ -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 }} альбомах"
|
||||
@@ -0,0 +1,15 @@
|
||||
menu: 菜单
|
||||
home: 主页
|
||||
pageNotFound: 页面不存在
|
||||
toHomepage: 返回主页
|
||||
closeTitle: 关闭
|
||||
zoomTitle: 缩放
|
||||
arrowPrevTitle: 前一张
|
||||
arrowNextTitle: 后一张
|
||||
errorMsg: 照片无法加载
|
||||
downloadTitle: 下载
|
||||
relatedAlbums: 相关相册
|
||||
photoCount:
|
||||
other: "{{ . }} 张照片"
|
||||
albumCount:
|
||||
other: "{{ .count }} 个相册,共计 {{ .photoCount }}"
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<hgroup>
|
||||
<h1>404</h1>
|
||||
<p>{{ T "pageNotFound" }}</p>
|
||||
</hgroup>
|
||||
{{ end }}
|
||||
@@ -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>
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "title.html" . }}
|
||||
<section class="prose">
|
||||
{{ .Content }}
|
||||
</section>
|
||||
{{ end }}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "title.html" . }}
|
||||
{{ partial "gallery.html" . }}
|
||||
{{ partial "related.html" . }}
|
||||
{{ end }}
|
||||
@@ -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>
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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,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>
|
||||
@@ -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" . }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,8 @@
|
||||
{{ if .Title }}
|
||||
<hgroup>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ with .Params.Description }}
|
||||
<p>{{ . }}</p>
|
||||
{{ end }}
|
||||
</hgroup>
|
||||
{{ end }}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
plugins: ["prettier-plugin-go-template", "prettier-plugin-organize-attributes", "prettier-plugin-css-order"],
|
||||
printWidth: 1000,
|
||||
tabWidth: 2,
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.html"],
|
||||
options: {
|
||||
parser: "go-template",
|
||||
},
|
||||
},
|
||||
],
|
||||
cssDeclarationSorterCustomOrder: ["concentric-css"],
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 292 B |
@@ -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 |
@@ -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