14 Commits

38 changed files with 5138 additions and 590 deletions

10
.hintrc
View File

@@ -3,6 +3,14 @@
"development"
],
"hints": {
"apple-touch-icons": "off"
"apple-touch-icons": "off",
"compat-api/css": [
"default",
{
"ignore": [
"-webkit-app-region"
]
}
]
}
}

View File

@@ -1 +1 @@
2.8.2
2.8.5

View File

@@ -90,6 +90,8 @@
"Scans",
"--exclude-folder",
"*/Galleries/*",
"--exclude-folder",
"*/Wallpaper/*",
"--folderthumbnails"
],
"console": "integratedTerminal",
@@ -160,10 +162,12 @@
}
],
"prettier.htmlWhitespaceSensitivity": "css",
"pylint.args": [
"--disable=C0111",
"--disable=C0301",
"--good-names-rgxs=^[_a-z][_a-z0-9]?$"
"python-envs.pythonProjects": [
{
"path": "",
"envManager": "ms-python.python:pyenv",
"packageManager": "ms-python.python:pip"
}
],
"python.analysis.inlayHints.callArgumentNames": "off",
"python.analysis.inlayHints.functionReturnTypes": false,

View File

@@ -11,6 +11,7 @@ from pathlib import Path
from tqdm.auto import tqdm
from PIL import Image, ImageOps
from jsmin import jsmin
from modules.argumentparser import parse_arguments, Args
@@ -123,6 +124,10 @@ def copy_static_files(_args: Args) -> None:
with open(os.path.join(static_dir, "theme.css"), "x", encoding="utf-8") as f:
logger.info("writing theme file")
f.write(themehead + '\n.foldericon {\n content: url("data:image/svg+xml,' + svg + '");\n}\n' + themetail)
logger.info("minifying javascript")
with open(os.path.join(SCRIPTDIR, "templates", "functionality.js"), "r", encoding="utf-8") as js_file:
with open(os.path.join(static_dir, "functionality.min.js"), "w+", encoding="utf-8") as min_file:
min_file.write(jsmin(js_file.read()))
def generate_thumbnail(arguments: tuple[str, str, str]) -> None:
@@ -169,7 +174,7 @@ def main(args) -> None:
try:
Path(lock_file).touch()
logger.info("starting builder", extra={"version": VERSION})
logger.info("starting builder", extra={"version": VERSION, "arguments": args})
logger.info("getting logo from sorogon.eu")
req = urllib.request.Request("https://files.sorogon.eu/logo.svg")

View File

@@ -66,6 +66,13 @@ body {
padding: 0 2px;
}
.centerload {
margin-top: 100px;
display: flex;
justify-content: center;
align-items: center;
}
figure {
margin: 0;
}
@@ -208,7 +215,7 @@ input {
}
.tooltiptext.tagdropdown.show {
max-height: 286px;
max-height: 80vh;
overflow-y: scroll;
opacity: 1;
}
@@ -232,7 +239,7 @@ input {
}
.tagentryparent.show {
max-height: 286px;
max-height: 80vh;
opacity: 1;
overflow-y: scroll;
}

View File

@@ -35,8 +35,7 @@
transition: opacity 0.2s;
-webkit-box-shadow: none;
box-shadow: none; }
.pswp__button:focus,
.pswp__button:hover {
.pswp__button:focus, .pswp__button:hover {
opacity: 1; }
.pswp__button:active {
outline: none;
@@ -272,14 +271,13 @@ a.pswp__share--download:hover {
color: #BBB; }
.pswp__caption__center {
text-align: center;
text-align: left;
max-width: 420px;
margin: 0 auto;
font-size: 13px;
padding: 10px;
line-height: 20px;
color: #CCC;
font-weight: bold; }
color: #CCC; }
.pswp__caption--empty {
display: none; }

View File

@@ -1,131 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="264"
height="88"
viewBox="0 0 264 88"
version="1.1"
id="svg12"
sodipodi:docname="default-skin.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview12"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="true"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
showborder="true"
borderlayer="true"
inkscape:zoom="3.0855569"
inkscape:cx="95.606729"
inkscape:cy="65.142212"
inkscape:window-width="1896"
inkscape:window-height="1016"
inkscape:window-x="1932"
inkscape:window-y="52"
inkscape:window-maximized="1"
inkscape:current-layer="g11" />
<title
id="title1">default-skin 2</title>
<g
fill="none"
fill-rule="evenodd"
id="g12">
<g
id="g11">
<g
fill="#fff"
id="g1">
<path
d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z"
id="path1" />
</g>
<g
fill="#fff"
id="g2">
<path
d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"
id="path2" />
</g>
<path
d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z"
fill="#fff"
id="path3" />
<path
d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z"
fill="#fff"
id="path4" />
<path
d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z"
fill="#fff"
id="path5" />
<g
id="g6">
<path
id="Rectangle-11"
fill="#fff"
d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z" />
<path
d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z"
id="Oval-1"
stroke="#fff"
stroke-width="1.5" />
<path
fill="#fff"
d="M150 21h5v1h-5z"
id="path6" />
</g>
<g
id="g10">
<path
d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z"
fill="#fff"
id="path7" />
<path
d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z"
stroke="#fff"
stroke-width="1.5"
id="path8" />
<path
fill="#fff"
d="M106 21h5v1h-5z"
id="path9" />
<path
fill="#fff"
d="M109.043 19.008l-.085 5-1-.017.085-5z"
id="path10" />
</g>
<g
id="g14">
<path
id="Shape"
style="fill:#ffffff"
d="M 63.708984 57.572266 L 63.708984 66.917969 L 62.773438 66.917969 L 59.173828 66.917969 L 65.089844 72.916016 L 71.173828 66.917969 L 67.40625 66.917969 L 66.640625 66.917969 L 66.640625 57.572266 L 63.708984 57.572266 z " />
<path
id="rect13"
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.649;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 56.24391,72.078606 v 3.51729 h 17.859837 v -3.51729 h -1.210961 v 2.324677 H 57.456705 v -2.324677 z" />
</g>
</g>
</g>
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>default-skin 2</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>
<svg width="264" height="88" viewBox="0 0 264 88" xmlns="http://www.w3.org/2000/svg"><title>default-skin 2</title><g fill="none" fill-rule="evenodd"><g><path d="M67.002 59.5v3.768c-6.307.84-9.184 5.75-10.002 9.732 2.22-2.83 5.564-5.098 10.002-5.098V71.5L73 65.585 67.002 59.5z" id="Shape" fill="#fff"/><g fill="#fff"><path d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z" id="Shape"/></g><g fill="#fff"><path d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"/></g><path d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z" fill="#fff"/><path d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z" fill="#fff"/><path d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z" fill="#fff"/><g><path id="Rectangle-11" fill="#fff" d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z"/><path d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" id="Oval-1" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M150 21h5v1h-5z"/></g><g><path d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z" fill="#fff"/><path d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M106 21h5v1h-5z"/><path fill="#fff" d="M109.043 19.008l-.085 5-1-.017.085-5z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

View File

@@ -15,21 +15,14 @@
touch-action: none;
z-index: 1500;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
/* create separate layer, to avoid paint on window.onscroll in webkit/blink */
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
outline: none;
}
outline: none; }
.pswp * {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
box-sizing: border-box; }
.pswp img {
max-width: none;
}
max-width: none; }
/* style is added when JS option showHideOpacity is set to true */
.pswp--animate_opacity {
@@ -38,33 +31,28 @@
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--open {
display: block;
}
display: block; }
.pswp--zoom-allowed .pswp__img {
/* autoprefixer: off */
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in;
}
cursor: zoom-in; }
.pswp--zoomed-in .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
cursor: grab; }
.pswp--dragging .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
cursor: grabbing; }
/*
Background is added as a separate element.
@@ -78,10 +66,10 @@
height: 100%;
background: #000;
opacity: 0;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
will-change: opacity;
}
will-change: opacity; }
.pswp__scroll-wrap {
position: absolute;
@@ -89,8 +77,7 @@
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
overflow: hidden; }
.pswp__container,
.pswp__zoom-wrap {
@@ -100,8 +87,7 @@
left: 0;
right: 0;
top: 0;
bottom: 0;
}
bottom: 0; }
/* Prevent selection and tap highlights */
.pswp__container,
@@ -111,8 +97,7 @@
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
-webkit-touch-callout: none; }
.pswp__zoom-wrap {
position: absolute;
@@ -122,27 +107,22 @@
transform-origin: left top;
/* for open/close transition */
-webkit-transition: -webkit-transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp__bg {
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
}
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--animated-in .pswp__bg,
.pswp--animated-in .pswp__zoom-wrap {
-webkit-transition: none;
transition: none;
}
transition: none; }
.pswp__container,
.pswp__zoom-wrap {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
-webkit-backface-visibility: hidden; }
.pswp__item {
position: absolute;
@@ -150,40 +130,34 @@
right: 0;
top: 0;
bottom: 0;
overflow: hidden;
}
overflow: hidden; }
.pswp__img {
position: absolute;
width: auto;
height: auto;
top: 0;
left: 0;
}
left: 0; }
/*
stretched thumbnail or div placeholder element (see below)
style is added to avoid flickering in webkit/blink when layers overlap
*/
.pswp__img--placeholder {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
-webkit-backface-visibility: hidden; }
/*
div element that matches size of large image
large image loads on top of it
*/
.pswp__img--placeholder--blank {
background: #222;
}
background: #222; }
.pswp--ie .pswp__img {
width: 100% !important;
height: auto !important;
left: 0;
top: 0;
}
top: 0; }
/*
Error message appears when image is not loaded
@@ -198,10 +172,8 @@
font-size: 14px;
line-height: 16px;
margin-top: -8px;
color: #CCC;
}
color: #CCC; }
.pswp__error-msg a {
color: #CCC;
text-decoration: underline;
}
text-decoration: underline; }

3788
files/pswp/photoswipe.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,7 @@ import re
import urllib.parse
import fnmatch
import json
import html
from typing import Any
from datetime import datetime
from collections import defaultdict
@@ -11,6 +12,7 @@ from tqdm.auto import tqdm
from PIL import Image, ExifTags, TiffImagePlugin, UnidentifiedImageError
from jinja2 import Environment, FileSystemLoader
from defusedxml import ElementTree
from bs4 import BeautifulSoup
from modules.logger import logger
from modules import cclicense
@@ -516,17 +518,18 @@ def process_subfolder(item: str, folder: str, baseurl: str, subfolders: list[dic
def process_license(folder: str, item: str) -> None:
"""
Processes a LICENSE file.
Processes a LICENSE file, preserving formatting in HTML.
Args:
folder (str): The folder containing the info file.
item (str): The licenses file name.
folder (str): The folder containing the LICENSE file.
item (str): The LICENSE file name.
"""
with open(os.path.join(folder, item), encoding="utf-8") as f:
logger.info("processing LICENSE", extra={"path": os.path.join(folder, item)})
licens[urllib.parse.quote(folder)] = (
f.read().replace("\n", "</br>\n").replace(" ", "&emsp;").replace(" ", "&ensp;").replace("sp; ", "sp;&ensp;").replace("&ensp;&ensp;", "&emsp;")
)
path = os.path.join(folder, item)
with open(path, encoding="utf-8") as f:
logger.info("processing LICENSE", extra={"path": path})
raw_text = f.read()
escaped_text = html.escape(raw_text)
licens[urllib.parse.quote(folder)] = f"<pre>{escaped_text}</pre>"
def process_info_file(folder: str, item: str) -> None:
@@ -556,6 +559,11 @@ def should_generate_html(images: list[dict[str, Any]], contains_files, _args: Ar
return images or (_args.use_fancy_folders and not contains_files) or (_args.use_fancy_folders and _args.ignore_other_files)
def format_html(html: str) -> str:
soup = BeautifulSoup(html, "html5lib")
return soup.prettify()
def create_html_file(
folder: str, title: str, foldername: str, images: list[dict[str, Any]], subfolders: list[dict[str, str]], _args: Args, version: str, logo: str, subfoldertags: list[str]
) -> list[str]:
@@ -591,6 +599,7 @@ def create_html_file(
alltags = set()
for img in images:
if img["tags"]:
alltags.update(img["tags"])
alltags.update(set(subfoldertags))
@@ -624,7 +633,7 @@ def create_html_file(
logo=logo,
licensefile=folder_license,
)
f.write(content)
f.write(format_html(content))
html = env.get_template("index.html.j2")
content = html.render(
@@ -646,8 +655,8 @@ def create_html_file(
)
with open(html_file, "w", encoding="utf-8") as f:
logger.info("writing html file", extra={"path": html_file})
f.write(content)
logger.info("writing formatted html file", extra={"path": html_file})
f.write(format_html(content))
return sorted(alltags)

View File

@@ -95,7 +95,7 @@ def setup_logger(level=logging.INFO):
"""
_logger = logging.getLogger(name="defaultlogger")
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "name", "pathname", "process", "processName", "relativeCreated", "thread", "threadName", "taskName"]
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "process", "processName", "relativeCreated", "thread", "threadName"]
custom_format = " ".join(log_format(supported_keys))
formatter = jsonlogger.JsonFormatter(custom_format)
@@ -121,7 +121,7 @@ def setup_consolelogger(level=logging.INFO):
"""
_logger = logging.getLogger(name="consolelogger")
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "name", "pathname", "process", "processName", "relativeCreated", "thread", "threadName", "taskName"]
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "process", "processName", "relativeCreated", "thread", "threadName"]
custom_format = " ".join(log_format(supported_keys))
formatter = jsonlogger.JsonFormatter(custom_format)

View File

@@ -146,7 +146,7 @@ def render_manifest_json(_args: Args, icon_list: list[Icon], colors: dict[str, s
colors : dict[str, str]
dictionary containing color scheme and theme color.
"""
manifest = env.get_template("manifest.json.j2")
manifest = env.get_template("manifest.webmanifest.j2")
content = manifest.render(
name=_args.web_root_url.replace("https://", "").replace("http://", "").replace("/", ""),
short_name=_args.site_title,
@@ -154,8 +154,8 @@ def render_manifest_json(_args: Args, icon_list: list[Icon], colors: dict[str, s
background_color=colors["bcolor1"],
theme_color=colors["theme_color"],
)
with open(os.path.join(_args.root_directory, ".static", "manifest.json"), "w", encoding="utf-8") as f:
logger.info("rendering manifest.json", extra={"path": os.path.join(_args.root_directory, ".static", "manifest.json")})
with open(os.path.join(_args.root_directory, ".static", "manifest.webmanifest"), "w", encoding="utf-8") as f:
logger.info("rendering manifest.webmanifest", extra={"path": os.path.join(_args.root_directory, ".static", "manifest.webmanifest")})
f.write(content)

View File

@@ -1,9 +1,12 @@
CairoSVG==2.7.1
defusedxml==0.7.1
Jinja2==3.1.5
Pillow==11.1.0
pyinstaller==6.11.1
python_json_logger==2.0.7
rich_argparse==1.7.0
selenium==4.28.1
tqdm==4.66.4
beautifulsoup4~=4.13.4
CairoSVG~=2.7.1
defusedxml~=0.7.1
html5lib~=1.1
Jinja2~=3.1.6
jsmin~=3.0.1
Pillow~=11.3.0
pyinstaller~=6.11.1
python_json_logger~=2.0.7
rich_argparse~=1.7.1
selenium~=4.34.2
tqdm~=4.66.4

419
templates/functionality.js Normal file
View File

@@ -0,0 +1,419 @@
class PhotoGallery {
constructor() {
this.pswpElement = document.querySelector(".pswp");
this.items = [];
this.shown = [];
this.subfolders = [];
this.controllers = {};
this.tagDropdownShown = false;
this.debounce = this.debounce.bind(this);
this.openSwipe = this.openSwipe.bind(this);
this.prefetch = this.prefetch.bind(this);
this.cancel = this.cancel.bind(this);
this.reset = this.reset.bind(this);
this.recursive = this.recursive.bind(this);
this.requestMetadata = this.requestMetadata.bind(this);
this.filter = this.filter.bind(this);
this.updateImageList = this.updateImageList.bind(this);
this.setFilter = this.setFilter.bind(this);
this.toggleTag = this.toggleTag.bind(this);
this.setupDropdownToggle = this.setupDropdownToggle.bind(this);
this.setupTagHandlers = this.setupTagHandlers.bind(this);
this.setupClickHandlers = this.setupClickHandlers.bind(this);
this.scrollFunction = this.scrollFunction.bind(this);
this.topFunction = this.topFunction.bind(this);
this.onLoad = this.onLoad.bind(this);
this.init();
}
debounce(fn, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
openSwipe(imgIndex) {
const options = { index: imgIndex };
const gallery = new PhotoSwipe(
this.pswpElement,
PhotoSwipeUI_Default,
this.shown,
options
);
gallery.init();
}
prefetch(imgIndex) {
if (this.controllers[imgIndex]) {
this.cancel(imgIndex);
}
const controller = new AbortController();
const signal = controller.signal;
this.controllers[imgIndex] = controller;
const urlToFetch = this.shown[imgIndex]?.src;
if (urlToFetch) {
fetch(urlToFetch, { method: "GET", signal }).catch(() => {});
}
}
cancel(imgIndex) {
if (this.controllers[imgIndex]) {
this.controllers[imgIndex].abort();
delete this.controllers[imgIndex];
}
}
reset() {
const content = document.documentElement.innerHTML;
const title = document.title;
const folders = document.querySelector(".folders");
let path = window.location.origin + window.location.pathname;
if (path.startsWith("null")) {
path = window.location.protocol + "//" + path.substring(4);
}
if (folders) folders.style.display = "";
document.getElementById("recursive").checked = false;
document
.querySelectorAll("#tagdropdown input.tagcheckbox:checked")
.forEach((checkbox) => (checkbox.checked = false));
window.history.replaceState({ html: content, pageTitle: title }, "", path);
this.requestMetadata();
}
showLoader() {
const imagelist = document.getElementById("imagelist");
imagelist.innerHTML = '<span class="loader"></span>';
imagelist.classList.add("centerload");
imagelist.classList.remove("row");
}
async recursive() {
this.showLoader();
const loc = new URL(window.location.href);
const content = document.documentElement.innerHTML;
const title = document.title;
const isChecked = document.getElementById("recursive")?.checked;
const folders = document.querySelector(".folders");
if (!isChecked) {
if (folders) folders.style.display = "";
loc.searchParams.delete("recursive");
window.history.replaceState({ html: content, pageTitle: title }, "", loc);
this.requestMetadata();
return;
}
if (folders) folders.style.display = "none";
loc.searchParams.delete("recursive");
loc.searchParams.append("recursive", true);
window.history.replaceState({ html: content, pageTitle: title }, "", loc);
const visited = new Set();
const existingItems = new Set();
const newItems = [];
try {
const response = await fetch(".metadata.json");
if (!response.ok) throw new Error("Failed to fetch metadata");
const data = await response.json();
this.items = [];
this.subfolders = data.subfolders || [];
for (const image of Object.values(data.images || {})) {
newItems.push(image);
existingItems.add(image.src);
}
} catch {
return;
}
const fetchFoldersRecursively = async (folderList) => {
if (!Array.isArray(folderList)) return;
const nextLevel = [];
await Promise.all(
folderList.map(async (folder) => {
if (!folder || !folder.metadata || visited.has(folder.url)) return;
visited.add(folder.url);
try {
const response = await fetch(folder.metadata);
if (!response.ok) throw new Error();
const data = await response.json();
for (const image of Object.values(data.images || {})) {
if (!existingItems.has(image.src)) {
newItems.push(image);
existingItems.add(image.src);
}
}
if (Array.isArray(data.subfolders))
nextLevel.push(...data.subfolders);
} catch {}
})
);
if (nextLevel.length > 0) await fetchFoldersRecursively(nextLevel);
};
await fetchFoldersRecursively(this.subfolders);
this.items = [...newItems];
this.filter();
}
requestMetadata() {
this.showLoader();
const hash = window.location.hash;
const searchParams = new URLSearchParams(window.location.search);
fetch(".metadata.json")
.then((response) => {
if (!response.ok) throw new Error("Failed to fetch metadata");
return response.json();
})
.then((data) => {
this.items = Object.values(data.images || {});
this.subfolders = data.subfolders || [];
if (hash != "") {
const selected = hash.replace("#", "").split(",");
this.setFilter(selected);
}
if (searchParams.get("recursive") != null) {
const recChk = document.getElementById("recursive");
if (recChk) recChk.checked = true;
this.recursive();
} else {
this.filter();
}
})
.catch(() => {});
}
filter() {
const searchParams = new URLSearchParams(window.location.search);
this.shown = [];
let path = decodeURIComponent(
window.location.origin +
window.location.pathname.replace("index.html", "")
);
if (path.startsWith("null")) {
path = window.location.protocol + "//" + path.substring(4);
}
const selectedTags = [];
document
.querySelectorAll("#tagdropdown input.tagcheckbox:checked")
.forEach((checkbox) => {
let tag = checkbox.parentElement.id.trim().substring(1);
if (checkbox.parentElement.parentElement.children.length > 1)
tag += "|";
selectedTags.push(tag);
});
const urltags = selectedTags.join(",");
let isRecursiveChecked = false;
try {
isRecursiveChecked =
document.getElementById("recursive")?.checked || false;
} catch {}
for (const item of this.items) {
const tags = item.tags || [];
const include = selectedTags.every((selected) => {
const isParent = selected.endsWith("|");
return isParent
? tags.some((t) => t.startsWith(selected))
: tags.includes(selected);
});
if (include || selectedTags.length === 0) {
if (!isRecursiveChecked) {
if (decodeURIComponent(item.src).replace(item.name, "") === path) {
this.shown.push(item);
}
} else {
this.shown.push(item);
}
}
}
this.updateImageList();
window.location.hash = urltags;
const pid = searchParams.get("pid") - 1;
if (pid != -1) {
this.openSwipe(pid);
}
}
updateImageList() {
const imagelist = document.getElementById("imagelist");
if (!imagelist) return;
let str = "";
this.shown.forEach((item, index) => {
str += `<div class="column"><figure><img src="${item.msrc}" data-index="${index}" /><figcaption class="caption">${item.name}`;
if (item.tiff) str += `&nbsp;<a href="${item.tiff}">TIFF</a>`;
if (item.raw) str += `&nbsp;<a href="${item.raw}">RAW</a>`;
str += "</figcaption></figure></div>";
});
imagelist.classList.add("row");
imagelist.classList.remove("centerload");
imagelist.innerHTML = str;
}
setFilter(selected) {
document
.querySelectorAll("#tagdropdown input.tagcheckbox")
.forEach((checkbox) => {
selected.forEach((tag) => {
if (
checkbox.parentElement.id
.trim()
.substring(1)
.replace(" ", "%20") === tag
) {
checkbox.checked = true;
}
});
});
}
toggleTag(tagid) {
const tag = document.getElementById(tagid);
const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent");
const svg = tag?.parentElement.querySelector(".tagtoggle svg");
if (!ol || !svg) return;
ol.classList.toggle("show");
svg.style.transform = ol.classList.contains("show")
? "rotate(180deg)"
: "rotate(0deg)";
}
setupDropdownToggle() {
const toggleLink = document.getElementById("tagtogglelink");
const dropdown = document.getElementById("tagdropdown");
if (!toggleLink) return;
toggleLink.addEventListener("click", (event) => {
event.stopPropagation();
const svg = toggleLink.querySelector("svg");
dropdown.classList.toggle("show");
if (svg)
svg.style.transform = dropdown.classList.contains("show")
? "rotate(180deg)"
: "rotate(0deg)";
this.tagDropdownShown = dropdown.classList.contains("show");
});
document.addEventListener("click", (event) => {
if (
!dropdown.contains(event.target) &&
!toggleLink.contains(event.target)
) {
dropdown.classList.remove("show");
this.tagDropdownShown = false;
const svg = toggleLink.querySelector("svg");
if (svg) svg.style.transform = "rotate(0deg)";
}
});
}
setupTagHandlers() {
const tagContainer = document.getElementById("tagdropdown");
if (!tagContainer) return;
const debouncedFilter = this.debounce(this.filter, 150);
tagContainer.addEventListener("change", debouncedFilter);
tagContainer.addEventListener("click", (event) => {
const toggle = event.target.closest(".tagtoggle");
if (toggle) {
event.stopPropagation();
const tagid = toggle.dataset.toggleid;
this.toggleTag(tagid);
}
});
}
setupClickHandlers() {
const resetEl = document
.getElementById("reset-filter")
?.querySelector("label");
if (resetEl) resetEl.addEventListener("click", this.reset);
const recurseEl = document.getElementById("recursive");
if (recurseEl)
recurseEl.addEventListener("change", this.debounce(this.recursive, 150));
const totop = document.getElementById("totop");
if (totop) totop.addEventListener("click", this.topFunction);
const imagelist = document.getElementById("imagelist");
if (imagelist) {
imagelist.addEventListener("click", (event) => {
const img = event.target.closest("img");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.openSwipe(index);
});
imagelist.addEventListener("mouseover", (event) => {
const img = event.target.closest("img");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.prefetch(index);
});
imagelist.addEventListener("mouseleave", (event) => {
const img = event.target.closest("img");
if (!img || !img.dataset.index) return;
const index = parseInt(img.dataset.index);
if (!isNaN(index)) this.cancel(index);
});
}
}
scrollFunction() {
const totopbutton = document.getElementById("totop");
if (!totopbutton) return;
if (
document.body.scrollTop > 20 ||
document.documentElement.scrollTop > 20
) {
totopbutton.style.display = "block";
} else {
totopbutton.style.display = "none";
}
}
topFunction() {
window.scrollTo({ top: 0, behavior: "smooth" });
}
onLoad() {
document.querySelectorAll(".tagtoggle").forEach((toggle) => {
toggle.addEventListener("mouseup", (event) => {
event.stopPropagation();
const tagid = toggle.getAttribute("data-tagid");
this.toggleTag(tagid);
});
});
this.requestMetadata();
this.setupDropdownToggle();
this.setupTagHandlers();
this.setupClickHandlers();
window.addEventListener("scroll", this.scrollFunction);
}
init() {
if (window.addEventListener) {
window.addEventListener("load", this.onLoad, false);
} else if (window.attachEvent) {
window.attachEvent("onload", this.onLoad);
}
}
}

View File

@@ -28,7 +28,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
{%- if webmanifest %}
<link rel="manifest" href="/.static/manifest.json">
<link rel="manifest" href="/.static/manifest.webmanifest">
{%- endif %}
<link rel="preload" href="{{ stylesheet }}" as="style">
{%- if theme %}
@@ -43,10 +43,12 @@
<link rel="preload" href="{{ root }}.static/pswp/default-skin/default-skin.css" as="style">
<link rel="modulepreload" href="{{ root }}.static/pswp/photoswipe.min.js">
<link rel="modulepreload" href="{{ root }}.static/pswp/photoswipe-ui-default.min.js">
<link rel="modulepreload" href="{{ root }}.static/functionality.min.js">
<link rel="stylesheet" href="{{ root }}.static/pswp/photoswipe.css">
<link rel="stylesheet" href="{{ root }}.static/pswp/default-skin/default-skin.css">
<script src="{{ root }}.static/pswp/photoswipe.min.js"></script>
<script src="{{ root }}.static/pswp/photoswipe-ui-default.min.js"></script>
<script src="{{ root }}.static/functionality.min.js"></script>
</head>
<body>
@@ -78,8 +80,9 @@
</g>
</svg></a>
<ol class="tooltiptext tagdropdown" id="tagdropdown">
<span class="tagentry" id="reset-filter"><label>reset filter</label></span>
<span class="tagentry">
<label onclick="recursive()">
<label>
<input type="checkbox" id="recursive" />recursive filter
</label>
</span>
@@ -130,14 +133,14 @@
{%- endif %}
<span class="attribution">Made with <a href="https://github.com/greflm13/StaticGalleryBuilder" target="_blank" rel="noopener noreferrer">StaticGalleryBuilder {{ version }}</a> by <a
href="https://github.com/greflm13" target="_blank" rel="noopener noreferrer">{{ logo }}</a>.</span>
<button type="button" onclick="topFunction()" id="totop" title="Back to Top">Back to Top</button>
<button type="button" id="totop" title="Back to Top">Back to Top</button>
</div>
{%- endif %}
{%- else %}
<div class="footer">
<span class="attribution">Made with <a href="https://github.com/greflm13/StaticGalleryBuilder" target="_blank" rel="noopener noreferrer">StaticGalleryBuilder {{ version }}</a> by <a
href="https://github.com/greflm13" target="_blank" rel="noopener noreferrer">{{ logo }}</a>.</span>
<button type="button" onclick="topFunction()" id="totop" title="Back to Top">Back to Top</button>
<button type="button" id="totop" title="Back to Top">Back to Top</button>
</div>
{%- endif %}
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
@@ -176,329 +179,9 @@
</div>
</div>
</div>
<script>
const pswpElement = document.querySelectorAll('.pswp')[0];
const re = /pid=(\d+)/;
const filterre = /#(.*)/;
const recursere = /\?recursive/;
let items = [];
let shown = [];
let subfolders = [];
let controllers = {};
let tagdropdownshown = false;
function requestMetadata() {
fetch(".metadata.json").then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
items = Object.values(data.images);
subfolders = data.subfolders;
if (filterre.test(window.location.href)) {
const selected = window.location.href.match(filterre)[1].split(",");
setFilter(selected);
}
if (recursere.test(window.location.href)) {
document.getElementById("recursive").checked = true;
recursive();
}
filter();
if (re.test(window.location.href)) {
const pid = window.location.href.match(re)[1];
openSwipe(parseInt(pid));
}
})
.catch(error => console.error('Failed to fetch data:', error));
}
function setupTagHandlers() {
const tagContainer = document.getElementById("tagdropdown");
if (tagContainer == null) {
return;
}
tagContainer.addEventListener("change", debounce(filter, 150));
tagContainer.addEventListener("click", function (event) {
const toggle = event.target.closest(".tagtoggle");
if (toggle) {
event.stopPropagation();
const tagid = toggle.dataset.toggleid;
toggleTag(tagid);
}
});
}
function toggleTag(tagid) {
const tag = document.getElementById(tagid);
const ol = tag?.closest(".tagentry")?.querySelector(".tagentryparent");
const svg = tag?.parentElement.querySelector(".tagtoggle svg");
if (!ol || !svg) return;
ol.classList.toggle("show");
svg.style.transform = ol.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
}
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
function openSwipe(img) {
const options = {
index: img
};
const gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, shown, options);
gallery.init();
}
async function recursive(sub = undefined) {
const curr = window.location.href.split("#");
const content = document.getRootNode().innerHTML;
const title = document.title;
const isChecked = document.getElementById("recursive").checked;
const folders = document.querySelector(".folders");
if (!isChecked) {
if (folders) folders.style.display = "";
window.history.replaceState({ html: content, pageTitle: title }, "", curr[0].split("?")[0] + "#" + curr[1]);
requestMetadata();
return;
}
if (folders) folders.style.display = "none";
window.history.replaceState({ html: content, pageTitle: title }, "", curr[0].split("?")[0] + "?recursive#" + curr[1]);
const visited = new Set();
const existingItems = new Set();
const newItems = [];
try {
const response = await fetch(".metadata.json");
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
items = [];
subfolders = data.subfolders || [];
sub = subfolders;
for (const image of Object.values(data.images || {})) {
newItems.push(image);
existingItems.add(image.src);
}
} catch (error) {
console.error("Failed to fetch base .metadata.json:", error);
return;
}
async function fetchFoldersRecursively(folderList) {
if (!Array.isArray(folderList)) return;
const nextLevel = [];
await Promise.all(folderList.map(async (folder) => {
if (!folder || !folder.metadata || visited.has(folder.url)) return;
visited.add(folder.url);
if (!folder.metadata) return;
try {
const response = await fetch(folder.metadata);
if (!response.ok) throw new Error(`Failed to fetch ${folder.metadata}`);
const data = await response.json();
for (const image of Object.values(data.images || {})) {
if (!existingItems.has(image.src)) {
newItems.push(image);
existingItems.add(image.src);
}
}
if (Array.isArray(data.subfolders)) {
nextLevel.push(...data.subfolders);
}
} catch (error) {
console.error("Failed to fetch folder metadata:", error);
}
}));
if (nextLevel.length > 0) {
await fetchFoldersRecursively(nextLevel);
}
}
await fetchFoldersRecursively(sub);
items = [...newItems];
filter();
}
const totopbutton = document.getElementById("totop");
window.onscroll = function () { scrollFunction() };
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
totopbutton.style.display = "block";
} else {
totopbutton.style.display = "none";
}
}
function topFunction() {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
function updateImageList() {
let str = ""
let imagelist = document.getElementById("imagelist");
shown.forEach((item, index) => {
str += '<div class="column"><figure><img src="' + item.msrc + '" onclick="openSwipe(' + index + ')" onmouseover="prefetch(' + index + ')" onmouseleave="cancel(' + index + ')" /><figcaption class="caption">' + item.name;
if (item.tiff != "" & item.tiff != undefined) {
str += ' <a href="' + item.tiff + '">TIFF</a>';
}
if (item.raw != "" & item.raw != undefined) {
str += ' <a href="' + item.raw + '">RAW</a>';
}
str += '</figcaption></figure></div>';
});
imagelist.innerHTML = str;
}
function prefetch(img) {
const controller = new AbortController()
const signal = controller.signal
controllers[img] = controller;
let urlToFetch = items[img].src;
fetch(urlToFetch, {
method: 'get',
signal: signal,
}).catch(function (err) { });
}
function cancel(img) {
controllers[img].abort();
delete controllers[img];
}
function filter() {
shown = [];
let isRecursiveChecked = false;
const curr = window.location.href.split("#")[0] + "#";
const path = decodeURIComponent(window.location.href.split("#")[0].replace("index.html", ""))
const selected_tags = [];
const tagcheckboxes = document.querySelectorAll("#tagdropdown input[class='tagcheckbox']:checked");
tagcheckboxes.forEach((checkbox) => {
let tag = checkbox.parentElement.id.trim().substring(1);
if (checkbox.parentElement.parentElement.children.length > 1) {
tag += "|"
}
selected_tags.push(tag);
});
const urltags = selected_tags.join(",");
try {
isRecursiveChecked = document.getElementById("recursive").checked;
} catch { }
for (const item of items) {
const tags = item.tags || [];
const include = selected_tags.every(selected => {
const isParent = selected.endsWith('|');
if (isParent) {
return tags.some(t => t.startsWith(selected));
} else {
return tags.includes(selected);
}
});
if (include || selected_tags.length === 0) {
if (!isRecursiveChecked) {
if (decodeURIComponent(item.src.replace(item.name, "")) == path) {
shown.push(item);
}
} else {
shown.push(item);
}
}
}
updateImageList();
window.location.href = curr + urltags;
}
function setFilter(selected) {
const tagcheckboxes = document.querySelectorAll("#tagdropdown input[class='tagcheckbox']");
selected.forEach((tag) => {
tagcheckboxes.forEach((checkbox) => {
if (checkbox.parentElement.id.trim().substring(1).replace(" ", "%20") == tag) {
checkbox.checked = true;
}
});
});
}
function setupDropdownToggle() {
const toggleLink = document.getElementById("tagtogglelink");
const dropdown = document.getElementById("tagdropdown");
if (toggleLink == null) {
return;
}
toggleLink.addEventListener("click", function (event) {
event.stopPropagation();
const svg = this.querySelector("svg");
dropdown.classList.toggle("show");
if (svg) svg.style.transform = dropdown.classList.contains("show") ? "rotate(180deg)" : "rotate(0deg)";
tagdropdownshown = dropdown.classList.contains("show");
});
document.addEventListener("click", function (event) {
if (!dropdown.contains(event.target) && !toggleLink.contains(event.target)) {
dropdown.classList.remove("show");
tagdropdownshown = false;
const svg = toggleLink.querySelector("svg");
if (svg) svg.style.transform = "rotate(0deg)";
}
});
}
function onLoad() {
document.querySelectorAll('.tagtoggle').forEach(toggle => {
toggle.addEventListener('mouseup', function (event) {
event.stopPropagation();
const tagid = this.getAttribute('data-tagid');
toggleTag(tagid);
});
});
requestMetadata();
setupDropdownToggle();
setupTagHandlers();
const recurseEl = document.getElementById("recursive")
if (recurseEl != null) { recurseEl.addEventListener("change", debounce(recursive, 150)); }
}
window.addEventListener ?
window.addEventListener("load", onLoad, false) :
window.attachEvent && window.attachEvent("onload", onLoad);
</script>
</body>
<script>
new PhotoGallery();
</script>
</html>

View File

@@ -21,13 +21,16 @@
<body>
<div class="header">
<ul class="navbar">
<ol class="navbar">
<div class="navleft">
<li><a href="{{ root }}">Home</a></li>
{%- if parent %}
<li><a href="{{ parent }}">Parent Directory</a></li>
{%- endif %}
</div>
<div class="navcenter">
<li class="title"><span class="header">{{ header }}</span></li>
</ul>
</div>
</div>
{%- if licensefile %}
<div class="licensefile">

View File

@@ -118,3 +118,23 @@ body {
color: var(--bcolor1);
font-weight: 600;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid var(--bcolor1);
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -117,3 +117,23 @@ body {
color: var(--bcolor2);
font-weight: 600;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid var(--bcolor2);
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -96,6 +96,52 @@
font-weight: 600;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid var(--bcolor2);
animation: prixClipFix 2s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg)
}
}
@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0)
}
25% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0)
}
50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%)
}
75% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%)
}
100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0)
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);

View File

@@ -96,6 +96,52 @@
font-weight: 600;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid var(--bcolor2);
animation: prixClipFix 2s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg)
}
}
@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0)
}
25% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0)
}
50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%)
}
75% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%)
}
100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0)
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);

View File

@@ -1,14 +1,22 @@
@import url("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&display=swap");
* {
--color1: #FF7F50; /* Coral */
--color2: #D2691E; /* Chocolate */
--color3: #8B4513; /* SaddleBrown */
--color4: #FFA07A; /* LightSalmon */
--bcolor1: #FFF8DC; /* Cornsilk */
--bcolor2: #2F4F4F; /* DarkSlateGray */
--bcolor3: #3E2723; /* Darker brown */
--bcolor4: #4E342E; /* Slightly lighter brown */
--color1: #FF7F50;
/* Coral */
--color2: #D2691E;
/* Chocolate */
--color3: #8B4513;
/* SaddleBrown */
--color4: #FFA07A;
/* LightSalmon */
--bcolor1: #FFF8DC;
/* Cornsilk */
--bcolor2: #2F4F4F;
/* DarkSlateGray */
--bcolor3: #3E2723;
/* Darker brown */
--bcolor4: #4E342E;
/* Slightly lighter brown */
}
.navbar {
@@ -102,6 +110,54 @@
font-family: "Playfair Display", serif;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--bcolor2);
font-size: 7px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em
}
40% {
box-shadow: 0 2.5em 0 0
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor3);

