Reorganize http resources and build logic
This commit is contained in:
parent
691f92d90d
commit
5ab9ad02de
471
build.py
471
build.py
@ -1,14 +1,18 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
|
import hashlib
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import List
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
from typing import Tuple
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
@ -19,80 +23,108 @@ import ruamel.yaml
|
|||||||
yaml = ruamel.yaml.YAML(typ="safe")
|
yaml = ruamel.yaml.YAML(typ="safe")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def multi_replace(source: str, replacements: Sequence[Tuple[str, str]]) -> str:
|
||||||
class MediaContainer:
|
for old, new in replacements:
|
||||||
|
replaced = source.replace(old, new)
|
||||||
|
return replaced
|
||||||
|
|
||||||
|
|
||||||
|
class PathField(msh.fields.String):
|
||||||
|
def _deserialize(self, value, *args, **kwargs):
|
||||||
|
return Path(value).expanduser().resolve()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSchema(msh.Schema):
|
||||||
|
@msh.post_load
|
||||||
|
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs):
|
||||||
|
return self.Container(**data)
|
||||||
|
|
||||||
|
|
||||||
|
class MediaSerializer(BaseSchema):
|
||||||
|
@dataclass
|
||||||
|
class Container:
|
||||||
title: str
|
title: str
|
||||||
source: str
|
|
||||||
preview: Optional[str] = None
|
|
||||||
anchor: Optional[Union[str, int]] = None
|
|
||||||
content: Optional[str] = None
|
|
||||||
hide_source: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class MediaSerializer(msh.Schema):
|
|
||||||
title = msh.fields.String(required=True)
|
|
||||||
source = msh.fields.String(required=True)
|
|
||||||
preview = msh.fields.String(required=False)
|
|
||||||
anchor = msh.fields.String(required=False)
|
|
||||||
content = msh.fields.String(required=False)
|
|
||||||
hide_source = msh.fields.Boolean(required=False)
|
|
||||||
|
|
||||||
@msh.post_load
|
|
||||||
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> MediaContainer:
|
|
||||||
return MediaContainer(**data)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LinkContainer:
|
|
||||||
link: str
|
link: str
|
||||||
title: Optional[str] = None
|
anchor: str
|
||||||
icon: Optional[str] = None
|
content: Optional[str]
|
||||||
|
|
||||||
|
def preload_url(self, config) -> str:
|
||||||
|
if config.build.kodak:
|
||||||
|
return f"{config.build.kodak.baseurl}image/{self.link}/{config.build.kodak.preload}.jpeg"
|
||||||
|
return self.link
|
||||||
|
|
||||||
class LinkSerializer(msh.Schema):
|
def asset_url(self, config) -> str:
|
||||||
link = msh.fields.URL(required=True)
|
if config.build.kodak:
|
||||||
title = msh.fields.String(required=False)
|
return f"{config.build.kodak.baseurl}image/{self.link}/{config.build.kodak.asset}.jpeg"
|
||||||
icon = msh.fields.String(required=False)
|
return self.link
|
||||||
|
|
||||||
|
def source_url(self, config) -> str:
|
||||||
|
if config.build.kodak:
|
||||||
|
return f"{config.build.kodak.baseurl}image/{self.link}/original"
|
||||||
|
return self.link
|
||||||
|
|
||||||
|
title = msh.fields.String()
|
||||||
|
link = msh.fields.String()
|
||||||
|
anchor = msh.fields.String(allow_none=True, missing=None)
|
||||||
|
content = msh.fields.String(allow_none=True, missing=None)
|
||||||
|
|
||||||
@msh.post_load
|
@msh.post_load
|
||||||
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> LinkContainer:
|
def _make_default_anchor(self, data, **kwargs):
|
||||||
return LinkContainer(**data)
|
if not data.anchor:
|
||||||
|
data.anchor = multi_replace(
|
||||||
|
data.title, [(" ", "-"), ("?", ""), ("!", ""), (".", ""), (":", "")]
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class Location(NamedTuple):
|
class LinkSerializer(BaseSchema):
|
||||||
|
@dataclass
|
||||||
|
class Container:
|
||||||
|
title: Optional[str]
|
||||||
|
url: str
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
url = msh.fields.URL()
|
||||||
|
title = msh.fields.String(allow_none=True, missing=None)
|
||||||
|
icon = msh.fields.String(missing="fas fa-external-link-square-alt")
|
||||||
|
|
||||||
|
|
||||||
|
class LocationSeralizer(BaseSchema):
|
||||||
|
class Container(NamedTuple):
|
||||||
title: str
|
title: str
|
||||||
link: str
|
link: str
|
||||||
|
|
||||||
|
title = msh.fields.String()
|
||||||
class LocationSeralizer(msh.Schema):
|
link = msh.fields.URL()
|
||||||
title = msh.fields.String(required=True)
|
|
||||||
link = msh.fields.URL(required=True)
|
|
||||||
|
|
||||||
@msh.post_load
|
|
||||||
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> Location:
|
|
||||||
return Location(**data)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class PostSerializer(BaseSchema):
|
||||||
class PostContainer:
|
@dataclass
|
||||||
|
class Container:
|
||||||
title: str
|
title: str
|
||||||
location: Location
|
description: Optional[str]
|
||||||
|
location: LocationSeralizer.Container
|
||||||
date: datetime.date
|
date: datetime.date
|
||||||
banner: str
|
banner: Optional[str]
|
||||||
media: Sequence[MediaContainer]
|
slug: str
|
||||||
links: Sequence[LinkContainer] = ()
|
links: Sequence[LinkSerializer.Container]
|
||||||
slug: Optional[str] = None
|
media: Sequence[MediaSerializer.Container]
|
||||||
|
|
||||||
|
def banner_url(self, config) -> str:
|
||||||
|
if config.build.kodak:
|
||||||
|
return f"{config.build.kodak.baseurl}image/{self.banner}/{config.build.kodak.banner}.jpeg"
|
||||||
|
return self.banner
|
||||||
|
|
||||||
class PostSerializer(msh.Schema):
|
title = msh.fields.String()
|
||||||
|
description = msh.fields.String(missing=None, allow_none=True)
|
||||||
title = msh.fields.String(required=True)
|
location = msh.fields.Nested(LocationSeralizer)
|
||||||
location = msh.fields.Nested(LocationSeralizer, required=True)
|
date = msh.fields.Raw()
|
||||||
date = msh.fields.Date("%Y-%m-%d", required=True)
|
banner = msh.fields.String(missing=None, allow_none=True)
|
||||||
banner = msh.fields.URL(required=True)
|
slug = msh.fields.String(
|
||||||
slug = msh.fields.String(required=False)
|
validate=msh.validate.Regexp(r"^[a-z0-9][a-z0-9\-]+[a-z0-9]$")
|
||||||
links = msh.fields.List(msh.fields.Nested(LinkSerializer), required=False)
|
)
|
||||||
media = msh.fields.List(msh.fields.Nested(MediaSerializer), required=True)
|
links = msh.fields.List(msh.fields.Nested(LinkSerializer), missing=list())
|
||||||
|
media = msh.fields.List(msh.fields.Nested(MediaSerializer), missing=list())
|
||||||
|
|
||||||
@msh.validates_schema
|
@msh.validates_schema
|
||||||
def _unique_anchors(self, data: Dict[str, Any], **kwargs):
|
def _unique_anchors(self, data: Dict[str, Any], **kwargs):
|
||||||
@ -102,87 +134,306 @@ class PostSerializer(msh.Schema):
|
|||||||
f"Media anchors used multiple times: {set([item for item in anchors if anchors.count(item) > 1])}"
|
f"Media anchors used multiple times: {set([item for item in anchors if anchors.count(item) > 1])}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@msh.post_load
|
|
||||||
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> PostContainer:
|
class ConfigBuildKodakSerializer(BaseSchema):
|
||||||
for index, item in enumerate(data["media"]):
|
@dataclass
|
||||||
item.anchor = item.anchor or index
|
class Container:
|
||||||
data["media"][index] = item
|
baseurl: str
|
||||||
return PostContainer(**data)
|
link_original: bool
|
||||||
|
asset: str
|
||||||
|
banner: str
|
||||||
|
preload: str
|
||||||
|
|
||||||
|
baseurl = msh.fields.URL()
|
||||||
|
link_original = msh.fields.Boolean(missing=False)
|
||||||
|
asset = msh.fields.String()
|
||||||
|
banner = msh.fields.String()
|
||||||
|
preload = msh.fields.String()
|
||||||
|
|
||||||
|
|
||||||
class ConfigSerializer(msh.Schema):
|
class ConfigBuildSerializer(BaseSchema):
|
||||||
static = msh.fields.List(msh.fields.String(), required=False)
|
@dataclass
|
||||||
posts = msh.fields.List(msh.fields.Nested(PostSerializer), required=True)
|
class Container:
|
||||||
|
generated: Path
|
||||||
|
posts: Path
|
||||||
|
static: Path
|
||||||
|
bundle: Path
|
||||||
|
templates: Path
|
||||||
|
post_base: str
|
||||||
|
kodak: ConfigBuildKodakSerializer.Container
|
||||||
|
|
||||||
@msh.validates_schema
|
generated = PathField(missing=Path("publish"))
|
||||||
def _unique_slugs(self, data: Dict[str, Any], **kwargs):
|
posts = PathField(missing=Path("posts"))
|
||||||
slugs = [item.slug for item in data["posts"] if item.slug is not None]
|
static = PathField(missing=Path("static"))
|
||||||
if len(slugs) != len(set(slugs)):
|
bundle = PathField(missing=Path("bundle"))
|
||||||
raise msh.ValidationError(
|
templates = PathField(missing=Path("templates"))
|
||||||
f"Post slugs used multiple times: {set([item for item in slugs if slugs.count(item) > 1])}"
|
post_base = msh.fields.String(
|
||||||
|
missing="explore", validate=msh.validate.Regexp(r"[a-z0-9\-]+")
|
||||||
)
|
)
|
||||||
|
kodak = msh.fields.Nested(ConfigBuildKodakSerializer, missing=None)
|
||||||
|
|
||||||
@msh.post_load
|
|
||||||
def _remove_future(self, data: Dict[str, Any], *args, **kwargs) -> PostContainer:
|
class ConfigSerializer(BaseSchema):
|
||||||
posts = [item for item in data["posts"] if item.date <= datetime.date.today()]
|
@dataclass
|
||||||
data["posts"] = posts
|
class Container:
|
||||||
return data
|
domain: str
|
||||||
|
https: bool
|
||||||
|
baseurl: str
|
||||||
|
title: str
|
||||||
|
email: str
|
||||||
|
description: str
|
||||||
|
keywords: Sequence[str]
|
||||||
|
social: Dict[str, str]
|
||||||
|
build: ConfigBuildSerializer.Container
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str:
|
||||||
|
return f"http{'s' if self.https else ''}://{self.domain}{self.baseurl}"
|
||||||
|
|
||||||
|
domain = msh.fields.String()
|
||||||
|
https = msh.fields.Boolean(missing=True)
|
||||||
|
baseurl = msh.fields.String()
|
||||||
|
title = msh.fields.String()
|
||||||
|
email = msh.fields.Email()
|
||||||
|
description = msh.fields.String()
|
||||||
|
keywords = msh.fields.List(
|
||||||
|
msh.fields.String(validate=msh.validate.Regexp(r"^[a-z0-9]+$"))
|
||||||
|
)
|
||||||
|
social = msh.fields.Dict(
|
||||||
|
keys=msh.fields.String(
|
||||||
|
validate=msh.validate.OneOf(
|
||||||
|
["instagram", "facebook", "twitter", "mastodon", "patreon"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
values=msh.fields.Url(),
|
||||||
|
missing=dict(),
|
||||||
|
)
|
||||||
|
build = msh.fields.Nested(ConfigBuildSerializer)
|
||||||
|
|
||||||
|
|
||||||
def get_args() -> argparse.Namespace:
|
def get_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config", help="Path to the config file", default=(Path.cwd() / "config.yaml")
|
"-c",
|
||||||
|
"--config",
|
||||||
|
help="Path to the config file",
|
||||||
|
default=(Path.cwd() / "config.yaml"),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c", "--check", action="store_true", help="Check the config without building"
|
"--check", action="store_true", help="Check the config without building"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dev",
|
||||||
|
action="store_true",
|
||||||
|
help="Run local development server",
|
||||||
)
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_from_file(path: Union[str, Path]):
|
||||||
|
"""Construct from a file path, generating the hash of the file
|
||||||
|
|
||||||
|
.. note:: This method attempts to _efficiently_ compute a hash of large image files. The
|
||||||
|
hashing code was adapted from here:
|
||||||
|
|
||||||
|
https://stackoverflow.com/a/44873382/5361209
|
||||||
|
"""
|
||||||
|
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
view = memoryview(bytearray(1024 * 1024))
|
||||||
|
with Path(path).open("rb", buffering=0) as infile:
|
||||||
|
for chunk in iter(lambda: infile.readinto(view), 0): # type: ignore
|
||||||
|
hasher.update(view[:chunk])
|
||||||
|
|
||||||
|
return hasher
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_resource(path: Path, dest_dir: Path):
|
||||||
|
if path.is_file():
|
||||||
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(path, dest_dir / path.name, follow_symlinks=True)
|
||||||
|
elif path.is_dir():
|
||||||
|
for item in path.iterdir():
|
||||||
|
_copy_resource(item, dest_dir / path.name)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_template(env: jinja2.Environment, name: str, dest: Path, **kwargs):
|
||||||
|
dest.parent.mkdir(exist_ok=True)
|
||||||
|
template = env.get_template(name).render(**kwargs)
|
||||||
|
with dest.open("w") as outfile:
|
||||||
|
outfile.write(template)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_bundle(
|
||||||
|
config: ConfigSerializer.Container, ftype: str, dest: str, sources: List[str]
|
||||||
|
) -> str:
|
||||||
|
(config.build.generated / ftype.lower()).mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
working_path = (
|
||||||
|
config.build.generated / ftype.lower() / f"{uuid.uuid4().hex}.{ftype.lower()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
with working_path.open("w") as outfile:
|
||||||
|
for source in sources:
|
||||||
|
try:
|
||||||
|
with (
|
||||||
|
config.build.bundle / ftype.lower() / f"{source}.{ftype.lower()}"
|
||||||
|
).open("r") as infile:
|
||||||
|
outfile.write(infile.read())
|
||||||
|
outfile.write("\n\n")
|
||||||
|
except FileNotFoundError as err:
|
||||||
|
raise ValueError(
|
||||||
|
f"No {ftype.upper()} source file to bundle named '{source}'"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
bundle_hash = _hash_from_file(working_path)
|
||||||
|
slug = f"{dest}-{bundle_hash.hexdigest()[:8]}"
|
||||||
|
final_path = config.build.generated / ftype.lower() / f"{slug}.{ftype.lower()}"
|
||||||
|
|
||||||
|
working_path.rename(final_path)
|
||||||
|
|
||||||
|
return slug
|
||||||
|
|
||||||
|
|
||||||
|
def _dev(
|
||||||
|
cwd: Path,
|
||||||
|
config: ConfigSerializer.Container,
|
||||||
|
posts: Sequence[PostSerializer.Container],
|
||||||
|
):
|
||||||
|
config.https = False
|
||||||
|
config.domain = "localhost:5000"
|
||||||
|
config.base_url = "/"
|
||||||
|
# server = http.server.HTTPServer(
|
||||||
|
# ("127.0.0.1", 5000),
|
||||||
|
# functools.partial(
|
||||||
|
# http.server.SimpleHTTPRequestHandler, directory=str(cwd / config.build.generated)
|
||||||
|
# ),
|
||||||
|
# )
|
||||||
|
_build(cwd, config, posts)
|
||||||
|
# print(f"Serving dev site at {config.url}, press Ctrl+C to exit", file=sys.stderr)
|
||||||
|
# try:
|
||||||
|
# server.serve_forever()
|
||||||
|
# except KeyboardInterrupt:
|
||||||
|
# print("Stopping...", file=sys.stderr)
|
||||||
|
# server.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def _build(
|
||||||
|
cwd: Path,
|
||||||
|
config: ConfigSerializer.Container,
|
||||||
|
posts: Sequence[PostSerializer.Container],
|
||||||
|
):
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Rebuilding static assets into {cwd / config.build.generated}", file=sys.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
env = jinja2.Environment(
|
||||||
|
loader=jinja2.FileSystemLoader(str(cwd / config.build.templates)),
|
||||||
|
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
output = cwd / config.build.generated
|
||||||
|
static = cwd / config.build.static
|
||||||
|
today = datetime.datetime.utcnow()
|
||||||
|
bundle_slug = uuid.uuid4().hex[:8]
|
||||||
|
|
||||||
|
index_css_bundle = _build_bundle(config, "css", "index", ["common", "home"])
|
||||||
|
index_js_bundle = _build_bundle(
|
||||||
|
config, "js", "index", ["random-background", "preloader"]
|
||||||
|
)
|
||||||
|
_write_template(
|
||||||
|
env,
|
||||||
|
"index.html.j2",
|
||||||
|
output / "index.html",
|
||||||
|
config=config,
|
||||||
|
today=today,
|
||||||
|
css_bundle=index_css_bundle,
|
||||||
|
js_bundle=index_js_bundle,
|
||||||
|
)
|
||||||
|
_write_template(
|
||||||
|
env, "sitemap.xml.j2", output / "sitemap.xml", config=config, today=today
|
||||||
|
)
|
||||||
|
_write_template(
|
||||||
|
env,
|
||||||
|
"robots.txt.j2",
|
||||||
|
output / "robots.txt",
|
||||||
|
config=config,
|
||||||
|
today=today,
|
||||||
|
disallowed=[item.name for item in static.iterdir() if item.is_dir()],
|
||||||
|
)
|
||||||
|
|
||||||
|
static = cwd / config.build.static
|
||||||
|
if static.exists():
|
||||||
|
for item in static.iterdir():
|
||||||
|
_copy_resource(item, output)
|
||||||
|
|
||||||
|
explore_css_bundle = _build_bundle(config, "css", "explore", ["common", "explore"])
|
||||||
|
explore_js_bundle = _build_bundle(
|
||||||
|
config,
|
||||||
|
"js",
|
||||||
|
"explore",
|
||||||
|
["random-background", "preloader", "toggle-article-text-button"],
|
||||||
|
)
|
||||||
|
_write_template(
|
||||||
|
env,
|
||||||
|
"explore.html.j2",
|
||||||
|
output / config.build.post_base / "index.html",
|
||||||
|
config=config,
|
||||||
|
today=today,
|
||||||
|
posts=posts,
|
||||||
|
css_bundle=explore_css_bundle,
|
||||||
|
js_bundle=explore_js_bundle,
|
||||||
|
)
|
||||||
|
|
||||||
|
post_css_bundle = _build_bundle(config, "css", "post", ["common"])
|
||||||
|
post_js_bundle = _build_bundle(config, "js", "post", ["preloader"])
|
||||||
|
for post in posts:
|
||||||
|
_write_template(
|
||||||
|
env,
|
||||||
|
"post.html.j2",
|
||||||
|
output / config.build.post_base / post.slug / "index.html",
|
||||||
|
config=config,
|
||||||
|
today=today,
|
||||||
|
post=post,
|
||||||
|
css_bundle=post_css_bundle,
|
||||||
|
js_bundle=post_js_bundle,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = get_args()
|
args = get_args()
|
||||||
|
|
||||||
cwd = Path.cwd().resolve()
|
cwd = Path.cwd().resolve()
|
||||||
output = cwd / "export"
|
|
||||||
explore = output / "explore"
|
|
||||||
|
|
||||||
with Path(args.config).resolve().open() as infile:
|
with Path(args.config).resolve().open(encoding="utf-8") as infile:
|
||||||
config = ConfigSerializer().load(yaml.load(infile))
|
config = ConfigSerializer().load(yaml.load(infile))
|
||||||
|
|
||||||
|
posts = []
|
||||||
|
post_serializer = PostSerializer()
|
||||||
|
for item in (cwd / config.build.posts).iterdir():
|
||||||
|
if item.suffix.lower() == ".yaml":
|
||||||
|
with item.open() as infile:
|
||||||
|
raw = yaml.load(infile)
|
||||||
|
raw["slug"] = raw.get("slug", item.stem)
|
||||||
|
posts.append(post_serializer.load(raw))
|
||||||
|
|
||||||
|
slugs = [post.slug for post in posts]
|
||||||
|
if len(set(slugs)) != len(slugs):
|
||||||
|
raise msh.ValidationError("Duplicate post slugs found in config")
|
||||||
|
|
||||||
if args.check:
|
if args.check:
|
||||||
|
print("Config check successful!", file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
env = jinja2.Environment(
|
posts = sorted(posts, key=lambda item: item.date, reverse=True)
|
||||||
loader=jinja2.FileSystemLoader(str(cwd / "templates")),
|
|
||||||
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
|
||||||
)
|
|
||||||
|
|
||||||
output.mkdir(exist_ok=True)
|
if args.dev:
|
||||||
explore.mkdir(exist_ok=True)
|
_dev(cwd, config, posts)
|
||||||
|
else:
|
||||||
|
_build(cwd, config, posts)
|
||||||
|
|
||||||
index = env.get_template("index.html.j2").render(config=config)
|
return 0
|
||||||
with (explore / "index.html").open("w") as outfile:
|
|
||||||
outfile.write(index)
|
|
||||||
|
|
||||||
sitemap = env.get_template("sitemap.xml.j2").render(config=config)
|
|
||||||
with (output / "sitemap.xml").open("w") as outfile:
|
|
||||||
outfile.write(sitemap)
|
|
||||||
|
|
||||||
for static in config["static"]:
|
|
||||||
dest = Path(output / static).resolve()
|
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
shutil.copyfile(static, str(output / static), follow_symlinks=True)
|
|
||||||
|
|
||||||
post_template = env.get_template("post.html.j2")
|
|
||||||
for post_data in config["posts"]:
|
|
||||||
post = post_template.render(post=post_data)
|
|
||||||
with (explore / f"{post_data.slug}.html").open("w") as outfile:
|
|
||||||
outfile.write(post)
|
|
||||||
|
|
||||||
nginx = env.get_template("nginx.conf.d.j2").render(config=config)
|
|
||||||
with (cwd / "nginx.conf").open("w") as outfile:
|
|
||||||
outfile.write(nginx)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -5,19 +5,13 @@ html {
|
|||||||
font-family: Verdana, Helvetica, sans-serif;
|
font-family: Verdana, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
body {
|
||||||
color: inherit;
|
color: white;
|
||||||
text-decoration: none;
|
font-family: sans-serif;
|
||||||
transition: all 0.1s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 5px 5px 10px #fff, -5px -5px 10px #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#background-image {
|
#background-image {
|
||||||
background-image: url("https://cdn.enp.one/img/backgrounds/cl-photo-allis.jpg");
|
background-image: url("https://cdn.enp.one/img/backgrounds/cl-photo-rt112.jpg");
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
@ -38,136 +32,13 @@ a:hover {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#background-image .overlay {
|
||||||
text-align: center;
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
text-shadow: 3px 3px 5px #000, -3px -3px 5px #000;
|
width: 100%;
|
||||||
font-weight: bold;
|
height: 100%;
|
||||||
color: white;
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
width: 40em;
|
|
||||||
max-width: 90%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 2px;
|
|
||||||
border-color: rgba(0, 0, 0, 0);
|
|
||||||
border-radius: 128px;
|
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 15%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo {
|
|
||||||
margin: auto;
|
|
||||||
margin-top: -5em;
|
|
||||||
max-width: 60%;
|
|
||||||
width: 50%;
|
|
||||||
display: block;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
border-width: 5px;
|
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4), 0 6px 20px 0 rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content p {
|
|
||||||
margin: 2em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.buttons {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
font-size: 1.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.buttons li {
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0.5em;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.nav {
|
|
||||||
padding-top: 0.75em;
|
|
||||||
padding-bottom: 0.55em;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
padding-right: 1.5em;
|
|
||||||
|
|
||||||
border-radius: 30px;
|
|
||||||
|
|
||||||
transition: all 0.25s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
text-shadow:
|
|
||||||
-3px -3px 5px #fff,
|
|
||||||
-3px 3px 5px #fff,
|
|
||||||
3px -3px 5px #fff,
|
|
||||||
3px 3px 5px #fff,
|
|
||||||
0px 0px 7px #ff0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.nav:hover {
|
|
||||||
text-shadow:
|
|
||||||
0px 0px 7px #000,
|
|
||||||
-5px -5px 10px #fff,
|
|
||||||
-5px 5px 10px #fff,
|
|
||||||
5px -5px 10px #fff,
|
|
||||||
5px 5px 10px #fff;
|
|
||||||
|
|
||||||
-webkit-animation-name: pulse;
|
|
||||||
-webkit-animation-duration: 5s;
|
|
||||||
-webkit-animation-timing-function: linear;
|
|
||||||
-webkit-animation-iteration-count: infinite;
|
|
||||||
-webkit-animation-fill-mode: none;
|
|
||||||
animation-name: pulse;
|
|
||||||
animation-duration: 5s;
|
|
||||||
animation-timing-function: linear;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
animation-fill-mode: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explore:hover { color: #5588e0; }
|
|
||||||
|
|
||||||
.youtube:hover { color: #ff0000; }
|
|
||||||
|
|
||||||
.instagram:hover { color: #c13584; }
|
|
||||||
|
|
||||||
.twitter:hover { color: #1da1f2; }
|
|
||||||
|
|
||||||
#background-info {
|
|
||||||
text-align: right;
|
|
||||||
font-size: 0.85em;
|
|
||||||
|
|
||||||
padding: 0.75em;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
top: 0;
|
||||||
right: 0;
|
left: 0;
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer { font-size: 0.9em; }
|
|
||||||
|
|
||||||
footer div { margin-bottom: 0.5em; }
|
|
||||||
|
|
||||||
footer a.button i {
|
|
||||||
padding: 0.5em;
|
|
||||||
font-size: 1.25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeout {
|
.fadeout {
|
||||||
@ -227,49 +98,6 @@ footer a.button i {
|
|||||||
100% {opacity: 0;}
|
100% {opacity: 0;}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0% {
|
|
||||||
box-shadow:
|
|
||||||
0px 0px 15px 3px #fff,
|
|
||||||
0px 0px 15px 3px #88a9fc;
|
|
||||||
}
|
|
||||||
10% {
|
|
||||||
box-shadow:
|
|
||||||
-10px -10px 15px 3px #fff,
|
|
||||||
10px 10px 15px 3px #88a9fc;
|
|
||||||
}
|
|
||||||
30% {
|
|
||||||
box-shadow:
|
|
||||||
-10px 10px 15px 3px #b5f7fc,
|
|
||||||
10px -10px 15px 3px #fcaa99;
|
|
||||||
}
|
|
||||||
45% {
|
|
||||||
box-shadow:
|
|
||||||
10px 10px 15px 3px #ecf9a7,
|
|
||||||
-10px -10px 15px 3px #fcaa99;
|
|
||||||
}
|
|
||||||
60% {
|
|
||||||
box-shadow:
|
|
||||||
10px -10px 15px 3px #ecf9a7,
|
|
||||||
-10px 10px 15px 3px #abfcad;
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
box-shadow:
|
|
||||||
-10px -10px 15px 3px #b5f7fc,
|
|
||||||
10px 10px 15px 3px #abfcad;
|
|
||||||
}
|
|
||||||
90% {
|
|
||||||
box-shadow:
|
|
||||||
-10px 10px 15px 3px #fff,
|
|
||||||
10px -10px 15px 3px #88a9fc;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow:
|
|
||||||
0px 0px 15px 3px #b5f7fc,
|
|
||||||
0px 0px 15px 3px #88a9fc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes spinner {
|
@-webkit-keyframes spinner {
|
||||||
0% { transform: translate(-50%,-50%) rotate(0deg); }
|
0% { transform: translate(-50%,-50%) rotate(0deg); }
|
||||||
100% { transform: translate(-50%,-50%) rotate(360deg); }
|
100% { transform: translate(-50%,-50%) rotate(360deg); }
|
||||||
@ -280,25 +108,67 @@ footer a.button i {
|
|||||||
100% { transform: translate(-50%,-50%) rotate(360deg); }
|
100% { transform: translate(-50%,-50%) rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
a {
|
||||||
#content {
|
color: inherit;
|
||||||
padding: 0;
|
text-decoration: none;
|
||||||
padding-bottom: 1em;
|
transition: all 0.1s ease-in-out;
|
||||||
border-radius: 32px;
|
|
||||||
top: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content p {
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.buttons {
|
|
||||||
margin-top: 1.5em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.buttons li {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 5px 5px 10px #fff, -5px -5px 10px #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.buttons {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-size: 1.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.buttons li {
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
text-shadow:
|
||||||
|
-3px -3px 5px #fff,
|
||||||
|
-3px 3px 5px #fff,
|
||||||
|
3px -3px 5px #fff,
|
||||||
|
3px 3px 5px #fff,
|
||||||
|
0px 0px 7px #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.nav:hover {
|
||||||
|
text-shadow:
|
||||||
|
0px 0px 7px #000,
|
||||||
|
-5px -5px 10px #fff,
|
||||||
|
-5px 5px 10px #fff,
|
||||||
|
5px -5px 10px #fff,
|
||||||
|
5px 5px 10px #fff;
|
||||||
|
|
||||||
|
-webkit-animation-name: pulse;
|
||||||
|
-webkit-animation-duration: 5s;
|
||||||
|
-webkit-animation-timing-function: linear;
|
||||||
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
-webkit-animation-fill-mode: none;
|
||||||
|
animation-name: pulse;
|
||||||
|
animation-duration: 5s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-fill-mode: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore:hover { color: #5588e0; }
|
||||||
|
|
||||||
|
.youtube:hover { color: #ff0000; }
|
||||||
|
|
||||||
|
.instagram:hover { color: #c13584; }
|
||||||
|
|
||||||
|
.twitter:hover { color: #1da1f2; }
|
@ -1,53 +1,8 @@
|
|||||||
html {
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: white;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#background-image {
|
|
||||||
background-image: url("https://cdn.enp.one/img/backgrounds/cl-photo-rt112.jpg");
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
-webkit-background-size: cover;
|
|
||||||
-moz-background-size: cover;
|
|
||||||
-o-background-size: cover;
|
|
||||||
|
|
||||||
filter: blur(6px);
|
|
||||||
-webkit-filter: blur(6px);
|
|
||||||
|
|
||||||
position: fixed;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#background-image .overlay {
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toggle-description {
|
#toggle-description {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -74,17 +29,26 @@ ul {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header h1 {
|
#header {
|
||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
border-bottom-style: solid;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 2em;
|
|
||||||
margin-top: 1em;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
width: 75%;
|
|
||||||
text-shadow: 3px 3px 5px #000;
|
text-shadow: 3px 3px 5px #000;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h1 {
|
||||||
|
border-bottom-style: solid;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header p {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header span {
|
#header span {
|
||||||
@ -170,3 +134,27 @@ ul {
|
|||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-right: 0.7em;
|
margin-right: 0.7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
h1 { font-size: 1.5rem; }
|
||||||
|
|
||||||
|
h2 { font-size: 1.25rem; }
|
||||||
|
|
||||||
|
p { font-size: 0.9rem; }
|
||||||
|
|
||||||
|
#toggle-description { font-size: 1.25rem; }
|
||||||
|
|
||||||
|
.article {
|
||||||
|
border-radius: 3em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-banner {
|
||||||
|
border-radius: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-right: 2em;
|
||||||
|
}
|
||||||
|
}
|
143
bundle/css/home.css
Normal file
143
bundle/css/home.css
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#content {
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 3px 3px 5px #000, -3px -3px 5px #000;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
width: 40em;
|
||||||
|
max-width: 90%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: rgba(0, 0, 0, 0);
|
||||||
|
border-radius: 128px;
|
||||||
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
margin: auto;
|
||||||
|
margin-top: -5em;
|
||||||
|
max-width: 60%;
|
||||||
|
width: 50%;
|
||||||
|
display: block;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-width: 5px;
|
||||||
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4), 0 6px 20px 0 rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-variant: small-caps;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content p {
|
||||||
|
margin: 2em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.nav {
|
||||||
|
padding-top: 0.75em;
|
||||||
|
padding-bottom: 0.55em;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
padding-right: 1.5em;
|
||||||
|
|
||||||
|
border-radius: 30px;
|
||||||
|
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#background-info {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.85em;
|
||||||
|
|
||||||
|
padding: 0.75em;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer { font-size: 0.9em; }
|
||||||
|
|
||||||
|
footer div { margin-bottom: 0.5em; }
|
||||||
|
|
||||||
|
footer a.button i {
|
||||||
|
padding: 0.5em;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 15px 3px #fff,
|
||||||
|
0px 0px 15px 3px #88a9fc;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
box-shadow:
|
||||||
|
-10px -10px 15px 3px #fff,
|
||||||
|
10px 10px 15px 3px #88a9fc;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
box-shadow:
|
||||||
|
-10px 10px 15px 3px #b5f7fc,
|
||||||
|
10px -10px 15px 3px #fcaa99;
|
||||||
|
}
|
||||||
|
45% {
|
||||||
|
box-shadow:
|
||||||
|
10px 10px 15px 3px #ecf9a7,
|
||||||
|
-10px -10px 15px 3px #fcaa99;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
box-shadow:
|
||||||
|
10px -10px 15px 3px #ecf9a7,
|
||||||
|
-10px 10px 15px 3px #abfcad;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
box-shadow:
|
||||||
|
-10px -10px 15px 3px #b5f7fc,
|
||||||
|
10px 10px 15px 3px #abfcad;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
box-shadow:
|
||||||
|
-10px 10px 15px 3px #fff,
|
||||||
|
10px -10px 15px 3px #88a9fc;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
0px 0px 15px 3px #b5f7fc,
|
||||||
|
0px 0px 15px 3px #88a9fc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
#content {
|
||||||
|
padding: 0;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
border-radius: 32px;
|
||||||
|
top: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content p {
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.buttons {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.buttons li {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
8
bundle/js/preloader.js
Normal file
8
bundle/js/preloader.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
window.addEventListener("load", async function() {
|
||||||
|
document.getElementById("preloader").classList.add("fadeout");
|
||||||
|
// I don't actually know how promises or async works
|
||||||
|
// ¯\_(ツ)_/¯
|
||||||
|
// https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep
|
||||||
|
await new Promise(r => setTimeout(r, 250))
|
||||||
|
document.getElementById("preloader").style.display = "none";
|
||||||
|
});
|
@ -66,7 +66,6 @@ const BACKGROUND_IMAGES = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
function selectBackground() {
|
function selectBackground() {
|
||||||
let max = BACKGROUND_IMAGES.length - 1
|
let max = BACKGROUND_IMAGES.length - 1
|
||||||
let min = 0;
|
let min = 0;
|
||||||
@ -75,42 +74,10 @@ function selectBackground() {
|
|||||||
return BACKGROUND_IMAGES[index];
|
return BACKGROUND_IMAGES[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", function() {
|
||||||
function togglePrimaryText() {
|
|
||||||
let items = document.getElementsByClassName("article");
|
|
||||||
|
|
||||||
for (index = 0; index < items.length; index++) {
|
|
||||||
if (items[index].classList.contains("primary-text")) {
|
|
||||||
items[index].classList.remove("primary-text");
|
|
||||||
} else {
|
|
||||||
items[index].classList.add("primary-text");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let button = document.getElementById("toggle-description");
|
|
||||||
|
|
||||||
if (button.classList.contains("active")) {
|
|
||||||
button.classList.remove("active");
|
|
||||||
} else {
|
|
||||||
button.classList.add("active");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", function () {
|
|
||||||
let selected = selectBackground()
|
let selected = selectBackground()
|
||||||
|
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
"background-image"
|
"background-image"
|
||||||
).style.backgroundImage = "url(" + selected.url + ")";
|
).style.backgroundImage = "url(" + selected.url + ")";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener("load", async function () {
|
|
||||||
document.getElementById("toggle-description").addEventListener("click", togglePrimaryText);
|
|
||||||
document.getElementById("preloader").classList.add("fadeout");
|
|
||||||
// I don't actually know how promises or async works
|
|
||||||
// ¯\_(ツ)_/¯
|
|
||||||
// https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep
|
|
||||||
await new Promise(r => setTimeout(r, 250))
|
|
||||||
document.getElementById("preloader").style.display = "none";
|
|
||||||
});
|
|
23
bundle/js/toggle-article-text-button.js
Normal file
23
bundle/js/toggle-article-text-button.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
function togglePrimaryText() {
|
||||||
|
let items = document.getElementsByClassName("article");
|
||||||
|
|
||||||
|
for (index = 0; index < items.length; index++) {
|
||||||
|
if (items[index].classList.contains("primary-text")) {
|
||||||
|
items[index].classList.remove("primary-text");
|
||||||
|
} else {
|
||||||
|
items[index].classList.add("primary-text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = document.getElementById("toggle-description");
|
||||||
|
|
||||||
|
if (button.classList.contains("active")) {
|
||||||
|
button.classList.remove("active");
|
||||||
|
} else {
|
||||||
|
button.classList.add("active");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("load", async function() {
|
||||||
|
document.getElementById("toggle-description").addEventListener("click", togglePrimaryText);
|
||||||
|
});
|
40
templates/explore.html.j2
Normal file
40
templates/explore.html.j2
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% from "macros.html.j2" import make_header %}{% from "macros.html.j2" import make_social_links %}<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ make_header(config, alttitle="Explore " + config.title, css_bundle=css_bundle, js_bundle=js_bundle) }}
|
||||||
|
<body>
|
||||||
|
<div id="background-image"><div class="overlay"></div></div>
|
||||||
|
|
||||||
|
<div id="preloader" class="nojs"><div class="spinner"><div></div></div></div>
|
||||||
|
|
||||||
|
<div id="toggle-description" class="nojs active"><i class="fas fa-paragraph"></i></div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="header">
|
||||||
|
<h1>
|
||||||
|
Explore {{ config.title }}
|
||||||
|
|
||||||
|
<span class="float-right">
|
||||||
|
{{ make_social_links(config) }}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for post in posts %}
|
||||||
|
<li class="article primary-text{{ ' hidden' if loop.index > 10 else '' }}">
|
||||||
|
<div class="article-banner" style="background-image: url('{{ post.banner_url(config) }}');">
|
||||||
|
<a href="{{ post.slug }}/" class="article-content">
|
||||||
|
<h2>{{ post.title }}</h2>
|
||||||
|
<p>
|
||||||
|
<i class="fas fa-map-marker-alt"></i>{{ post.location.title }}
|
||||||
|
<i class="far fa-calendar-alt"></i>{{ post.date }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,100 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
{% from "macros.html.j2" import make_header %}{% from "macros.html.j2" import make_social_links %}<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
{{ make_header(config, css_bundle=css_bundle, js_bundle=js_bundle) }}
|
||||||
<!-- Web crawler and search indexing meta -->
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<meta name="author" content="admin@allaroundhere.org"/>
|
|
||||||
<meta name="description" content="Some of the best places are all around here"/>
|
|
||||||
<meta name="robots" content="index follow"/>
|
|
||||||
<meta
|
|
||||||
name="keywords"
|
|
||||||
content="travel photography explore exploration urbex urban nature all around here local museum history historical society"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Facebook integration meta -->
|
|
||||||
<meta property="og:title" content="All Around Here"/>
|
|
||||||
<meta property="og:url" content="https://allaroundhere.org/explore/"/>
|
|
||||||
<meta property='og:site_name' content="All Around Here"/>
|
|
||||||
<meta property="og:type" content="website"/>
|
|
||||||
<meta property='og:locale' content="en_US"/>
|
|
||||||
<meta property="og:image" content="https://cdn.enp.one/img/backgrounds/cl-photo-boston.jpg"/>
|
|
||||||
<meta property='og:description' content="Some of the best places are all around here"/>
|
|
||||||
|
|
||||||
<!-- Twitter integration meta -->
|
|
||||||
<meta name="twitter:card" content="summary">
|
|
||||||
<meta name="twitter:site" content="https://allaroundhere.org/explore/">
|
|
||||||
<meta name="twitter:title" content="All Around Here">
|
|
||||||
<meta name="twitter:description" content="Some of the best places are all around here">
|
|
||||||
<meta name="twitter:image" content="https://cdn.enp.one/img/backgrounds/cl-photo-boston.jpg">
|
|
||||||
<meta name="twitter:image:alt" content="All Around Here">
|
|
||||||
|
|
||||||
<title>Explore All Around Here</title>
|
|
||||||
<link rel="shortcut icon" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.enp.one/img/logos/aah-b-sm.png" >
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="../css/explore.css"/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
|
||||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/custom.js"></script>
|
|
||||||
|
|
||||||
<noscript><style>.nojs { display: none; }</style></noscript>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="background-image"><div class="overlay"></div></div>
|
<div id="background-image"></div>
|
||||||
|
|
||||||
<div id="preloader" class="nojs"><div class="spinner"><div></div></div></div>
|
<div id="preloader" class="nojs"><div class="spinner"><div></div></div></div>
|
||||||
|
|
||||||
<div id="toggle-description" class="nojs"><i class="fas fa-paragraph"></i></div>
|
|
||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="header">
|
<img
|
||||||
<h1>
|
id="logo"
|
||||||
Explore All Around Here
|
alt="Road to the great wide nowhere"
|
||||||
|
src="https://cdn.enp.one/img/logos/aah-md.jpg"
|
||||||
|
/>
|
||||||
|
|
||||||
<span class="float-right">
|
<h1>{{ config.title }}</h1>
|
||||||
<a
|
|
||||||
class="button instagram"
|
|
||||||
title="Follow All Around Here on instagram @allaroundhere"
|
|
||||||
href="https://www.instagram.com/allaroundhere/"
|
|
||||||
>
|
|
||||||
<i class="fab fa-instagram"></i>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="button twitter"
|
|
||||||
title="Follow me on twitter @enpaul_"
|
|
||||||
href="https://www.twitter.com/enpaul_/"
|
|
||||||
>
|
|
||||||
<i class="fab fa-twitter"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for post in config.posts %}
|
|
||||||
<li class="article">
|
|
||||||
<div class="article-banner" style="background-image: url('{{ post.banner }}');">
|
|
||||||
<a href="{{ post.slug }}/" class="article-content">
|
|
||||||
<h2>{{ post.title }}</h2>
|
|
||||||
<p>
|
<p>
|
||||||
<i class="fas fa-map-marker-alt"></i>{{ post.location.title }}
|
This is a project of mine where I turn my random travels, undirected wanderings, and
|
||||||
<i class="far fa-calendar-alt"></i>{{ post.date }}
|
unexpected discoveries into something other people can enjoy along with me. There are a
|
||||||
|
lot of cool things in the world and I like to find them, wherever I happen to be. If you're
|
||||||
|
interested in seeing some of these arbitrary oddities then check out the links below.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<ul class="buttons">
|
||||||
|
<li>
|
||||||
|
<a class="button nav" title="Explore All Around Here" href="{{ config.baseurl }}{{ config.build.post_base }}">
|
||||||
|
<i class="fas fa-binoculars"></i> Explore
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div>
|
||||||
|
{{ make_social_links(config) }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a title="Personal website" href="https://enpaul.net/">©2022-{{ today.year }} enpaul</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
62
templates/macros.html.j2
Normal file
62
templates/macros.html.j2
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{% macro make_header(config, alttitle=none, css_bundle=none, js_bundle=none) %}
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||||
|
<meta content="utf-8" http-equiv="encoding">
|
||||||
|
|
||||||
|
<!-- Web crawler and search indexing meta -->
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<meta name="author" content="{{ config.email }}"/>
|
||||||
|
<meta name="description" content="{{ config.description }}"/>
|
||||||
|
<meta name="robots" content="index follow"/>
|
||||||
|
<meta name="keywords" content="{{ config.keywords | join(' ') }}"/>
|
||||||
|
|
||||||
|
<!-- Facebook integration meta -->
|
||||||
|
<meta property="og:title" content="{{ alttitle or config.title }}"/>
|
||||||
|
<meta property="og:url" content="{{ config.url }}{{ config.build.post_base }}"/>
|
||||||
|
<meta property='og:site_name' content="{{ config.title }}"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property='og:locale' content="en_US"/>
|
||||||
|
<meta property="og:image" content="https://cdn.enp.one/img/backgrounds/cl-photo-boston.jpg"/>
|
||||||
|
<meta property='og:description' content="{{ config.description }}"/>
|
||||||
|
|
||||||
|
<!-- Twitter integration meta -->
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:site" content="{{ config.url }}{{ config.build.post_base }}">
|
||||||
|
<meta name="twitter:title" content="{{ alttitle or config.title }}">
|
||||||
|
<meta name="twitter:description" content="{{ config.description }}">
|
||||||
|
<meta name="twitter:image" content="https://cdn.enp.one/img/backgrounds/cl-photo-boston.jpg">
|
||||||
|
<meta name="twitter:image:alt" content="{{ config.title }}">
|
||||||
|
|
||||||
|
<title>{{ alttitle or config.title }}</title>
|
||||||
|
<link rel="shortcut icon" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.enp.one/img/logos/aah-b-sm.png" >
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="https://cdn.enp.one/img/logos/aah-b-sm.png">
|
||||||
|
|
||||||
|
{% if css_bundle %}<link rel="stylesheet" href="{{ config.baseurl }}css/{{ css_bundle }}.css"/>{% endif %}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||||
|
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{% if js_bundle %}<script type="text/javascript" src="{{ config.baseurl }}js/{{ js_bundle }}.js"></script>{% endif %}
|
||||||
|
|
||||||
|
<noscript><style>.nojs { display: none; }</style></noscript>
|
||||||
|
</head>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro make_social_links(config) %}
|
||||||
|
{% for social, link in config.social.items() %}
|
||||||
|
<a
|
||||||
|
class="button {{ social }}"
|
||||||
|
title="Follow me on {{ social }} @{{ link.rstrip('/').rpartition('/')[-1] }}"
|
||||||
|
href="{{ link }}"
|
||||||
|
>
|
||||||
|
<i class="fab fa-{{ social }}"></i>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endmacro %}
|
@ -0,0 +1,43 @@
|
|||||||
|
{% from "macros.html.j2" import make_header %}{% from "macros.html.j2" import make_social_links %}<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ make_header(config, alttitle=post.title, css_bundle=css_bundle, js_bundle=js_bundle) }}
|
||||||
|
<body>
|
||||||
|
<div id="preloader" class="nojs"><div class="spinner"><div></div></div></div>
|
||||||
|
{% for media in post.media %}
|
||||||
|
<div class="post-media" style="background-image: url('{{ media.preload_url(config) }}')"><div class="overlay"></div></div>
|
||||||
|
{% endfor %}
|
||||||
|
<div id="download-source" class="nojs"><i class="fas fa-cloud-download-alt"></i></div>
|
||||||
|
<div id="link" class="nojs"><i class="fas fa-link"></i></div>
|
||||||
|
<div id="media-previous" class="nojs"><i class="fas fa-chevron-left"></i></div>
|
||||||
|
<div id="media-next" class="nojs"><i class="fas fa-chevron-right"></i></div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<div id="header">
|
||||||
|
<h1>
|
||||||
|
Explore {{ config.title }}
|
||||||
|
|
||||||
|
<span class="float-right">
|
||||||
|
{{ make_social_links(config) }}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for post in posts %}
|
||||||
|
<li class="article primary-text">
|
||||||
|
<div class="article-banner" style="background-image: url('{{ post.banner_url(config) }}');">
|
||||||
|
<a href="{{ post.slug }}/" class="article-content">
|
||||||
|
<h2>{{ post.title }}</h2>
|
||||||
|
<p>
|
||||||
|
<i class="fas fa-map-marker-alt"></i>{{ post.location.title }}
|
||||||
|
<i class="far fa-calendar-alt"></i>{{ post.date }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
7
templates/robots.txt.j2
Normal file
7
templates/robots.txt.j2
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Allow all bots
|
||||||
|
User-agent: *
|
||||||
|
|
||||||
|
# Disallow access to non-content directories{% for path in disallowed %}
|
||||||
|
Disallow: {{ path }}{% endfor %}
|
||||||
|
|
||||||
|
Sitemap: {{ config.url }}sitemap.xml
|
@ -7,21 +7,22 @@
|
|||||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
|
|
||||||
<url>
|
<url>
|
||||||
<loc>https://allaroundhere.org/</loc>
|
<loc>{{ config.url }}</loc>
|
||||||
<lastmod>2021-02-01T00:30:55+00:00</lastmod>
|
<lastmod>{{ today.strftime('%Y-%m-%dT%H:%M:%S') }}+00:00</lastmod>
|
||||||
<priority>1.00</priority>
|
<priority>0.90</priority>
|
||||||
</url>
|
</url>
|
||||||
|
|
||||||
<url>
|
<url>
|
||||||
<loc>https://allaroundhere.org/explore/</loc>
|
<loc>{{ config.url }}{{ config.build.post_base }}</loc>
|
||||||
<lastmod>2021-02-01T00:30:55+00:00</lastmod>
|
<lastmod>{{ today.strftime('%Y-%m-%dT%H:%M:%S') }}+00:00</lastmod>
|
||||||
<priority>1.10</priority>
|
<priority>1.00</priority>
|
||||||
</url>
|
</url>
|
||||||
{% for post in config.posts %}
|
|
||||||
|
{% for post in config.posts %}
|
||||||
<url>
|
<url>
|
||||||
<loc>https://allaroundhere.org/explore/{{ post.slug }}/</loc>
|
<loc>{{ config.url }}{{ config.build.post_base }}{{ post.slug }}</loc>
|
||||||
<lastmod>2021-02-01T00:30:55+00:00</lastmod>
|
<lastmod>{{ today.strftime('%Y-%m-%dT%H:%M:%S') }}+00:00</lastmod>
|
||||||
<priority>0.90</priority>
|
<priority>0.80</priority>
|
||||||
</url>
|
</url>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</urlset>
|
</urlset>
|
||||||
|
Loading…
Reference in New Issue
Block a user