├── .gitignore ├── exampleSite ├── content │ ├── .gitignore │ ├── happy │ │ └── index.md │ ├── people │ │ └── index.md │ ├── tints │ │ └── index.md │ ├── travel │ │ └── index.md │ ├── empty │ │ └── index.md │ ├── love │ │ └── index.md │ ├── animals │ │ └── index.md │ ├── flowers │ │ └── index.md │ ├── food │ │ └── index.md │ └── architecture │ │ └── index.md ├── themes │ └── photo-stream ├── .gitignore ├── config.toml └── fetch-photos.sh ├── static ├── favicon.png ├── social-preview.png ├── touch-icon-iphone.png ├── img │ ├── icon-rss.svg │ ├── icon-info.svg │ ├── icon-right.svg │ ├── icon-left.svg │ ├── icon-github.svg │ └── icon-twitter.svg ├── js │ ├── lazy-loading.js │ └── photos.js └── favicon.svg ├── layouts ├── _default │ ├── baseof.html │ ├── list.html │ ├── single.html │ └── album.html ├── 404.html └── partials │ ├── links.html │ ├── photos.html │ └── head.html ├── i18n ├── en.yaml └── fr.yaml ├── archetypes └── album.md ├── theme.toml ├── LICENSE ├── README.md └── assets └── css └── master.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /exampleSite/content/.gitignore: -------------------------------------------------------------------------------- 1 | *.jpeg -------------------------------------------------------------------------------- /exampleSite/themes/photo-stream: -------------------------------------------------------------------------------- 1 | ../../ -------------------------------------------------------------------------------- /exampleSite/.gitignore: -------------------------------------------------------------------------------- 1 | resources 2 | public -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmasse-itix/photo-stream/HEAD/static/favicon.png -------------------------------------------------------------------------------- /static/social-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmasse-itix/photo-stream/HEAD/static/social-preview.png -------------------------------------------------------------------------------- /static/touch-icon-iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmasse-itix/photo-stream/HEAD/static/touch-icon-iphone.png -------------------------------------------------------------------------------- /exampleSite/config.toml: -------------------------------------------------------------------------------- 1 | title = "Photo Stream" 2 | theme = "photo-stream" 3 | [params] 4 | album_date_format = "2006" 5 | -------------------------------------------------------------------------------- /exampleSite/content/happy/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2015-01-01" 3 | title: Happy 4 | sort_by: "Name" 5 | resources: 6 | - src: '**.jpeg' 7 | --- 8 | -------------------------------------------------------------------------------- /exampleSite/content/people/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2013-01-01" 3 | title: People 4 | sort_by: "Name" 5 | resources: 6 | - src: '**.jpeg' 7 | --- 8 | -------------------------------------------------------------------------------- /exampleSite/content/tints/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2012-01-01" 3 | title: Tints 4 | sort_by: "Name" 5 | resources: 6 | - src: '**.jpeg' 7 | --- 8 | -------------------------------------------------------------------------------- /exampleSite/content/travel/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2011-01-01" 3 | title: Travel 4 | sort_by: "Name" 5 | resources: 6 | - src: '**.jpeg' 7 | --- 8 | -------------------------------------------------------------------------------- /exampleSite/content/empty/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2020-01-01" 3 | title: Architecture 4 | sort_by: "Exif.Date" 5 | resources: 6 | - src: '**.jpeg' 7 | --- 8 | -------------------------------------------------------------------------------- /layouts/_default/baseof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{- partial "head.html" . -}} 4 | 5 | {{- block "main" . }}{{- end }} 6 | 7 | 8 | -------------------------------------------------------------------------------- /layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | 2 | {{ define "main"}} 3 | 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /exampleSite/content/love/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2014-01-01" 3 | title: Love 4 | sort_by: "Name" 5 | resources: 6 | - src: 'love02.jpeg' 7 | params: 8 | cover: true 9 | - src: '**.jpeg' 10 | --- 11 | -------------------------------------------------------------------------------- /exampleSite/content/animals/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2016-01-01" 3 | title: Animals 4 | sort_by: "Name" 5 | resources: 6 | - src: 'camel.jpeg' 7 | params: 8 | cover: true 9 | - src: '**.jpeg' 10 | --- 11 | -------------------------------------------------------------------------------- /exampleSite/content/flowers/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2019-01-01" 3 | title: Flowers 4 | sort_by: "Name" 5 | resources: 6 | - src: 'flower09.jpeg' 7 | params: 8 | cover: true 9 | - src: '**.jpeg' 10 | --- 11 | -------------------------------------------------------------------------------- /exampleSite/content/food/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2017-01-01" 3 | title: Food and drink 4 | sort_by: "Name" 5 | resources: 6 | - src: 'food06.jpeg' 7 | params: 8 | cover: true 9 | - src: '**.jpeg' 10 | --- 11 | -------------------------------------------------------------------------------- /exampleSite/content/architecture/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: "2020-01-01" 3 | title: Architecture 4 | sort_by: "Name" 5 | resources: 6 | - src: 'archi10.jpeg' 7 | params: 8 | cover: true 9 | - src: '**.jpeg' 10 | --- 11 | -------------------------------------------------------------------------------- /layouts/404.html: -------------------------------------------------------------------------------- 1 | {{ define "main"}} 2 |
3 | 4 |

404

5 |

{{ i18n "pageNotFound" }}