View File

@@ -95,6 +95,36 @@
font-weight: 800;
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--color1);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor3);

View File

@@ -103,6 +103,91 @@
font-family: "Nunito", sans-serif;
}
.loader {
transform: rotateZ(45deg);
perspective: 1000px;
border-radius: 50%;
width: 48px;
height: 48px;
color: var(--color1);
}
.loader:before,
.loader:after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
width: inherit;
height: inherit;
border-radius: 50%;
transform: rotateX(70deg);
animation: 1s spin linear infinite;
}
.loader:after {
color: var(--bcolor4);
transform: rotateY(70deg);
animation-delay: .4s;
}
@keyframes rotate {
0% {
transform: translate(-50%, -50%) rotateZ(0deg);
}
100% {
transform: translate(-50%, -50%) rotateZ(360deg);
}
}
@keyframes rotateccw {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(-360deg);
}
}
@keyframes spin {
0%,
100% {
box-shadow: .2em 0px 0 0px currentcolor;
}
12% {
box-shadow: .2em .2em 0 0 currentcolor;
}
25% {
box-shadow: 0 .2em 0 0px currentcolor;
}
37% {
box-shadow: -.2em .2em 0 0 currentcolor;
}
50% {
box-shadow: -.2em 0 0 0 currentcolor;
}
62% {
box-shadow: -.2em -.2em 0 0 currentcolor;
}
75% {
box-shadow: 0px -.2em 0 0 currentcolor;
}
87% {
box-shadow: .2em -.2em 0 0 currentcolor;
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);

View File

@@ -95,6 +95,36 @@
font-weight: 800;
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--color1);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor3);

