initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.JPG filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -0,0 +1,7 @@
|
||||
.hugo_build.lock
|
||||
public/
|
||||
resources/_gen/
|
||||
assets/jsconfig.json
|
||||
hugo_stats.json
|
||||
result
|
||||
.direnv
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "themes/gallery"]
|
||||
path = themes/gallery
|
||||
url = https://github.com/nicokaiser/hugo-theme-gallery.git
|
||||
@@ -0,0 +1,16 @@
|
||||
.PHONY: build exif_clean dev clean deploy
|
||||
|
||||
build: exif_clean
|
||||
nix build '.?submodules=1#default'
|
||||
|
||||
exif_clean:
|
||||
exiftool -r -overwrite_original -all= -tagsFromFile @ -Orientation -Canon -AllDates content/
|
||||
|
||||
dev:
|
||||
hugo server
|
||||
|
||||
clean:
|
||||
git clean -fdX
|
||||
|
||||
deploy: build
|
||||
rsync -e 'ssh -J root@100.64.0.1' -cdr --progress --delete-after result/ root@100.64.0.3:/var/www/website
|
||||
@@ -0,0 +1,5 @@
|
||||
+++
|
||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
+++
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
---
|
||||
sort_by: Date # Exif.Date
|
||||
sort_order: desc
|
||||
#type: gallery
|
||||
params:
|
||||
theme: dark
|
||||
---
|
||||
Generated
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1731245184,
|
||||
"narHash": "sha256-vmLS8+x+gHRv1yzj3n+GTAEObwmhxmkkukB2DwtJRdU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "aebe249544837ce42588aa4b2e7972222ba12e8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
description = "Rixxc's Personal Website and Blog";
|
||||
|
||||
inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
|
||||
outputs = { nixpkgs, ... }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
with pkgs;
|
||||
{
|
||||
packages.${system}.default = stdenv.mkDerivation
|
||||
{
|
||||
name = "Personal Website";
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
hugo
|
||||
rsync
|
||||
exiftool
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
hugo
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
cp -r public/* $out/
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
baseURL = 'https://photo.rixxc.de/'
|
||||
languageCode = 'en-us'
|
||||
#title = 'My New Hugo Site'
|
||||
theme = 'gallery'
|
||||
timeout = '120s'
|
||||
defaultTheme = 'dark'
|
||||
|
||||
[params]
|
||||
defaultTheme = 'dark'
|
||||
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 */
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user