6 | {{ i18n "visitHomePage" }} 7 |
8 | {{ end }} 9 | -------------------------------------------------------------------------------- /i18n/en.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - id: pageNotFound 3 | translation: "Sorry, this page does not exist." 4 | - id: visitHomePage 5 | translation: "Return to home page" 6 | - id: open 7 | translation: "Open" 8 | - id: previous 9 | translation: "Previous" 10 | - id: next 11 | translation: "Next" -------------------------------------------------------------------------------- /layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main"}} 2 | 6 | {{ end }} 7 | -------------------------------------------------------------------------------- /i18n/fr.yaml: -------------------------------------------------------------------------------- 1 | - id: pageNotFound 2 | translation: "Désolé, cette page n'existe pas !" 3 | - id: visitHomePage 4 | translation: "Retour à la page d'accueil" 5 | - id: open 6 | translation: "Ouvrir" 7 | - id: previous 8 | translation: "Précédent" 9 | - id: next 10 | translation: "Suivant" 11 | -------------------------------------------------------------------------------- /archetypes/album.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | # By default, photos are sorted by filename 5 | sort_by: Name 6 | # But you can sort instead by EXIF date if you prefer 7 | # sort_by: Exif.Date 8 | resources: 9 | # 10 | # You can set the album cover image by setting the param 'cover: true' 11 | # on a photo. 12 | # 13 | # - src: 'IMG_1234.jpeg' 14 | # params: 15 | # cover: true 16 | # 17 | - src: '**.jpeg' 18 | - src: '**.jpg' 19 | --- 20 | -------------------------------------------------------------------------------- /theme.toml: -------------------------------------------------------------------------------- 1 | # theme.toml template for a Hugo theme 2 | # See https://github.com/gohugoio/hugoThemes#themetoml for an example 3 | 4 | name = "Photo Stream" 5 | license = "MIT" 6 | licenselink = "https://github.com/nmasse-itix/photo-stream/blob/master/LICENSE" 7 | description = "A theme to host your photo albums, based on maxvoltar's photo-stream theme" 8 | homepage = "https://www.itix.fr/" 9 | tags = [ "photo-stream", "responsive", "flexbox", "minimalistic", "gallery" ] 10 | features = [ "rss", "page resources", "responsive" ] 11 | min_version = "0.68.0" 12 | 13 | [author] 14 | name = "Nicolas Massé" 15 | homepage = "https://www.itix.fr/" 16 | 17 | [original] 18 | name = "Tim Van Damme" 19 | homepage = "http://timvandamme.com/" 20 | repo = "https://github.com/maxvoltar/photo-stream" 21 | -------------------------------------------------------------------------------- /static/img/icon-rss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /layouts/partials/links.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/icon-info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon-info 5 | Created with Sketch Beta. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /static/img/icon-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 YOUR_NAME_HERE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /static/img/icon-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon-left 5 | Created with Sketch Beta. 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /static/img/icon-github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /layouts/_default/album.html: -------------------------------------------------------------------------------- 1 | {{ $photos := .Resources.ByType "image" }} 2 | {{ $scratch := newScratch }} 3 | {{ $scratch.Set "index" 0 }} 4 | {{ range $i, $photo := $photos }} 5 | {{ if index $photo.Params "cover" }} 6 | {{ $scratch.Set "index" $i }} 7 | {{ end }} 8 | {{ end }} 9 | {{ if gt (len $photos) 0 }} 10 | {{ $photo := index $photos ($scratch.Get "index") }} 11 | {{ if $photo.Exif }} 12 | {{ $orientation := index $photo.Exif.Tags "Orientation" }} 13 | {{ if eq $orientation 6 }} 14 | {{ $scratch.Set "image_rotation" "r270" }} 15 | {{ else if eq $orientation 8 }} 16 | {{ $scratch.Set "image_rotation" "r90" }} 17 | {{ else if eq $orientation 3 }} 18 | {{ $scratch.Set "image_rotation" "r180" }} 19 | {{ else }} 20 | {{ $scratch.Set "image_rotation" "" }} 21 | {{ end }} 22 | {{ else }} 23 | {{ $scratch.Set "image_rotation" "" }} 24 | {{ end }} 25 | {{ $tint := $photo.Fill "1x1 Box png" }} 26 | {{ $thumbnail := $photo.Fit (print "800x800 Lanczos q80 " ($scratch.Get "image_rotation")) }} 27 |
  • 28 | 29 | {{ i18n "open" }} 30 |
    {{ .Title }}
    31 |
    {{ dateFormat ((index .Site.Params "album_date_format") | default "01/2006") .Date }}
    32 |
  • 33 | {{ end }} 34 | -------------------------------------------------------------------------------- /static/js/lazy-loading.js: -------------------------------------------------------------------------------- 1 | // Name: Lazy Load 2.0.0-rc.2 2 | // Source: https://github.com/tuupola/lazyload 3 | // License: MIT license 4 | // Copyright: 2007-2019 Mika Tuupola 5 | 6 | !function(t,e){"object"==typeof exports?module.exports=e(t):"function"==typeof define&&define.amd?define([],e):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=s(r,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}"function"==typeof define&&define.amd&&(t=window);const r={src:"data-src",srcset:"data-srcset",selector:".lazyload",root:null,rootMargin:"0px",threshold:0},s=function(){let t={},e=!1,r=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],r++);for(;r 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /layouts/partials/photos.html: -------------------------------------------------------------------------------- 1 | {{ $photos := . }} 2 | {{ $size := len $photos }} 3 | {{ $scratch := newScratch }} 4 | {{ range $index, $photo := $photos }} 5 | {{ if $photo.Exif }} 6 | {{ $orientation := index $photo.Exif.Tags "Orientation" }} 7 | {{ if eq $orientation 6 }} 8 | {{ $scratch.Set "image_rotation" "r270" }} 9 | {{ else if eq $orientation 8 }} 10 | {{ $scratch.Set "image_rotation" "r90" }} 11 | {{ else if eq $orientation 3 }} 12 | {{ $scratch.Set "image_rotation" "r180" }} 13 | {{ else }} 14 | {{ $scratch.Set "image_rotation" "" }} 15 | {{ end }} 16 | {{ else }} 17 | {{ $scratch.Set "image_rotation" "" }} 18 | {{ end }} 19 | {{ $tint := $photo.Fill "1x1 Box png" }} 20 | {{ $thumbnail := $photo.Fit (print "800x800 Lanczos q80 " ($scratch.Get "image_rotation")) }} 21 | {{ $large := $photo.Fit (print "2048x2048 Lanczos q85 " ($scratch.Get "image_rotation")) }} 22 |
  • 23 | 24 | 25 | 26 | 27 | 28 | {{ i18n "open" }} 29 | Close 30 | 31 | {{ if $index }} 32 | {{ $previous_photo := (index $photos (sub $index 1)) }} 33 | 36 | {{ end }} 37 | 38 | {{ if lt $index (sub $size 1) }} 39 | {{ $next_photo := (index $photos (add $index 1)) }} 40 | 43 | {{ end }} 44 |
  • 45 | {{ end }} -------------------------------------------------------------------------------- /layouts/partials/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ $url := replace .Permalink ( printf "%s" .Site.BaseURL) "" }} 4 | {{ if eq $url "/" }} 5 | {{ $.Scratch.Set "title" .Site.Title }} 6 | {{ else }} 7 | {{ $.Scratch.Set "title" .Title }} 8 | {{ end }} 9 | 11 | 12 | {{ $.Scratch.Get "title" }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{ range .AlternativeOutputFormats -}} 22 | {{ printf `` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }} 23 | {{ end -}} 24 | 25 | 26 | {{ template "_internal/opengraph.html" . }} 27 | 28 | 29 | {{ $scss := resources.Get "/css/master.scss" }} 30 | {{ $css := $scss | resources.ToCSS }} 31 | 32 | 33 | {{ if .Site.Params.cachebuster }} 34 | {{ $t := now }} 35 | 36 | {{ range .Site.Params.extracssfiles }} 37 | 38 | {{ end }} 39 | 40 | 41 | {{ else }} 42 | 43 | {{ range .Site.Params.extracssfiles }} 44 | 45 | {{ end }} 46 | 47 | 48 | {{ end }} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /static/js/photos.js: -------------------------------------------------------------------------------- 1 | const ESCAPE = 27; 2 | const RIGHT = 39; 3 | const LEFT = 37; 4 | const TARGET_CLASS = 'target'; 5 | 6 | const clickNavigationButton = (buttonClass) => { 7 | const id = window.history.state && window.history.state.id; 8 | if (id) { 9 | const photo = document.getElementById(id); 10 | console.log(photo); 11 | const button = photo.querySelector(buttonClass); 12 | console.log(button); 13 | button && button.click(); 14 | } 15 | } 16 | 17 | const openPhoto = (id, href) => { 18 | console.log(`Opening photo ${id}...`); 19 | const photo = document.getElementById(id); 20 | const title = photo.getAttribute('title'); 21 | removeTargetClass(); 22 | photo.classList.add(TARGET_CLASS); 23 | document.title = title; 24 | if (href) { 25 | window.history.pushState({id: id}, '', href); 26 | } 27 | } 28 | 29 | const closePhoto = (href) => { 30 | console.log(`Closing photo...`); 31 | const title = document.querySelector('head title').getAttribute('data-title'); 32 | removeTargetClass(); 33 | document.title = title; 34 | if (href) { 35 | window.history.pushState({}, '', href); 36 | } 37 | } 38 | 39 | const removeTargetClass = () => { 40 | let targets = document.querySelectorAll(`.${TARGET_CLASS}`); 41 | targets.forEach((target) => { 42 | target.classList.remove(TARGET_CLASS); 43 | }); 44 | } 45 | 46 | const handleClick = (selector, event, callback) => { 47 | if (event.target.matches(selector)) { 48 | callback(); 49 | event.preventDefault(); 50 | } 51 | } 52 | 53 | const handleKey = (keyCode, event, callback) => { 54 | if (event.keyCode === keyCode) { 55 | callback(); 56 | event.preventDefault(); 57 | } 58 | } 59 | 60 | window.onpopstate = function(event) { 61 | if (event.state && event.state.id) { 62 | const id = event.state.id; 63 | openPhoto(id, null); 64 | } else { 65 | closePhoto(null); 66 | } 67 | } 68 | 69 | document.addEventListener('keydown', (event) => { 70 | handleKey(ESCAPE, event, () => { 71 | clickNavigationButton('.close'); 72 | }); 73 | 74 | handleKey(RIGHT, event, () => { 75 | clickNavigationButton('.next'); 76 | }); 77 | 78 | handleKey(LEFT, event, () => { 79 | clickNavigationButton('.previous'); 80 | }); 81 | }); 82 | 83 | document.addEventListener('click', (event) => { 84 | handleClick('[data-target][href]', event, () => { 85 | const id = event.target.getAttribute('data-target'); 86 | const href = event.target.getAttribute('href'); 87 | openPhoto(id, href); 88 | }); 89 | 90 | handleClick('[href].close', event, () => { 91 | const href = event.target.getAttribute('href'); 92 | closePhoto(href); 93 | }); 94 | }); 95 | 96 | window.addEventListener('load', (event) => { 97 | console.log("Loaded !"); 98 | const id = window.location.hash.substr(1); 99 | if (id != "") { 100 | openPhoto(id, "#" + id); 101 | } 102 | lazyload(); 103 | }); 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Photo Stream 2 | 3 | A theme to showcase your photo albums, powered by [Hugo](https://gohugo.io). 4 | 5 | **A live demo is available [at hugo-photo-stream.netlify.app](https://hugo-photo-stream.netlify.app/).** 6 | 7 | ## Features 8 | 9 | This theme is basically a port of [maxvoltar's photo-stream theme](https://github.com/maxvoltar/photo-stream). 10 | Thanks to him for this nice creation! 11 | 12 | This theme features: 13 | 14 | * Lazy loading of photos (a photo is downloaded when it appears in the viewport) 15 | * Albums containing photos 16 | * Photos thumbnail are resized to fit 800x800 17 | * The large version is resized to fit 2048x2048 18 | * The background is filled with a tint matching the photo 19 | * Keyboard shortcuts for previous / next / back to list 20 | 21 | ## Installation 22 | 23 | From the root of your Hugo site, type the following: 24 | 25 | ```sh 26 | git submodule add https://github.com/nmasse-itix/photo-stream.git themes/photo-stream 27 | git submodule init 28 | git submodule update 29 | ``` 30 | 31 | Now you can get updates of this theme in the future by updating the submodule: 32 | 33 | ```sh 34 | git submodule update --remote themes/photo-stream 35 | ``` 36 | 37 | ## Configuration 38 | 39 | After installation, take a look at the `exampleSite` folder inside `themes/photo-stream`. 40 | 41 | To get started, copy the `config.toml` file inside `exampleSite` to the root of your Hugo site: 42 | 43 | ```sh 44 | cp themes/photo-stream/exampleSite/config.toml . 45 | ``` 46 | 47 | Now edit this file and add your own information. Note that some fields can be omitted. 48 | 49 | ## How to create an album 50 | 51 | The theme provides an **archetype** named `album`. 52 | Create a new album with the `hugo new` command. 53 | 54 | ```sh 55 | hugo new my-album/index.md -k album 56 | ``` 57 | 58 | ## How to add photos 59 | 60 | To add photos to an album, simply copy your JPEG files in the album directory, **under content, NOT static!** 61 | 62 | ```sh 63 | cp path/to/DCIM_*.jpeg content/my-album/ 64 | ``` 65 | 66 | ## How to customize an album 67 | 68 | A minimal `index.md` looks like this: 69 | 70 | ```yaml 71 | --- 72 | date: "2016-01-01" 73 | title: Animals 74 | - src: '**.jpeg' 75 | --- 76 | ``` 77 | 78 | This index file defines an album with a date, a title and instructs to add all JPEG files to the album. 79 | 80 | But a usual `index.md` would include more customization: 81 | 82 | ```yaml 83 | --- 84 | date: "2016-01-01" 85 | title: Animals 86 | sort_by: "Exif.Date" 87 | resources: 88 | - src: 'camel.jpeg' 89 | params: 90 | cover: true 91 | - src: '**.jpeg' 92 | - src: '**.jpg' 93 | --- 94 | ``` 95 | 96 | This index also specifies: 97 | 98 | * To sort photos by date (specified in the EXIF metadata). 99 | * To also include files with `.jpg` extension. 100 | * To set `camel.jpeg` as the cover photo for the album. 101 | 102 | ## Global configuration 103 | 104 | The Date format for the album can be set in your `config.toml`. 105 | 106 | ```toml 107 | [params] 108 | album_date_format = "01/2006" 109 | ``` 110 | 111 | Check the Go documentation for possible formats: [time.Format](https://golang.org/pkg/time/#Time.Format). 112 | 113 | ## Demo Website 114 | 115 | A live demo is available [at hugo-photo-stream.netlify.app](https://hugo-photo-stream.netlify.app/) but you can have a look by yourself at the example site. 116 | 117 | ```sh 118 | cd themes/photo-stream/exampleSite 119 | ./fetch-photos.sh 120 | hugo serve 121 | ``` 122 | 123 | On netlify, you need to customize the **build command** and **publish directory**: 124 | 125 | * Build command: `cd exampleSite && ./fetch-photos.sh && hugo` 126 | * Publish directory: `exampleSite/public` 127 | 128 | And add an environment variable to install a recent version of Hugo. 129 | 130 | * `HUGO_VERSION=0.68.3` 131 | 132 | -------------------------------------------------------------------------------- /static/img/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /assets/css/master.scss: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | font-size: 1em; 6 | box-sizing: border-box; 7 | } 8 | 9 | @mixin dark { 10 | @media (prefers-color-scheme: dark) { 11 | @content 12 | } 13 | } 14 | 15 | @mixin button { 16 | display: block; 17 | border-radius: 16px; 18 | text-indent: 150%; 19 | overflow: hidden; 20 | white-space: nowrap; 21 | width: 32px; 22 | height: 32px; 23 | background-repeat: no-repeat; 24 | background-position: 8px; 25 | background-size: 16px; 26 | transition: background-color .1s linear; 27 | -webkit-backdrop-filter: blur(20px); 28 | -moz-backdrop-filter: blur(20px); 29 | backdrop-filter: blur(20px); 30 | background-color: rgba(200, 200, 200, .25); 31 | // Disable until Safari supports `prefers-color-scheme` in SVG's 32 | // @include dark { 33 | // background-color: rgba(0, 0, 0, .25); 34 | // } 35 | 36 | &:hover, 37 | &:focus { 38 | background-color: rgba(200, 200, 200, .5); 39 | // Disable until Safari supports `prefers-color-scheme` in SVG's 40 | // @include dark { 41 | // background-color: rgba(0, 0, 0, .5); 42 | // } 43 | } 44 | 45 | &:active { 46 | background-color: rgba(200, 200, 200, .25); 47 | // Disable until Safari supports `prefers-color-scheme` in SVG's 48 | // @include dark { 49 | // background-color: rgba(0, 0, 0, .75); 50 | // } 51 | } 52 | 53 | 54 | } 55 | 56 | body, 57 | html { 58 | min-height: 100%; 59 | display: flex; 60 | } 61 | 62 | body { 63 | flex-grow: 1; 64 | font: 16px/24px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 65 | background: #fff; 66 | color: #2e2f30; 67 | 68 | @include dark { 69 | background: #000; 70 | color: #eee; 71 | } 72 | } 73 | 74 | // PHOTO GRID 75 | 76 | .grid { 77 | display: flex; 78 | flex-wrap: wrap; 79 | position: relative; 80 | align-content: flex-start; 81 | 82 | &:after { 83 | content: ""; 84 | display: block; 85 | flex-grow: 10; 86 | outline: 2px solid #fff; 87 | position: relative; 88 | 89 | @include dark { 90 | outline-color: #000; 91 | } 92 | } 93 | 94 | .item { 95 | height: 40vh; 96 | flex-grow: 1; 97 | outline: 2px solid #fff; 98 | position: relative; 99 | background-size: 100%; 100 | 101 | @include dark { 102 | outline-color: #000; 103 | } 104 | 105 | img { 106 | max-height: 100%; 107 | min-width: 100%; 108 | min-height: 100%; 109 | width: auto; 110 | object-fit: cover; 111 | display: block; 112 | } 113 | 114 | .open, 115 | .close { 116 | position: absolute; 117 | top: 0; 118 | right: 0; 119 | bottom: 0; 120 | left: 0; 121 | text-indent: 150%; 122 | overflow: hidden; 123 | white-space: nowrap; 124 | } 125 | 126 | .open { 127 | cursor: zoom-in; 128 | background-color: rgba(0, 0, 0, 0); 129 | transition: background-color .15s ease-out; 130 | 131 | &:hover, 132 | &:focus { 133 | background-color: rgba(0, 0, 0, .25); 134 | } 135 | 136 | &:active { 137 | background-color: rgba(0, 0, 0, .5); 138 | } 139 | } 140 | 141 | .close { 142 | display: none; 143 | cursor: zoom-out; 144 | } 145 | 146 | .full { 147 | display: none; 148 | } 149 | 150 | .previous, 151 | .next { 152 | position: absolute; 153 | top: 0; 154 | bottom: 0; 155 | left: 0; 156 | z-index: 20; 157 | width: 80px; 158 | display: none; 159 | align-items: center; 160 | justify-content: center; 161 | 162 | span { 163 | @include button; 164 | background-image: url(../img/icon-left.svg); 165 | pointer-events: none; 166 | } 167 | 168 | &:hover, 169 | &:focus { 170 | span { 171 | background-color: rgba(200, 200, 200, .5); 172 | // Disable until Safari supports `prefers-color-scheme` in SVG's 173 | // @include dark { 174 | // background-color: rgba(0, 0, 0, .5); 175 | // } 176 | } 177 | } 178 | 179 | &:active { 180 | span { 181 | background-color: rgba(200, 200, 200, .25); 182 | // Disable until Safari supports `prefers-color-scheme` in SVG's 183 | // @include dark { 184 | // background-color: rgba(0, 0, 0, .75); 185 | // } 186 | } 187 | } 188 | } 189 | 190 | .next { 191 | right: 0; 192 | left: auto; 193 | 194 | span { 195 | background-image: url(../img/icon-right.svg); 196 | } 197 | } 198 | 199 | .name { 200 | display: flex; 201 | position: absolute; 202 | right: 4px; 203 | bottom: 12px; 204 | color: #fff; 205 | text-shadow: #000 0 1px 1px, #000 0 2px 4px; 206 | opacity: 1; 207 | font-size: larger; 208 | margin-right: 12px; 209 | } 210 | 211 | .date { 212 | display: flex; 213 | position: absolute; 214 | left: 4px; 215 | bottom: 12px; 216 | color: #fff; 217 | text-shadow: #000 0 1px 1px, #000 0 2px 4px; 218 | opacity: 1; 219 | margin-left: 12px; 220 | } 221 | 222 | // PHOTO DETAIL 223 | 224 | &.target { 225 | position: fixed; 226 | top: 0; 227 | right: 0; 228 | bottom: 0; 229 | left: 0; 230 | height: 100%; 231 | z-index: 10; 232 | background: #fff; 233 | display: flex; 234 | align-items: center; 235 | 236 | @media (prefers-color-scheme: dark) { 237 | background: #000; 238 | } 239 | 240 | .open { 241 | display: none; 242 | } 243 | 244 | .close { 245 | display: block; 246 | } 247 | 248 | img { 249 | object-fit: contain; 250 | animation: fade-in .5s ease-out; 251 | } 252 | 253 | .full { 254 | display: flex; 255 | position: absolute; 256 | top: 0; 257 | right: 0; 258 | bottom: 0; 259 | left: 0; 260 | animation: fade-in .5s ease-out; 261 | 262 | span { 263 | flex-grow: 1; 264 | background-size: contain; 265 | background-repeat: no-repeat; 266 | background-position: center; 267 | } 268 | } 269 | 270 | .meta { 271 | display: none !important; 272 | } 273 | 274 | .previous, 275 | .next { 276 | display: flex; 277 | } 278 | } 279 | } 280 | } 281 | 282 | // SOCIAL LINKS 283 | 284 | .links { 285 | position: fixed; 286 | bottom: 24px; 287 | right: 24px; 288 | display: flex; 289 | flex-wrap: wrap; 290 | margin-left: 16px; 291 | 292 | li { 293 | margin-left: 8px; 294 | 295 | a { 296 | @include button; 297 | } 298 | 299 | &.github { 300 | a { 301 | background-image: url(../img/icon-github.svg); 302 | } 303 | } 304 | 305 | &.rss { 306 | a { 307 | background-image: url(../img/icon-rss.svg); 308 | } 309 | } 310 | 311 | &.link { 312 | a { 313 | text-indent: 0; 314 | width: auto; 315 | font-size: 13px; 316 | line-height: 32px; 317 | text-transform: uppercase; 318 | padding: 0 12px; 319 | color: rgba(0, 0, 0, .75); 320 | font-weight: 600; 321 | text-decoration: none; 322 | } 323 | } 324 | } 325 | } 326 | 327 | // 404 328 | 329 | .four-oh-four { 330 | flex-grow: 1; 331 | display: flex; 332 | flex-direction: column; 333 | height: 100%; 334 | padding: 64px; 335 | align-items: center; 336 | justify-content: center; 337 | text-align: center; 338 | 339 | img { 340 | width: 64px; 341 | vertical-align: bottom; 342 | margin-bottom: 24px; 343 | } 344 | 345 | h1 { 346 | font-size: 32px; 347 | line-height: 48px; 348 | font-weight: 700; 349 | } 350 | 351 | p { 352 | margin-bottom: 32px; 353 | } 354 | 355 | a { 356 | @include button; 357 | text-indent: 0; 358 | width: auto; 359 | font-size: 13px; 360 | line-height: 32px; 361 | text-transform: uppercase; 362 | padding: 0 12px; 363 | color: rgba(0, 0, 0, .75); 364 | font-weight: 600; 365 | text-decoration: none; 366 | } 367 | } 368 | 369 | // RESPONSIVE 370 | 371 | @media (max-aspect-ratio: 1/1) { 372 | .grid { 373 | .item { 374 | height: 30vh; 375 | } 376 | } 377 | } 378 | 379 | @media (max-height: 480px) { 380 | .grid { 381 | .item { 382 | height: 80vh; 383 | } 384 | } 385 | } 386 | 387 | @media (max-aspect-ratio: 1/1) and (max-width: 480px) { 388 | .grid { 389 | flex-direction: row; 390 | 391 | .item { 392 | height: auto; 393 | width: 100%; 394 | 395 | img { 396 | width: 100%; 397 | height: auto; 398 | } 399 | 400 | .previous, 401 | .next { 402 | width: 25vw; 403 | max-width: auto; 404 | 405 | span { 406 | display: none; 407 | } 408 | } 409 | 410 | .previous { 411 | cursor: w-resize; 412 | } 413 | 414 | .next { 415 | cursor: e-resize; 416 | } 417 | } 418 | } 419 | } 420 | 421 | // ANIMATIONS 422 | 423 | @keyframes fade-in { 424 | 0% { 425 | opacity: 0; 426 | } 427 | 100% { 428 | opacity: 1; 429 | } 430 | } -------------------------------------------------------------------------------- /exampleSite/fetch-photos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function fetch() { 4 | file=$1 5 | url=$2 6 | if [ ! -e "$1" ]; then 7 | echo "Fetching $1... " 8 | curl -sL -o "$1" "$2" 9 | if [ "$?" -gt 0 ]; then 10 | echo "Failed !" 11 | fi 12 | else 13 | echo "Skipping $1, the file already exists!" 14 | fi 15 | mime_type="$(file -b --mime-type "$1")" 16 | if [ "$mime_type" != "image/jpeg" ]; then 17 | echo "Removing image with wrong mime type!" 18 | rm "$1" 19 | fi 20 | } 21 | 22 | fetch "content/animals/dog.jpeg" "https://unsplash.com/photos/Am3RoG7GEl0/download?force=true" 23 | fetch "content/animals/cat.jpeg" "https://unsplash.com/photos/G21-GaAZrrs/download?force=true" 24 | fetch "content/animals/bee.jpeg" "https://unsplash.com/photos/VeRn-bKfoVA/download?force=true" 25 | fetch "content/animals/monkey.jpeg" "https://unsplash.com/photos/4nPq-CMKfvY/download?force=true" 26 | fetch "content/animals/turtle.jpeg" "https://unsplash.com/photos/_x-PryfGq0Y/download?force=true" 27 | fetch "content/animals/horse.jpeg" "https://unsplash.com/photos/dr1Hbu8Gwt8/download?force=true" 28 | fetch "content/animals/bird2.jpeg" "https://unsplash.com/photos/ZbMJ5VLrpQ4/download?force=true" 29 | fetch "content/animals/spider.jpeg" "https://unsplash.com/photos/BkV2pxgof-U/download?force=true" 30 | fetch "content/animals/wolf.jpeg" "https://unsplash.com/photos/9rloii_qmmw/download?force=true" 31 | fetch "content/animals/dog2.jpeg" "https://unsplash.com/photos/Oq9NDnlFQzU/download?force=true" 32 | fetch "content/animals/fox.jpeg" "https://unsplash.com/photos/oCxaclJklOI/download?force=true" 33 | fetch "content/animals/dog3.jpeg" "https://unsplash.com/photos/8o5uCdOQtko/download?force=true" 34 | fetch "content/animals/camel.jpeg" "https://unsplash.com/photos/i_Z1o10BPPk/download?force=true" 35 | fetch "content/animals/bee2.jpeg" "https://unsplash.com/photos/UPJ0vTjPFXE/download?force=true" 36 | fetch "content/animals/wasp.jpeg" "https://unsplash.com/photos/R_W86FHa-Sk/download?force=true" 37 | fetch "content/animals/dog4.jpeg" "https://unsplash.com/photos/Ugg-EIfzy0c/download?force=true" 38 | fetch "content/animals/rabbit.jpeg" "https://unsplash.com/photos/8bOwZ8ag9UY/download?force=true" 39 | fetch "content/animals/fish.jpeg" "https://unsplash.com/photos/l-QdJMZX7PU/download?force=true" 40 | 41 | fetch "content/architecture/archi01.jpeg" "https://unsplash.com/photos/37Hk9D4Ig_4/download?force=true" 42 | fetch "content/architecture/archi02.jpeg" "https://unsplash.com/photos/3pk1VnBeTQQ/download?force=true" 43 | fetch "content/architecture/archi03.jpeg" "https://unsplash.com/photos/t1tAOh-CaZ4/download?force=true" 44 | fetch "content/architecture/archi04.jpeg" "https://unsplash.com/photos/w6OniVDCfn0/download?force=true" 45 | fetch "content/architecture/archi05.jpeg" "https://unsplash.com/photos/zZ97YKTyj7s/download?force=true" 46 | fetch "content/architecture/archi06.jpeg" "https://unsplash.com/photos/tNGfc-2KNrc/download?force=true" 47 | fetch "content/architecture/archi07.jpeg" "https://unsplash.com/photos/LmS1g1fqyas/download?force=true" 48 | fetch "content/architecture/archi08.jpeg" "https://unsplash.com/photos/I-LFXWk3vLI/download?force=true" 49 | fetch "content/architecture/archi09.jpeg" "https://unsplash.com/photos/4VBFrMweUw8/download?force=true" 50 | fetch "content/architecture/archi13.jpeg" "https://unsplash.com/photos/IU1QUXkD-90/download?force=true" 51 | fetch "content/architecture/archi14.jpeg" "https://unsplash.com/photos/8o_x-NjXIcQ/download?force=true" 52 | fetch "content/architecture/archi15.jpeg" "https://unsplash.com/photos/YKAUA_Rt6xI/download?force=true" 53 | fetch "content/architecture/archi16.jpeg" "https://unsplash.com/photos/zUOqjnO_ZvM/download?force=true" 54 | fetch "content/architecture/archi17.jpeg" "https://unsplash.com/photos/s07In41ntgg/download?force=true" 55 | fetch "content/architecture/archi18.jpeg" "https://unsplash.com/photos/QsDEa0qvk20/download?force=true" 56 | 57 | fetch "content/flowers/flower01.jpeg" "https://unsplash.com/photos/EfhCUc_fjrU/download?force=true" 58 | fetch "content/flowers/flower02.jpeg" "https://unsplash.com/photos/9A_peGrSbZc/download?force=true" 59 | fetch "content/flowers/flower03.jpeg" "https://unsplash.com/photos/tu_mv6p2p5U/download?force=true" 60 | fetch "content/flowers/flower04.jpeg" "https://unsplash.com/photos/koy6FlCCy5s/download?force=true" 61 | fetch "content/flowers/flower06.jpeg" "https://unsplash.com/photos/5lRxNLHfZOY/download?force=true" 62 | fetch "content/flowers/flower07.jpeg" "https://unsplash.com/photos/iMdsjoiftZo/download?force=true" 63 | fetch "content/flowers/flower08.jpeg" "https://unsplash.com/photos/OWq8w3BYMFY/download?force=true" 64 | fetch "content/flowers/flower09.jpeg" "https://unsplash.com/photos/ATgfRqpFfFI/download?force=true" 65 | fetch "content/flowers/flower10.jpeg" "https://unsplash.com/photos/YmPqWIQcl9c/download?force=true" 66 | fetch "content/flowers/flower11.jpeg" "https://unsplash.com/photos/urUdKCxsTUI/download?force=true" 67 | fetch "content/flowers/flower12.jpeg" "https://unsplash.com/photos/p7mo8-CG5Gs/download?force=true" 68 | fetch "content/flowers/flower13.jpeg" "https://unsplash.com/photos/f0heeiu-Ec0/download?force=true" 69 | fetch "content/flowers/flower14.jpeg" "https://unsplash.com/photos/IicyiaPYGGI/download?force=true" 70 | fetch "content/flowers/flower15.jpeg" "https://unsplash.com/photos/KQ6sO8m1ZDE/download?force=true" 71 | fetch "content/flowers/flower16.jpeg" "https://unsplash.com/photos/kkJuQhp9Kw0/download?force=true" 72 | fetch "content/flowers/flower17.jpeg" "https://unsplash.com/photos/aolmXcUxr7Y/download?force=true" 73 | fetch "content/flowers/flower19.jpeg" "https://unsplash.com/photos/BlMj6RYy3c0/download?force=true" 74 | fetch "content/flowers/flower20.jpeg" "https://unsplash.com/photos/whOkVvf0_hU/download?force=true" 75 | 76 | fetch "content/food/food03.jpeg" "https://unsplash.com/photos/8W1KIj8iWX4/download?force=true" 77 | fetch "content/food/food04.jpeg" "https://unsplash.com/photos/D7NA2pEn3K0/download?force=true" 78 | fetch "content/food/food05.jpeg" "https://unsplash.com/photos/IlnF2g_3tpY/download?force=true" 79 | fetch "content/food/food06.jpeg" "https://unsplash.com/photos/qNhe2QXzLuo/download?force=true" 80 | fetch "content/food/food07.jpeg" "https://unsplash.com/photos/1J1mEZbag4I/download?force=true" 81 | fetch "content/food/food08.jpeg" "https://unsplash.com/photos/phEaeqe555M/download?force=true" 82 | fetch "content/food/food09.jpeg" "https://unsplash.com/photos/G3GxkxZOOYc/download?force=true" 83 | fetch "content/food/food10.jpeg" "https://unsplash.com/photos/dmnCGaqMEzE/download?force=true" 84 | fetch "content/food/food11.jpeg" "https://unsplash.com/photos/TAj4X5-eRqE/download?force=true" 85 | fetch "content/food/food12.jpeg" "https://unsplash.com/photos/H0fOnITjgw8/download?force=true" 86 | fetch "content/food/food13.jpeg" "https://unsplash.com/photos/MqT0asuoIcU/download?force=true" 87 | fetch "content/food/food14.jpeg" "https://unsplash.com/photos/EGvhPABaBos/download?force=true" 88 | fetch "content/food/food15.jpeg" "https://unsplash.com/photos/_MYcIi9DgYQ/download?force=true" 89 | fetch "content/food/food16.jpeg" "https://unsplash.com/photos/KG8ofkGRl1k/download?force=true" 90 | fetch "content/food/food17.jpeg" "https://unsplash.com/photos/C6JhUKs9q8M/download?force=true" 91 | fetch "content/food/food18.jpeg" "https://unsplash.com/photos/JyxMyWKOlSU/download?force=true" 92 | fetch "content/food/food20.jpeg" "https://unsplash.com/photos/aZfMW0hSnQI/download?force=true" 93 | 94 | fetch "content/happy/happy01.jpeg" "https://unsplash.com/photos/TyQ-0lPp6e4/download?force=true" 95 | fetch "content/happy/happy02.jpeg" "https://unsplash.com/photos/e3OUQGT9bWU/download?force=true" 96 | fetch "content/happy/happy03.jpeg" "https://unsplash.com/photos/FtZL0r4DZYk/download?force=true" 97 | fetch "content/happy/happy04.jpeg" "https://unsplash.com/photos/hRdVSYpffas/download?force=true" 98 | fetch "content/happy/happy05.jpeg" "https://unsplash.com/photos/1AhGNGKuhR0/download?force=true" 99 | 100 | 101 | fetch "content/love/love02.jpeg" "https://unsplash.com/photos/AsahNlC0VhQ/download?force=true" 102 | fetch "content/love/love03.jpeg" "https://unsplash.com/photos/EdULZpOKsUE/download?force=true" 103 | fetch "content/love/love04.jpeg" "https://unsplash.com/photos/Y9mWkERHYCU/download?force=true" 104 | 105 | fetch "content/people/people01.jpeg" "https://unsplash.com/photos/4nulm-JUYFo/download?force=true" 106 | fetch "content/people/people02.jpeg" "https://unsplash.com/photos/NAdFJtFFlHE/download?force=true" 107 | fetch "content/people/people03.jpeg" "https://unsplash.com/photos/2RhlxwRz4yc/download?force=true" 108 | fetch "content/people/people04.jpeg" "https://unsplash.com/photos/tokYjYqaPB0/download?force=true" 109 | fetch "content/people/people05.jpeg" "https://unsplash.com/photos/by0XNgDemsc/download?force=true" 110 | 111 | fetch "content/tints/tint01.jpeg" "https://unsplash.com/photos/kKvQJ6rK6S4/download?force=true" 112 | fetch "content/tints/tint02.jpeg" "https://unsplash.com/photos/Lw7BruqPnJY/download?force=true" 113 | fetch "content/tints/tint03.jpeg" "https://unsplash.com/photos/60eMQfQuGIk/download?force=true" 114 | fetch "content/tints/tint04.jpeg" "https://unsplash.com/photos/Q4q7kJxqfGI/download?force=true" 115 | fetch "content/tints/tint05.jpeg" "https://unsplash.com/photos/PsO_PfLXET4/download?force=true" 116 | 117 | fetch "content/travel/travel01.jpeg" "https://unsplash.com/photos/Q2ET6TX1poU/download?force=true" 118 | fetch "content/travel/travel02.jpeg" "https://unsplash.com/photos/c5F1hhK5t0Q/download?force=true" 119 | fetch "content/travel/travel03.jpeg" "https://unsplash.com/photos/A7KD1kdXD-o/download?force=true" 120 | fetch "content/travel/travel04.jpeg" "https://unsplash.com/photos/PSY_KuMcTJU/download?force=true" 121 | fetch "content/travel/travel05.jpeg" "https://unsplash.com/photos/rlIb6DLWcH8/download?force=true" 122 | --------------------------------------------------------------------------------