View File

@@ -74,6 +74,27 @@
font-weight: 800;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid var(--bcolor1);
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
body {
color: var(--bcolor1);
background-color: var(--color4);

View File

@@ -74,6 +74,27 @@
font-weight: 800;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
border-top: 3px solid var(--bcolor2);
border-right: 3px solid transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor1);

View File

@@ -93,6 +93,36 @@
background-color: var(--color1);
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--color1);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor3);

View File

@@ -94,6 +94,36 @@
font-weight: 800;
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--bcolor1);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor2);

View File

@@ -77,6 +77,91 @@
font-weight: 800;
}
.loader {
transform: rotateZ(45deg);
perspective: 1000px;
border-radius: 50%;
width: 48px;
height: 48px;
color: var(--color2);
}
.loader:before,
.loader:after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
width: inherit;
height: inherit;
border-radius: 50%;
transform: rotateX(70deg);
animation: 1s spin linear infinite;
}
.loader:after {
color: var(--color4);
transform: rotateY(70deg);
animation-delay: .4s;
}
@keyframes rotate {
0% {
transform: translate(-50%, -50%) rotateZ(0deg);
}
100% {
transform: translate(-50%, -50%) rotateZ(360deg);
}
}
@keyframes rotateccw {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(-360deg);
}
}
@keyframes spin {
0%,
100% {
box-shadow: .2em 0px 0 0px currentcolor;
}
12% {
box-shadow: .2em .2em 0 0 currentcolor;
}
25% {
box-shadow: 0 .2em 0 0px currentcolor;
}
37% {
box-shadow: -.2em .2em 0 0 currentcolor;
}
50% {
box-shadow: -.2em 0 0 0 currentcolor;
}
62% {
box-shadow: -.2em -.2em 0 0 currentcolor;
}
75% {
box-shadow: 0px -.2em 0 0 currentcolor;
}
87% {
box-shadow: .2em -.2em 0 0 currentcolor;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor4);

View File

@@ -98,6 +98,36 @@
font-weight: 800;
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--color4);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor3);
background-color: var(--bcolor1);

View File

@@ -1,14 +1,22 @@
@import url("https://fonts.googleapis.com/css2?family=Lora:wght@300;400;500;600;700&display=swap");
* {
--color1: #FFB6C1; /* LightPink */
--color2: #98FB98; /* PaleGreen */
--color3: #FFD700; /* Gold */
--color4: #87CEFA; /* LightSkyBlue */
--bcolor1: #FFFFFF; /* White */
--bcolor2: #2F4F4F; /* DarkSlateGray */
--bcolor3: #FAF0E6; /* Linen */
--bcolor4: #E6E6FA; /* Lavender */
--color1: #FFB6C1;
/* LightPink */
--color2: #98FB98;
/* PaleGreen */
--color3: #FFD700;
/* Gold */
--color4: #87CEFA;
/* LightSkyBlue */
--bcolor1: #FFFFFF;
/* White */
--bcolor2: #2F4F4F;
/* DarkSlateGray */
--bcolor3: #FAF0E6;
/* Linen */
--bcolor4: #E6E6FA;
/* Lavender */
}
.navbar {
@@ -102,6 +110,54 @@
font-family: "Lora", serif;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--bcolor2);
font-size: 7px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em
}
40% {
box-shadow: 0 2.5em 0 0
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);

View File

@@ -102,6 +102,52 @@
font-weight: 700;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid var(--color2);
animation: prixClipFix 2s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg)
}
}
@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0)
}
25% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0)
}
50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%)
}
75% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%)
}
100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0)
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor2);

View File

@@ -104,6 +104,54 @@
font-family: "Roboto", sans-serif;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--bcolor2);
font-size: 7px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em
}
40% {
box-shadow: 0 2.5em 0 0
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);

View File

@@ -96,6 +96,36 @@
font-weight: 800;
}
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: var(--color1);
box-sizing: border-box;
animation: animloader 1s linear infinite alternate;
}
@keyframes animloader {
0% {
box-shadow: -38px -12px, -14px 0, 14px 0, 38px 0;
}
33% {
box-shadow: -38px 0px, -14px -12px, 14px 0, 38px 0;
}
66% {
box-shadow: -38px 0px, -14px 0, 14px -12px, 38px 0;
}
100% {
box-shadow: -38px 0, -14px 0, 14px 0, 38px -12px;
}
}
body {
color: var(--bcolor1);
background-color: var(--bcolor3);

View File

@@ -111,6 +111,54 @@
font-family: "Montserrat", sans-serif;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: var(--bcolor2);
font-size: 7px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@keyframes bblFadInOut {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em
}
40% {
box-shadow: 0 2.5em 0 0
}
}
body {
color: var(--bcolor2);
background-color: var(--bcolor3);