Compare commits

...

119 Commits

Author SHA1 Message Date
0416201742 [main] Imagesizes 150x150
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 45s
2024-12-29 18:47:43 +01:00
10de61cb25 [main] Imagesizes in slider
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 53s
2024-12-29 18:12:24 +01:00
e5960232f1 [main] requirements
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 46s
2024-12-29 10:59:18 +01:00
6c24ac7ec2 [main] requirements
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 1m1s
2024-12-29 04:34:54 +01:00
0914787be6 [main] Playing with images
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-29 04:34:25 +01:00
0121662530 Lets make the frontpage in markdown too
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-29 04:34:15 +01:00
95b6c1fa05 Lets make the frontpage in markdown too
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 40s
2024-12-24 01:41:46 +01:00
668a1ae7a3 Lets make the frontpage in markdown too 2024-12-24 01:41:29 +01:00
71a89b7b10 Lets make the frontpage in markdown too
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-24 01:38:41 +01:00
1ff4ae2b24 Lets make the frontpage in markdown too
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
2024-12-24 01:34:48 +01:00
677867dbdd [main] Playing with images
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-24 01:12:20 +01:00
4059d6d7be Lets make the frontpage in markdown too
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-24 00:50:24 +01:00
6d7f365069 [main] Playing with images
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 36s
2024-12-24 00:30:04 +01:00
8e7f86a78b [main] Playing with images
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-24 00:28:06 +01:00
a5b560e404 [main] Playing with images
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 9s
2024-12-24 00:26:41 +01:00
87512d2e6d [main] Playing with images
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 10s
2024-12-24 00:14:44 +01:00
87efffe1c1 Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-22 00:09:07 +01:00
42f765366d Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-22 00:04:56 +01:00
e392b43123 Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 35s
2024-12-21 23:56:46 +01:00
79eeb1b10c Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-21 23:47:48 +01:00
5b6f1d22b4 Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-21 23:41:56 +01:00
904ca07f09 Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 47s
2024-12-21 23:34:02 +01:00
bde441326b Lets see what lighthouse says
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-21 23:33:49 +01:00
3026972dee Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-21 22:13:04 +01:00
5dcdbd38fb Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 36s
2024-12-21 21:19:12 +01:00
e3ef48c788 Lets see what lighthouse says
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-21 21:19:01 +01:00
cf60aacb0f Lets see what lighthouse says
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 40s
2024-12-21 20:55:41 +01:00
b80c192eba Update templates/base_template.html
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 40s
2024-12-21 17:58:52 +01:00
79b087b99c lang
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-21 03:10:40 +01:00
fdad51dc6e lang
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-21 03:10:27 +01:00
cb6b674522 lang
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-21 03:04:14 +01:00
323adc9d28 [main] Playing with images
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 41s
2024-12-21 03:00:29 +01:00
d240c06364 lang
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-21 02:54:32 +01:00
71d338d6fc lang
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-21 02:51:27 +01:00
7c9dc1cd01 næste side fix
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 36s
2024-12-21 02:47:31 +01:00
162591f527 næste side fix
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-21 02:27:07 +01:00
4c33ada9b2 [main] Score fixes
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-21 02:16:29 +01:00
9a3039b011 næste side fix
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 43s
2024-12-21 02:02:35 +01:00
a1c2233358 næste side fix
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
2024-12-21 01:29:42 +01:00
494eb6c156 Meta tags
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 41s
2024-12-21 01:04:00 +01:00
c69dca47e1 Meta tags
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-21 00:55:55 +01:00
8dacced1b4 update gitignore
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-21 00:45:27 +01:00
306e45da5f update gitignore
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
2024-12-21 00:17:13 +01:00
a6bc0707fb update gitignore
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-21 00:16:44 +01:00
bd2fef9b6c Lidt CSS fixeri og ja BS4 til at fixe tags
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-21 00:15:58 +01:00
7d55191d91 cache fix og title
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-20 23:20:57 +01:00
6beef9fc96 cache fix og title
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
2024-12-20 23:20:49 +01:00
a549f3f7a6 cache fix og title
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 41s
2024-12-20 23:15:44 +01:00
0e82979a70 Middelware update and footer content
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 43s
2024-12-20 22:40:44 +01:00
c8ad950d98 Middelware update and footer content
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 42s
2024-12-20 22:39:00 +01:00
d58e5b0d12 Middelware update and footer content
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-20 22:35:39 +01:00
18181d7ce8 Better CSS
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-20 20:11:28 +01:00
Henrik Jess
74685601b2 Lets restore
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
2024-12-20 10:44:33 +01:00
Henrik Jess
240ee1afbf Lets restore
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 7s
2024-12-20 10:43:35 +01:00
Henrik Jess
abf69e0074 Lets restore
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 7s
2024-12-20 10:43:24 +01:00
Henrik Jess
e502ea69aa Blue Green
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 7s
2024-12-20 10:42:35 +01:00
Henrik Jess
0b9eb0b9f0 Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 6s
2024-12-20 10:40:06 +01:00
Henrik Jess
23a22fb4d6 Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 6s
2024-12-20 10:37:56 +01:00
Henrik Jess
1b047a74fd Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 7s
2024-12-20 10:35:54 +01:00
Henrik Jess
459f51808f Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 38s
2024-12-20 10:11:25 +01:00
Henrik Jess
0b66a49d07 Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 38s
2024-12-20 10:02:56 +01:00
Henrik Jess
bc4dc6b5fb Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 38s
2024-12-20 10:01:37 +01:00
Henrik Jess
7e0d7e6466 Blue Green
Some checks failed
Build, Push, and Blue/Green Deploy to Nomad / docker-nomad (push) Failing after 38s
2024-12-20 09:57:40 +01:00
Henrik Jess
8aeb6dbd24 requirements.txt
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 57s
2024-12-20 09:43:25 +01:00
Henrik Jess
7e5c31b00d Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 40s
2024-12-19 20:20:07 +01:00
Henrik Jess
af9c8f77fd Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 35s
2024-12-19 15:57:20 +01:00
Henrik Jess
47d0834c12 Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 39s
2024-12-19 15:41:41 +01:00
b78fb42ed7 Merge pull request 'Sync' (#13) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
Reviewed-on: #13
2024-12-19 15:37:47 +01:00
Henrik Jess
33ca681f10 Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-18 21:53:43 +01:00
Henrik Jess
f32172879c Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 30s
2024-12-18 21:50:04 +01:00
Henrik Jess
f80c529a46 Bug somewhere
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-18 21:39:19 +01:00
5d6b621a99 Better CSS
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 36s
2024-12-17 22:34:05 +01:00
8d9f714701 Better CSS
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-17 22:03:47 +01:00
b4c673bdc4 Better CSS 2024-12-17 21:28:10 +01:00
dea59f3d23 Sync
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 37s
2024-12-17 19:06:34 +01:00
Henrik Jess
7072e7e099 Bug somewhere
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 4m38s
2024-12-17 17:10:37 +01:00
Henrik Jess
1cb9e066ab Modals
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-17 14:40:26 +01:00
4e43c10b54 Sync
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 30s
2024-12-16 23:15:37 +01:00
87257a0bd4 Sync 2024-12-16 21:13:50 +01:00
Henrik Jess
4009d49ee6 https enforcement
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 32s
2024-12-16 17:04:15 +01:00
Henrik Jess
f13aa9ec7e https enforcement 2024-12-16 17:04:06 +01:00
ce74aa5113 Merge pull request 'mvc' (#12) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
Reviewed-on: #12
2024-12-13 23:18:39 +01:00
48ba5feff9 Lets rememeber those pictures 2024-12-13 23:12:00 +01:00
3416dea62e Lets go back 2024-12-13 23:11:30 +01:00
eadd90322e Lets go back 2024-12-13 22:35:06 +01:00
11e66000c9 Merge pull request 'Lets go back' (#11) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 32s
Reviewed-on: #11
2024-12-13 22:20:38 +01:00
7a6cf87e3e Lets go back 2024-12-13 22:19:08 +01:00
afd60d3d58 Merge pull request 'Trying something ...' (#10) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
Reviewed-on: #10
2024-12-13 22:16:16 +01:00
70df58a097 Trying something ... 2024-12-13 22:14:44 +01:00
8b57e2af8b Merge pull request 'Trying something ...' (#9) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
Reviewed-on: #9
2024-12-13 22:13:25 +01:00
87b10611e7 Trying something ... 2024-12-13 22:12:03 +01:00
2029db143f Merge pull request 'Trying something ...' (#8) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 8s
Reviewed-on: #8
2024-12-13 22:09:22 +01:00
f4570ca7cc Trying something ... 2024-12-13 22:07:43 +01:00
93c8a066cb Merge pull request 'Renaming + CSS' (#7) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 32s
Reviewed-on: #7
2024-12-13 22:01:49 +01:00
4efe32116f Renaming + CSS 2024-12-13 22:00:13 +01:00
a2c6b94da1 Merge pull request 'mvc' (#6) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 14s
Reviewed-on: #6
2024-12-13 21:55:54 +01:00
f18f82fb99 Renaming + CSS 2024-12-13 21:52:50 +01:00
5875cc785b Renaming 2024-12-13 20:39:13 +01:00
a259de8adb Renaming 2024-12-13 20:37:54 +01:00
fce888183d Renaming 2024-12-13 20:35:33 +01:00
04acb1b5d9 Renaming 2024-12-13 20:34:46 +01:00
0d0b1c57b3 Budget 2024-12-13 20:07:04 +01:00
61bbdf421f Budget 2024-12-13 19:14:16 +01:00
63372e8210 Merge pull request 'Loads and loads of data' (#5) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
Reviewed-on: #5
2024-12-12 23:32:37 +01:00
c751d0b072 Loads and loads of data 2024-12-12 23:30:19 +01:00
e2e8c8bf66 Merge pull request 'Clas based startup' (#4) from mvc into main
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
Reviewed-on: #4
2024-12-12 20:07:41 +01:00
46a1951586 Clas based startup 2024-12-12 20:05:47 +01:00
adfa478eca Merge pull request 'Lets test' (#3) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 4m30s
Reviewed-on: #3
2024-12-12 20:02:38 +01:00
146670d7c7 Lets test 2024-12-12 20:01:18 +01:00
7f7dd5139e Merge pull request 'mvc' (#2) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Has been cancelled
Reviewed-on: #2
2024-12-12 20:00:00 +01:00
5d2bce8d6e [mvc] requirements 2024-12-12 19:58:26 +01:00
c6ac4599f4 Lets test 2024-12-12 19:55:30 +01:00
ffa1ae346f Merge pull request 'mvc' (#1) from mvc into main
Some checks failed
Build, Push, and Deploy to Nomad / docker-nomad (push) Failing after 4m28s
Reviewed-on: #1
2024-12-11 23:59:51 +01:00
e4887345a5 Lets test 2024-12-11 23:56:30 +01:00
82228fdb27 Lets test 2024-12-11 23:56:15 +01:00
379f6a4273 Lets test
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 34s
2024-12-11 21:14:52 +01:00
b2f8cc3c81 Lets test
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 33s
2024-12-11 20:49:14 +01:00
a8a6f7d0b0 Lets test
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 28s
2024-12-11 20:44:54 +01:00
38e8f16622 Lets test
All checks were successful
Build, Push, and Deploy to Nomad / docker-nomad (push) Successful in 38s
2024-12-11 20:43:30 +01:00
203 changed files with 16727 additions and 402 deletions

View File

@@ -0,0 +1,61 @@
job "lifefaq-blue" {
region = "global"
datacenters = ["dc1"]
type = "service"
update {
stagger = "60s"
max_parallel = 1
progress_deadline = "6m"
}
group "lifefaq-blue-group" {
count = 1
network {
port "port-app" {
to = 9210 # Internal application port
}
}
# Register the service with Consul
service {
provider = "consul"
name = "lifefaq-blue"
port = "port-app"
# Traefik-specific tags for routing
tags = [
"PORT=${NOMAD_PORT_port-app}"
]
# Define a health check using TCP
check {
name = "tcp_check"
type = "tcp"
interval = "10s"
timeout = "2s"
}
}
task "lifefaq-blue-task" {
driver = "docker"
config {
image = "registry.i80.dk/gitea/lifefaq:latest"
ports = ["port-app"]
}
env {
APP_ENV = "production"
PORT = "${NOMAD_PORT_port-app}"
}
resources {
cpu = 250
memory = 80
}
}
}
}

1
.gitignore vendored
View File

@@ -3,4 +3,5 @@
.ídea .ídea
.idea/* .idea/*
.gitea/**/build*/ .gitea/**/build*/
data/**/index.html

View File

@@ -17,4 +17,4 @@ COPY . .
EXPOSE 9210 EXPOSE 9210
# Command to run the FastAPI application # Command to run the FastAPI application
CMD ["uvicorn", "app:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "9210", "--workers", "1"] CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "9210", "--workers", "1"]

Binary file not shown.

147
app.py
View File

@@ -1,144 +1,5 @@
from fastapi import FastAPI, Request import uvicorn
from fastapi.staticfiles import StaticFiles from app.main import app
from fastapi.templating import Jinja2Templates
import json
import os
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from contextlib import asynccontextmanager
from markdown_render import render_markdown_with_jinja
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
if __name__ == "__main__":
uvicorn.run("app.main:app", host="0.0.0.0", port=9210, reload=False)
# Context manager for app lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
print("App startup: Processing Markdown files...")
process_markdown_files("./data", "./data") # Process all Markdown files
print("Markdown processing complete!")
yield # Allow the app to start
print("App shutdown: Cleanup complete.")
app = FastAPI(lifespan=lifespan)
app.mount("/data", StaticFiles(directory="data"), name="data")
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
app.add_middleware( HTTPSRedirectMiddleware )
# Templates directory
templates = Jinja2Templates(directory="templates")
# Load JSON data
with open("mock_data.json") as file:
data = json.load(file)
@app.get("/test", response_class=HTMLResponse)
async def home_test():
# Load the Markdown content from a file
with open("templates/example.md", "r") as f:
markdown_content = f.read()
# Render Markdown first, then inject Jinja2 components
rendered_html = render_markdown_with_jinja(markdown_content)
# Wrap in a base HTML layout
html_template = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown + Jinja2</title>
<style>
.img-left-overlay img {{ width: 300px; }}
.box {{ border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }}
.note {{ background-color: #e7f3fe; border-left: 4px solid #2196F3; padding: 10px; }}
.warning {{ background-color: #fff3cd; border-left: 4px solid #ffeb3b; padding: 10px; }}
</style>
</head>
<body>
<div class="content">
{rendered_html}
</div>
</body>
</html>
"""
return HTMLResponse(content=html_template)
def process_markdown_files(input_dir: str, output_dir: str):
"""
Recursively process all Markdown files in the input directory,
render them to HTML, and save them in the output directory.
"""
for root, _, files in os.walk(input_dir):
for file in files:
if file.endswith(".md"):
input_file_path = os.path.join(root, file)
# Determine output file path (convert .md to .html)
relative_path = os.path.relpath(input_file_path, input_dir)
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
# Ensure the output directory exists
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
# Read Markdown content
with open(input_file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
# Render Markdown with Jinja2
print(f"Processing: {input_file_path} -> {output_file_path}")
rendered_html = render_markdown_with_jinja(markdown_content)
# Write the rendered HTML to the output file
with open(output_file_path, "w", encoding="utf-8") as html_file:
html_file.write(rendered_html)
# Index route
@app.get("/", response_class=HTMLResponse)
async def get_index(request: Request):
return templates.TemplateResponse(
"index.html",
{"request": request, "data": data, "page_title": "Forside", "author": "Henrik"}
)
@app.get("/sitemap", response_class=HTMLResponse)
async def sitemap():
"""Simple home page listing available HTML files."""
links = []
for root, _, files in os.walk("./data"):
for file in files:
if file.endswith(".html"):
relative_path = os.path.relpath(os.path.join(root, file), "./data")
link = f"<a href='/data/{relative_path}'>{relative_path}</a>"
links.append(link)
links_html = "<br>".join(links)
return HTMLResponse(content=f"<h1>Available Pages</h1>{links_html}")
# Category route
@app.get("/category/{category_name}", response_class=HTMLResponse)
async def get_category(request: Request, category_name: str):
# Find den korrekte kategori
category = next((cat for cat in data["categories"] if cat["path"] == category_name), None)
if category:
category_file = f"data/{category_name}/index.html"
if os.path.exists(category_file):
with open(category_file) as file:
category_content = file.read()
return templates.TemplateResponse(
"category.html",
{
"request": request,
"data": data,
"page_title": category["name"],
"author": category["author"],
"content": category_content
},
)
return HTMLResponse("Kategori ikke fundet", status_code=404)

0
app/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,117 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.responses import JSONResponse
import time
class CategoryController:
def __init__(self,data_file="generated_data.json"):
"""Initialize the controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data = self._load_data( data_file )
self._add_routes()
def _add_routes(self):
"""Add routes to the router."""
self.router.add_api_route("/", self.get_index, methods=["GET"], response_class=HTMLResponse)
self.router.add_api_route(
"/category/{category_name}",
self.get_category,
methods=["GET"],
response_class=HTMLResponse,
)
self.router.add_api_route(
"/categories", self.list_categories, methods = ["GET"], response_class = JSONResponse
)
def _load_data(self, data_file):
"""Load JSON data from a file."""
with open(data_file, "r", encoding="utf-8") as file:
return json.load(file)
async def get_index(self, request: Request):
"""
Handle requests for the index (home) page.
This function is executed every time the root route (index page) is accessed.
It renders the 'index.html' template and populates it with dynamic data, such as:
- 'page_title': A static title for the home page ("Forside").
- 'author': The author's name ("Henrik").
- 'data': General data accessible to the template.
Args:
request (Request): The HTTP request object.
Returns:
TemplateResponse: A rendered HTML page for the index (home) route.
"""
unix_time_now = int( time.time() )
with open(f"data/_frontpage/index.html", "r") as fp:
content = fp.read()
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"data": self.data,
"page_title": "Frontpage",
"author": "Henrik Jess",
"content": content,
"timestamp": unix_time_now
})
async def get_category(self, request: Request, category_name: str):
"""
Handle requests for specific category pages.
This function is executed every time a category route is accessed.
It dynamically retrieves and serves content for the requested category.
- Searches for the requested category in 'self.data["categories"]' based on the provided category name.
- Reads the 'index.html' file located under 'data/{category_name}/' if it exists.
- Returns the rendered 'category.html' template with the following dynamic data:
- 'page_title': The name of the category.
- 'author': The author of the category.
- 'content': The content of the 'index.html' file.
- 'timestamp': The current Unix time when the request is processed.
- Returns a 404 HTML response if the category is not found or the file does not exist.
Args:
request (Request): The HTTP request object.
category_name (str): The name of the category being accessed.
Returns:
TemplateResponse: A rendered HTML page with dynamic category content.
HTMLResponse: A 404 response if the category does not exist.
"""
category = next((cat for cat in self.data["categories"] if cat["path"] == category_name), None)
unix_time_now = int( time.time() )
if category:
category_file = f"data/{category_name}/index.html"
if os.path.exists(category_file):
with open(category_file) as file:
category_content = file.read()
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"data": self.data,
"page_title": category["name"],
"author": category["author"],
"content": category_content,
"timestamp": unix_time_now
},
)
return HTMLResponse("Kategori ikke fundet", status_code=404)
async def list_categories(self, request: Request):
"""Return a list of all categories with their name and path."""
categories = [
{ "name": category["name"], "path": category["path"] }
for category in self.data.get( "categories", [] )
]
return JSONResponse( content = categories )

View File

@@ -0,0 +1,64 @@
import os
import json
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, Response
from fastapi.templating import Jinja2Templates
class DynamicController:
def __init__(self, data_dir: str):
"""Initialize the dynamic controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.data_dir = data_dir
self.data = self._load_mock_data()
self._add_dynamic_routes()
def _load_mock_data(self):
"""Load mock data from a JSON file."""
with open("generated_data.json") as file:
return json.load(file)
def _add_dynamic_routes(self):
"""Scan data directory and create dynamic routes."""
for root, dirs, files in os.walk(self.data_dir):
for directory in dirs:
route_path = f"/{directory}" # Create route based on directory name
directory_path = os.path.join(root, directory)
# Register route dynamically
self.router.add_api_route(
route_path,
self._serve_dynamic_template(directory, directory_path),
methods=["GET"],
response_class=HTMLResponse,
)
def _serve_dynamic_template(self, route_name: str, directory_path: str):
"""Closure to serve templates for each route."""
async def route_handler(request: Request):
# Look for index.html or render fallback content
index_html = os.path.join(directory_path, "index.html")
if os.path.exists(index_html):
with open(index_html, "r", encoding="utf-8") as file:
content = file.read()
# Find the author for this route from preloaded data
for category in self.data.get("categories", []):
if category["path"] == route_name:
author_name = category["author"]
break
# Pass required data to the template
return self.templates.TemplateResponse(
"category.html",
{
"request": request,
"page_title": route_name.capitalize(),
"content": content,
"author": author_name,
"data": self.data, # Pass additional data if needed
},
)
# Fallback: Return a 404 if no content is found
return Response(f"No content found for {route_name}", status_code=404)
return route_handler

View File

@@ -0,0 +1,86 @@
import json
import random
from fastapi import APIRouter, Request, FastAPI
from fastapi.templating import Jinja2Templates
from app.controllers.category_controller import CategoryController
class RouteToWeb:
def __init__(self, app: FastAPI):
"""Initialize the controller."""
self.router = APIRouter()
self.templates = Jinja2Templates(directory="templates")
self.app = app
self.category_controller = CategoryController()
self._add_routes()
self._add_global_middleware()
def _add_routes(self):
"""Add routes to the router."""
@self.router.get("/route-list", tags=["system"])
async def route_list(request: Request):
"""Render route list with categories."""
routes = [
{"path": route.path, "name": route.name or "Unnamed"}
for route in self.app.routes
]
categories = request.state.categories
return self.templates.TemplateResponse(
"route_list.html",
{"request": request, "routes": routes, "categories": categories, "page_title": "Route og Kategori Liste"},
)
def _add_global_middleware(self):
"""Middleware to add categories and next category globally to all requests."""
@self.app.middleware( "http" )
async def add_categories_to_request(request: Request, call_next):
def generate_dynamic_description(category_name: str) -> str:
"""Generate a dynamic and engaging link text for a category."""
templates = [
"Dyk ned i kategorien {category} og bliv inspireret!",
"Opdag alt, hvad du behøver at vide i kategorien {category}.",
"Udforsk {category}-kategorien og find noget nyt og spændende.",
"Lad dig fordybe i kategorien {category} der er meget at se!",
"Find din næste læseoplevelse i {category}-kategorien.",
"Gå på opdagelse i kategorien {category} og bliv klogere.",
"Der venter spændende indhold i {category}-kategorien klik her!",
"Vil du vide mere? Hele kategorien {category} er kun ét klik væk.",
"Læs videre i kategorien {category} og få ny inspiration.",
"Fordyb dig i {category}-kategorien og opdag nyt indhold.",
"Spring ind i {category}-kategorien og gå på opdagelse!",
"Find masser af viden og gode læseoplevelser i {category}-kategorien.",
"Udforsk hele kategorien {category} og bliv beriget med ny viden.",
"Der er mere at læse i {category}-kategorien gå ikke glip af det!",
"Tag et dybere kig i kategorien {category} og bliv inspireret!"
]
template = random.choice( templates )
return template.format( category = category_name.lower() )
"""Inject categories and next category into request.state globally."""
# Hent kategorier direkte fra CategoryController
categories_response = await self.category_controller.list_categories( request )
categories_data = categories_response.body.decode()
categories = json.loads( categories_data )
# Tilføj kategorier til request.state
request.state.categories = categories
# Find den aktuelle og næste kategori
current_path = request.url.path.split("/")[-1]
next_category = None
for index, category in enumerate( categories ):
if category["path"] == current_path:
# Find næste kategori (cirkulær, hvis det er den sidste)
next_index = (index + 1) % len( categories )
next_category = categories[next_index]
next_category["description"] = generate_dynamic_description( next_category["path"] )
break
# Tilføj næste kategori til request.state
request.state.next_category = next_category
response = await call_next( request )
return response

72
app/main.py Normal file
View File

@@ -0,0 +1,72 @@
from fastapi import FastAPI
from contextlib import asynccontextmanager
from fastapi.staticfiles import StaticFiles
from app.controllers.route_to_web import RouteToWeb
from app.services.markdown_processor import MarkdownProcessor
from app.services.metadata_processor import MetadataProcessor
from app.controllers.category_controller import CategoryController
from fastapi.middleware.gzip import GZipMiddleware
from app.services.image_service import ImageService
class Application:
def __init__(self):
"""Initialize the FastAPI app and configure it."""
self.app = FastAPI(lifespan=self._lifespan_event)
self._set_image_sizes()
self._setup_static_files()
self._include_routers()
self._include_middelware()
@asynccontextmanager
async def _lifespan_event(self, app: FastAPI):
"""Lifespan event for startup and shutdown logic."""
print("App startup: Processing Markdown files...")
# Generate dynamic JSON data
metadata_processor = MetadataProcessor(input_dir="./data", output_file="generated_data.json",app=self.app)
metadata_processor.generate_json()
print("Generated dynamic data file.")
# Process Markdown files into HTML
processor = MarkdownProcessor(input_dir="./data", templates_dir="./templates",app=self.app)
processor.run()
yield
print("App shutdown: Cleanup complete.")
def _setup_static_files(self):
"""Mount static file directories."""
self.app.mount("/data", StaticFiles(directory="data"), name="data")
self.app.mount("/static", StaticFiles(directory="static"), name="static")
self.app.mount( "/images", StaticFiles( directory = "static/images" ), name = "images" )
def _include_routers(self):
"""Include all route controllers."""
category_controller = CategoryController()
image_service = ImageService(self.app)
route_to_web = RouteToWeb(self.app)
self.app.include_router( category_controller.router )
self.app.include_router(route_to_web.router)
self.app.include_router( image_service.router )
def _include_middelware(self):
self.app.add_middleware( GZipMiddleware, minimum_size = 500 )
def _set_image_sizes(self):
self.app.state.IMAGE_SIZES = {
'thumbnails': {'width': 150, 'height': 150},
'large': {'width': 800, 'height': 600},
'small': {'width': 300, 'height': 200},
'original': {'width': None, 'height': None}, # Original størrelse
}
def get_app(self):
"""Return the FastAPI app instance."""
return self.app
application = Application()
app = application.get_app()

0
app/services/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,167 @@
import os
from fastapi import HTTPException
from fastapi.responses import FileResponse
from fastapi import APIRouter, Request, FastAPI
from PIL import Image
class FileHandler:
def __init__(self, category=None, image_type=None, filename=None):
self.filename = filename
self.category = category
self.image_type = image_type
@property
def src_file(self) -> str:
src_path = "data/{category}/images/{filename}"
return src_path.format( category = self.category, filename = self.filename )
@property
def dest_file(self) -> str:
base_url = "/images/{category}/{filename}"
return base_url.format( category = self.category, filename = self.filename )
@property
def dest_filename(self) -> str:
base_url = "static/images/{category}/{image_type}/{filename}"
return base_url.format( category = self.category, image_type = self.image_type, filename = self.filename )
@property
def dest_path(self) -> str:
base_url = "static/images/{category}/{image_type}"
return base_url.format( category = self.category, image_type = self.image_type )
def __str__(self):
return (
f"FileHandler(\n"
f" filename='{self.filename}',\n"
f" category='{self.category}',\n"
f" image_type='{self.image_type}',\n"
f" src_file='{self.src_file}',\n"
f" dest_file='{self.dest_file}',\n"
f" dest_filename='{self.dest_filename}'\n"
f")"
)
def get_category(self, file_path):
# List all categories in the data directory
categories = [
name for name in os.listdir( "data/" )
if os.path.isdir( os.path.join( "data/", name ) )
]
# Search for the category in the file path
for category in categories:
if f"/{category}/" in file_path or f"\\{category}\\" in file_path:
return category
# Return None if no category matches
return None
class ImageService:
def __init__(self,app: FastAPI):
self.router = APIRouter()
self.app = app
self.IMAGE_SIZES = self.app.state.IMAGE_SIZES
#self._ensure_directories_exist()
self._add_routes()
def __str__(self):
"""
Provides a string representation of the class instance.
"""
base_paths_str = "\n".join(
[f"{key}: {value}" for key, value in self.base_paths.items()]
)
image_sizes_str = "\n".join(
[
f"{key}: width={value['width']}, height={value['height']}"
for key, value in self.image_sizes.items()
]
)
return f"<Class:ImageService Base Paths:{base_paths_str} Image Sizes:\n{image_sizes_str}"
def get_image_size(self, image_type: str) -> dict:
"""
Retrieve the width and height for a given image type from the app state.
Args:
request (Request): FastAPI request object.
image_type (str): The type of the image (e.g., 'thumbnails').
Returns:
dict: A dictionary with 'width' and 'height'.
"""
image_sizes = self.app.state.IMAGE_SIZES
if image_type not in image_sizes:
raise ValueError( f"Invalid image type: {image_type}. Must be one of {list( image_sizes.keys() )}" )
return image_sizes[image_type]
def _add_routes(self):
self.router.add_api_route(
"/image/{category}/{type}/{filename}",
self.get_image,
methods=["GET"],
response_class=FileResponse,
)
async def get_image(self, category: str, type: str, filename: str):
"""
Retrieve an image file from the specified category and type.
"""
file_path = self._resolve_path(category, type, filename)
return FileResponse(file_path)
def validate_image(self, file_path:FileHandler=None, width:int=None, height:int=None, overwrite = True ) -> bool:
if not os.path.exists( file_path.dest_filename ):
with Image.open( file_path.src_file ) as img:
print(file_path.src_file)
self._resize_image( img, file_path, width, height )
return True
with Image.open( file_path.dest_filename ) as img:
if img.width != width or img.height != height:
if overwrite:
self._resize_image( img, file_path, width, height )
return False
return True
def _resize_image(self, img: Image.Image, file_path: FileHandler, width: int, height: int):
resized_img = img.resize( (width, height), Image.Resampling.LANCZOS )
os.makedirs(file_path.dest_path,exist_ok = True)
resized_img.save( file_path.dest_filename )
async def get_image(self, category: str, type: str, filename: str):
file_path = self._resolve_path( category, type, filename )
return FileResponse( file_path )
def image_tag(self, category: str, image_type: str, filename: str, alt: str = "", width: int = None,
height: int = None) -> str:
"""
Generate an HTML <img> tag with default sizes if dimensions are not provided.
"""
# Use default sizes if none are provided
default_size = self.get_image_size( image_type)
width = width or default_size.get( "width" )
height = height or default_size.get( "height" )
file_path = FileHandler(category = category,image_type = image_type,filename = filename)
self.validate_image( file_path, width = width,height=height, overwrite = True )
tag = f'<img src="/{file_path.dest_filename}" alt="{alt}"'
if width:
tag += f' width="{width}"'
if height:
tag += f' height="{height}"'
tag += ">"
return tag

View File

@@ -0,0 +1,99 @@
import os
from bs4 import BeautifulSoup
from fastapi import FastAPI
from app.services.markdown_render import MarkdownRenderer
from jinja2 import Environment, FileSystemLoader
class MarkdownProcessor:
"""
A class to process Markdown files, extract metadata, and generate a single
'index.html' per category directory using a custom rendering engine.
"""
def __init__(self, input_dir: str, templates_dir: str,app:FastAPI=None):
"""
Initialize the MarkdownProcessor.
Args:
input_dir (str): Root directory containing category subdirectories.
templates_dir (str): Directory containing Jinja2 templates.
"""
self.input_dir = input_dir
self.env = Environment(loader=FileSystemLoader(templates_dir))
self.app = app
def _process_markdown_files_in_directory(self, directory_path: str) -> list:
"""
Process all Markdown files in a directory using Markdown and Jinja2 custom tags.
Args:
directory_path (str): Path to the category directory.
Returns:
list: A list of processed sections containing metadata and rendered content.
"""
from pathlib import Path
sections = []
for file in sorted(os.listdir(directory_path)):
if file.endswith(".md"):
file_path = os.path.join(directory_path, file)
with open(file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
markdown_render = MarkdownRenderer(file_path=file_path,app=self.app)
# Process Markdown and Jinja2
rendered_content, metadata = markdown_render.render_markdown_with_jinja ( markdown_content )
# Append the section to the list
sections.append({
"name": metadata.get("title", "Untitled"),
"content": rendered_content,
"summary": metadata.get("summary", ""),
"author": metadata.get("author", "Unknown"),
})
return sections
def _generate_index_html(self, directory_path: str, sections: list, output_file: str):
"""
Generate the index.html file for a category using the combined sections.
Args:
directory_path (str): Path to the category directory.
sections (list): List of processed Markdown content and metadata.
output_file (str): Path to save the generated index.html.
"""
# Render the template with the combined sections
template = self.env.get_template("combined_template.html")
rendered_html = template.render(
title=os.path.basename(directory_path).capitalize(),
sections=sections
)
# Write the rendered HTML to index.html
os.makedirs(directory_path, exist_ok=True)
with open(output_file, "w", encoding="utf-8") as output:
soup = BeautifulSoup( rendered_html, 'html.parser' )
cleaned_html = soup.prettify(formatter="html5")
output.write(cleaned_html)
print(f"Generated: {output_file}")
def run(self):
"""
Run the Markdown processing workflow: one 'index.html' per category.
"""
for root, dirs, _ in os.walk(self.input_dir):
for directory in dirs:
category_path = os.path.join(root, directory)
output_file = os.path.join(category_path, "index.html")
# Process all Markdown files in the current category directory
sections = self._process_markdown_files_in_directory(category_path)
if sections:
self._generate_index_html(category_path, sections, output_file)

View File

@@ -0,0 +1,171 @@
from pathlib import Path
import sys
import markdown
from fastapi import FastAPI
from jinja2 import Environment, DictLoader
from markupsafe import Markup
from .image_service import ImageService, FileHandler
class MarkdownRenderer:
def __init__(self, file_path: str = None, app: FastAPI=None):
"""
Initialize the MarkdownRenderer with a Jinja2 environment and custom functions.
"""
self.app = app
self.image_service = ImageService(self.app)
self.jinja_env = self._create_jinja_environment()
self.file_path = file_path
def _create_jinja_environment(self) -> Environment:
"""
Create and configure the Jinja2 environment with custom functions.
Returns:
Environment: A configured Jinja2 environment.
"""
env = Environment(loader=DictLoader({"base_template": "{{ content | safe }}"}))
env.globals.update({
"img_left_overlay": self.img_left_overlay,
"box": self.box,
"note": self.note,
"warning": self.warning,
"link_to": self.link_to,
"slider": self.slider,
"image": self.get_image, # Add image handler function
})
return env
def img_left_overlay(self, src: str) -> str:
"""Render an image with overlay."""
return f'''
<div class="img-left-overlay">
<img src="{src}" alt="Overlay Image" loading="lazy">
<div class="overlay-text">Overlay Text</div>
</div>
'''
def box(self, title: str, content: str) -> str:
"""Render a box component."""
return f'''
<div class="box">
<strong>{title}</strong>
<p>{content}</p>
</div>
'''
def note(self, content: str) -> str:
"""Render a note component."""
return f'''
<div class="note">
<p>{content}</p>
</div>
'''
def link_to(self, title: str, url: str) -> str:
"""Render a link component."""
return f'''
<a href="{url}" target="_blank" rel="noopener noreferrer">{title}</a>
'''
def warning(self, content: str) -> str:
"""Render a warning component."""
return f'''
<div class="warning">
⚠️ <p>{content}</p>
</div>
'''
def slider(self, options: dict, images: list) -> str:
"""Render a slider component."""
import uuid
modal_id = uuid.uuid4().hex.upper()[0:6]
html_content = []
html_content.append('<div class="button-stack">')
for i, val in enumerate(images):
self.image_service = ImageService( self.app )
modal_id_current = f"{modal_id}_{i}"
modal_id_next = f"{modal_id}_{i + 1}" if i + 1 < len(images) else f"{modal_id}_0"
category = FileHandler().get_category(self.file_path)
thumbnal_img = self.image_service.image_tag(category = category, image_type = "thumbnails",filename = val,alt="A better description later on")
modal_img = self.image_service.image_tag(category = category, image_type = "large",filename = val,alt="A better description later on")
html_content.append(f"""
<button onclick="openModal('modal{modal_id_current}')" class="stacked-button">
{thumbnal_img}
</button>
<div class="modal" id="modal{modal_id_current}">
<div class="modal-content">
<h2>Modal {i}</h2>
{modal_img}
<div class="modal-buttons">
<button onclick="closeModal('modal{modal_id_current}')">Close</button>
<button class="next-btn" onclick="nextModal('modal{modal_id_current}', 'modal{modal_id_next}')">Next</button>
</div>
</div>
</div>
""")
html_content.append('</div>')
return '\n'.join(html_content)
def _get_category(self):
if isinstance(self.file_path, str):
this_path = Path(self.file_path)
return this_path.parent.name
return True
def get_image(self, image_type: str, filename: str, alt: str = "", width: int = None, height: int = None) -> Markup:
"""
Generate a dynamic HTML <img> tag for an image using ImageService's image_tag method.
"""
valid_types = ['thumbnails', 'large', 'small', 'original']
if image_type not in valid_types:
sys.tracebacklimit = 0
raise ValueError( f"Invalid image type: {image_type}. Must be one of {valid_types}" )
tag = self.image_service.image_tag(
category=self._get_category(),
image_type=image_type,
filename=filename,
alt=alt,
width=width,
height=height
)
return Markup(tag)
def render_markdown_with_jinja(self, markdown_content: str):
"""
Convert Markdown to HTML and apply Jinja2 rendering for custom tags.
Args:
markdown_content (str): Raw Markdown content.
Returns:
tuple: Rendered HTML content and metadata as a dictionary.
"""
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
intermediate_html = md.convert(markdown_content)
metadata = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
# Step 3: Pass the resulting HTML with Jinja2 custom tags through Jinja2
template = self.jinja_env.get_template("base_template")
final_html = template.render(content=intermediate_html)
# Step 4: Re-render final_html in Jinja2 for embedded tags like {{ image(...) }}
final_output = self.jinja_env.from_string(final_html).render()
return final_output, metadata

View File

@@ -0,0 +1,105 @@
import os
import markdown
import json
from typing import List, Dict
from fastapi import FastAPI
class MetadataProcessor:
"""
A class to scan Markdown files, extract front matter metadata,
and generate a structured JSON file.
"""
def __init__(self, input_dir: str, output_file: str,app:FastAPI=None):
"""
Initialize the MetadataProcessor.
Args:
input_dir (str): Directory containing Markdown files.
output_file (str): Path to save the generated JSON file.
"""
self.input_dir = input_dir
self.output_file = output_file
self.data = {"categories": [], "favorites": []}
def _extract_metadata(self, file_path: str) -> Dict:
"""
Extract front matter metadata using the 'markdown' package.
Args:
file_path (str): Path to the Markdown file.
Returns:
dict: A dictionary containing the extracted metadata.
"""
with open(file_path, "r", encoding="utf-8") as file:
markdown_content = file.read()
# Initialize Markdown with meta extension
md = markdown.Markdown(extensions=["extra", "nl2br", "meta"])
md.convert(markdown_content)
# Metadata is stored in md.Meta as a dictionary of lists
meta = {key: " ".join(value) for key, value in md.Meta.items()} if md.Meta else {}
return meta
def _process_directory(self):
"""
Recursively scan the input directory for Markdown files
and extract metadata to build the JSON structure.
"""
for root, _, files in os.walk(self.input_dir):
for file in files:
if file.endswith(".md"):
file_path = os.path.join(root, file)
metadata = self._extract_metadata(file_path)
if metadata:
# Add to 'categories'
self.data["categories"].append({
"name": metadata.get("name", "Unknown"),
"path": os.path.relpath(root, self.input_dir).replace(os.sep, "/"),
"author": metadata.get("author", "Unknown")
})
# Add to 'favorites' if 'favorite' is true
if metadata.get("favorite") and metadata["favorite"].lower() == "true":
self.data["favorites"].append({
"name": metadata.get("name", "Unknown"),
"image": metadata.get("image", "images/default.jpg"),
"description": metadata.get("summary", "No description provided"),
"path": os.path.relpath(root, self.input_dir).replace(os.sep, "/"),
})
def generate_json(self):
"""
Generate the JSON structure, deduplicate and sort categories by 'path',
then save it to the output file.
"""
self._process_directory() # Extract all markdown data into self.data
# Ensure 'categories' exists and is a list
if "categories" not in self.data:
self.data["categories"] = []
# Deduplicate 'categories' using 'path' as the unique key
unique_categories = { }
for category in self.data["categories"]:
if isinstance( category, dict ): # Ensure valid category structure
path = category.get( "path", "unknown" ) # Use 'path' as the unique key
if path not in unique_categories:
unique_categories[path] = category
# Replace the 'categories' list with a sorted version by 'path'
self.data["categories"] = sorted(
unique_categories.values(),
key = lambda x: x.get( "path", "unknown" )
)
# Save the updated JSON to file
with open( self.output_file, "w", encoding = "utf-8" ) as json_file:
json.dump( self.data, json_file, indent = 4, ensure_ascii = False )
print( f"Generated JSON saved to {self.output_file}" )

View File

@@ -1,43 +0,0 @@
import os
from markdown_render import render_markdown_with_jinja
def process_markdown_files(input_dir: str, output_dir: str):
"""
Recursively process all Markdown files in the input directory,
render them to HTML, and save them to the output directory.
"""
for root, _, files in os.walk(input_dir):
for file in files:
if file.endswith(".md"):
input_file_path = os.path.join(root, file)
# Determine output file path (convert .md to .html)
relative_path = os.path.relpath(input_file_path, input_dir)
output_file_path = os.path.join(output_dir, os.path.splitext(relative_path)[0] + ".html")
# Ensure the output directory exists
os.makedirs(os.path.dirname(output_file_path), exist_ok=True)
# Read Markdown content
with open(input_file_path, "r", encoding="utf-8") as md_file:
markdown_content = md_file.read()
# Render Markdown with Jinja2
print(f"Processing: {input_file_path} -> {output_file_path}")
rendered_html = render_markdown_with_jinja(markdown_content)
# Write the rendered HTML to the output file
with open(output_file_path, "w", encoding="utf-8") as html_file:
html_file.write(rendered_html)
print("Markdown processing complete!")
if __name__ == "__main__":
# Input directory containing Markdown files
input_directory = "./data"
# Output directory where HTML files will be stored
output_directory = "./data"
# Start the processing
process_markdown_files(input_directory, output_directory)

BIN
data.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,25 @@
---
name: Arbejde i Portugal
description: Mine noter og fund om at arbejde i Portugal som EU-borger
author: Henrik Jess
date: ons 11 dec 22:16:13 CET 2024
summary: Praktisk info fra min research om arbejde i Portugal
favorite: false
image: images/pic09.jpg
category: Job
tags: [Portugal, Arbejde, EU-borgere, NIF-nummer, Socialsikring]
---
# Kan jeg som udlænding arbejde i Portugal?
Jeg er ikke selv flyttet til Portugal endnu, men jeg har brugt en del tid på at undersøge, hvad der kræves for at arbejde der som EU-borger. Her er mine noter, baseret på det, jeg har fundet på nettet, YouTube og forskellige guides.
Som dansker kan man heldigvis arbejde i Portugal uden at skulle søge om en arbejdstilladelse. Det skyldes EU's regler om fri bevægelighed, så på det punkt er det ret ligetil.
Men der er nogle praktiske ting, du skal have styr på, før du kan komme i gang:
- **NIF-nummer**: Det er et skattemæssigt identifikationsnummer, som du skal bruge til stort set alt i Portugal arbejde, bankkonto og bolig.
- **Socialsikringssystemet**: Du skal registrere dig i det portugisiske socialsikringssystem for at få adgang til sundhed og sociale ydelser.
Selvom processen virker enkel på papiret, går det igen i mange kilder, at det kan tage lidt tid, især hvis du ikke taler portugisisk. Flere anbefaler at få hjælp fra nogen, der har prøvet det før, eller bruge lokale rådgivere, hvis det bliver for bøvlet.
Jeg deler bare det, jeg har fundet indtil videre, så hvis du også overvejer at arbejde i Portugal, håber jeg, det kan give dig en god start.

View File

@@ -0,0 +1,27 @@
---
name: Populære områder i Portugal
description: En guide til attraktive områder for tilflyttere i Portugal
author: Henrik Jess
date: ons 11 dec 23:00:00 CET 2024
summary: Populære regioner som Lissabon, Porto og Algarve
favorite: false
image: images/pic05.jpg
category: Bolig
tags: [Portugal, Lissabon, Porto, Algarve, Coimbra, Viseu, Tilflyttere]
---
# Hvilke områder er populære for tilflyttere?
Når man overvejer at flytte til Portugal, er der nogle områder, som skiller sig ud som særligt attraktive for tilflyttere.
### Populære områder:
- **{{ link_to(title="Lissabon", url="https://www.google.com/maps?q=Lissabon") }}**: Portugals hovedstad med god infrastruktur, et levende kulturliv og mange jobmuligheder.
- **{{ link_to(title="Porto", url="https://www.google.com/maps?q=Porto") }}**: Kendt for sin charme, historie og et voksende expat-fællesskab.
- **{{ link_to(title="Algarve", url="https://www.google.com/maps?q=Algarve") }}**: Ideelt for dem, der søger sol, strande og en afslappet livsstil.
### Alternativer til storbyerne:
Mindre kendte områder kan være attraktive for dem, der ønsker lavere boligpriser og en roligere livsstil:
- **{{ link_to(title="Coimbra", url="https://www.google.com/maps?q=Coimbra") }}**: En universitetsby med historie og lavere leveomkostninger.
- **{{ link_to(title="Viseu", url="https://www.google.com/maps?q=Viseu") }}**: Kendt for sin kvalitet af liv, smukke landskab og mere overkommelige boligpriser.
Uanset hvor i Portugal du vælger at bosætte dig, er der noget for enhver smag fra travle byer med international atmosfære til mindre byer med ro og autentisk portugisisk kultur.

View File

@@ -0,0 +1,15 @@
---
name: Mit kommende område i Portugal
description: Planer om at flytte til Porto
author: Henrik Jess
date: ons 11 dec 23:10:00 CET 2024
summary: Kort om hvorfor Porto er mit foretrukne område i Portugal
favorite: false
image: images/pic06.jpg
category: Bolig
tags: [Portugal, Porto, Flytning, Planlægning]
---
# Hvilket område i Portugal planlægger jeg at flytte til?
Primært Porto

View File

@@ -0,0 +1,23 @@
---
name: Boligsøgning i Portugal
description: Effektive platforme og tips til boligsøgning i Portugal
author: Henrik Jess
date: ons 11 dec 23:15:00 CET 2024
summary: Brug Idealista og tips til at finde bolig i Portugal
favorite: false
image: images/pic08.jpg
category: Bolig
tags: [Portugal, Bolig, Idealista, Boligsøgning, Flytning]
---
# Hvor finder man bolig?
En af de bedste platforme, jeg har fundet til boligsøgning i Portugal, er **{{ link_to(title="idealista.pt", url="https://www.idealista.pt/") }}**. Den minder lidt om DBA og er både overskuelig og nem at bruge. Det er hurtigt at oprette en profil, og tjenesten er tilmed gratis.
### Udfordringer ved boligsøgning
Hvis du ikke kender Portugal særlig godt, kan det være svært at beslutte, hvilket område der passer bedst til dine behov. Her er nogle tips:
- Brug kortfunktionen på Idealista til at få overblik over priser i forskellige områder.
- Overvej at besøge de mest interessante områder først, før du træffer din beslutning.
- Tjek lokale Facebook-grupper og fora for tips fra andre tilflyttere.
Portugal har meget at byde på, og det rigtige område afhænger af, om du søger storbyliv, strande eller roligere omgivelser.

View File

@@ -0,0 +1,41 @@
---
name: Bolig i Portugal
description: Købs- og lejepriser samt regionale forskelle i Portugal
author: Henrik Jess
date: ons 11 dec 22:45:00 CET 2024
summary: Boligpriser og ekstra omkostninger i Portugal
favorite: false
image: images/pic09.jpg
category: Bolig
tags: [Portugal, Bolig, Leje, Køb, Lissabon, Ejendomsskatter]
---
# Er det dyrt at købe eller leje bolig i Portugal?
Boligpriser i Portugal varierer meget afhængigt af regionen. I de større byer som **Lissabon** og **Porto** er priserne generelt høje, mens mindre byer som **Coimbra** og **Guarda** tilbyder mere overkommelige muligheder.
---
## Regionale forskelle
- **Lissabon og Porto**: Som de mest populære områder for både tilflyttere og turister har disse byer nogle af de højeste boligpriser i landet.
- **Mindre byer**: Byer som Coimbra, Viseu eller Guarda giver langt billigere alternativer og tilbyder en roligere livsstil.
---
## Ekstra omkostninger ved boligkøb
Ud over selve boligprisen skal man være opmærksom på flere ekstra udgifter:
- **Ejendomsskat (IMI)**: En årlig skat på mellem **0,3% og 0,45%** af boligens værdi.
- **IMT-afgift**: En engangsafgift ved køb af bolig, som afhænger af købsprisen.
- **Advokat- og notaromkostninger**: Ved boligkøb er det normalt at bruge advokat og notar for at sikre korrekt papirarbejde.
---
## Leje vs. køb
Hvis du ikke er klar til at købe, er **leje** en god mulighed. Lejepriser varierer også meget:
- I Lissabon og Porto er månedlig husleje typisk højere, men stadig billigere end i de fleste danske storbyer.
- I mindre byer kan du finde boliger til markant lavere priser, især hvis du er fleksibel med beliggenheden.
---
## Konklusion
Boligmarkedet i Portugal byder på både muligheder og udfordringer. Mens de populære områder har højere priser, er der stadig gode alternativer i mindre byer. Når man medregner de lavere leveomkostninger i Portugal sammenlignet med Danmark, er der potentiale for at få mere værdi for pengene.

View File

@@ -0,0 +1,142 @@
---
name: Budget - Indkøb
description: En kort sammenligning af priserne
author: Henrik Jess
date: ons 11 dec 23:25:00 CET 2024
summary: Fødevarer er markant billigere i Portugal med få undtagelser som bær og specialvarer.
favorite: true
image: images/budget2.jpg
category: Økonomi
tags: [Portugal, Budget, Økonomi]
---
Jeg har taget udgangspunkt i et indkøb fra Mambeno / Rema1000 indkøb for en uge, og forsøgt og sammenligne det. Her er den fulde kvittering med korrekt **omregning til EUR**, de portugisiske priser og den **procentvise forskel** mellem Danmark og Portugal. Nogen af priserne er konsekvent højere, så er dt fordi der er købt store pakker, eksempelvis Laks.
---
{{ box(title="Disclaimer",content="Priserne er baseret på her-og-nu priser fra REMA 1000 i Danmark. Enkelte produkter kan have været på tilbud, hvilket kan påvirke sammenligningen.") }}
## **Samlet Oversigt**
**Valutakurs**: 1 EUR = 7,44 DKK
| **Nr.** | **Varebeskrivelse** | **Pris i Danmark (EUR)** | **Pris i Portugal (EUR)** | **Forskel (EUR)** | **% Forskel** |
|---------|--------------------------------|--------------------------|---------------------------|------------------:|--------------:|
| 1 | FLUTES | 0,87 | 0,50 | 0,37 | -42,5% |
| 2 | TANDPASTA COLGATE | 3,49 | 2,00 | 1,49 | -42,7% |
| 3 | SOLSIKKERUGBRØD DET GODE | 3,43 | 2,00 | 1,43 | -41,7% |
| 4 | TOILETPAPIR SOFT 3-LAGS | 2,75 | 1,80 | 0,95 | -34,5% |
| 5 | JORDBÆR (1 LTR) | 1,14 | 1,50 | -0,36 | +31,6% |
| 6 | SOLBÆR (1 LTR) | 1,14 | 1,50 | -0,36 | +31,6% |
| 7 | HINDBÆR/APPELSIN (1 LTR) | 1,14 | 1,40 | -0,26 | +22,8% |
| 8 | MINIMÆLK 0,4% | 1,61 | 0,90 | 0,71 | -44,1% |
| 9 | RISOTTO M/SVAMPE | 2,01 | 1,70 | 0,31 | -15,4% |
| 10 | KÆRGÅRDEN SMØRBAR LET | 3,49 | 2,00 | 1,49 | -42,7% |
| 11 | SDJ. SPEGEPØLSE 3-STJERNET | 2,68 | 2,00 | 0,68 | -25,4% |
| 12 | OKSE SPEGEPØLSE 3-STJERNET | 2,68 | 2,00 | 0,68 | -25,4% |
| 13 | KARTOFFELSPEGEPØLSE 3-STJERNET | 2,68 | 2,00 | 0,68 | -25,4% |
| 14 | KØDPØLSE 3-STJERNET | 2,41 | 2,00 | 0,41 | -17,1% |
| 15 | TORTILLAS HVEDE | 1,41 | 1,20 | 0,21 | -14,9% |
| 16 | GUACAMOLE DIP | 1,51 | 1,20 | 0,31 | -20,5% |
| 17 | SALSASAUCE HOT | 1,31 | 1,00 | 0,31 | -23,7% |
| 18 | SMØR ARLA, ØKOLOGISK | 3,62 | 2,50 | 1,12 | -31,0% |
| 19 | SKYR NATUREL | 2,41 | 2,00 | 0,41 | -17,1% |
| 20 | CHEDDAR OST | 2,01 | 1,80 | 0,21 | -10,4% |
| 21 | FRAICHE 5% | 2,68 | 2,20 | 0,48 | -17,9% |
| 22 | KRYDDEROST BUKO | 3,62 | 2,50 | 1,12 | -31,0% |
| 23 | FRILANDS BRUNCHÆG | 2,01 | 1,80 | 0,21 | -10,4% |
| 24 | TORTILLA CHIPS SALT | 1,04 | 1,00 | 0,04 | -3,8% |
| 25 | GROV CAFESANDWICH | 2,95 | 2,20 | 0,75 | -25,4% |
| 26 | FULDKORNSTORTILLAS | 1,65 | 1,40 | 0,25 | -15,2% |
| 27 | KYLLINGESCHNITZLER | 2,68 | 2,30 | 0,38 | -14,2% |
| 28 | RATATOUILLE | 1,88 | 1,50 | 0,38 | -20,2% |
| 29 | FINE ÆRTER | 1,61 | 1,20 | 0,41 | -25,5% |
| 30 | FAJITA BLANDING | 1,88 | 1,40 | 0,48 | -25,5% |
| 31 | SOLSIKKEKERNER | 1,55 | 1,20 | 0,35 | -22,6% |
| 32 | KOGEPOSE RIS | 1,01 | 0,90 | 0,11 | -10,9% |
| 33 | RASP | 0,93 | 0,80 | 0,13 | -14,0% |
| 34 | HASSELNØDDEKERNER | 1,61 | 1,20 | 0,41 | -25,5% |
| 35 | PENNE RIGATE | 1,14 | 1,00 | 0,14 | -12,3% |
| 36 | ROGN | 2,14 | 1,80 | 0,34 | -15,9% |
| 37 | TOMAT PASTA | 0,67 | 0,50 | 0,17 | -25,4% |
| 38 | SØD FRANSK SENNEP | 1,28 | 1,00 | 0,28 | -21,9% |
| 39 | SOJASAUCE | 1,34 | 1,10 | 0,24 | -17,9% |
| 40 | MAJS | 1,01 | 0,80 | 0,21 | -20,8% |
| 41 | HVIDE BØNNER I TOMAT | 0,93 | 0,80 | 0,13 | -14,0% |
| 42 | HAKKEDE TOMATER | 1,01 | 0,80 | 0,21 | -20,8% |
| 43 | GRØNTSAGSBOUILLON | 0,60 | 0,50 | 0,10 | -16,7% |
| 44 | KRYDDERURTEDRESSING | 1,68 | 1,20 | 0,48 | -28,6% |
| 45 | HONNING | 3,62 | 2,50 | 1,12 | -31,0% |
| 46 | SØNDERJYSK SPEGEPØLSE | 1,34 | 1,20 | 0,14 | -10,4% |
| 47 | LAKSEFILET | 10,75 | 7,00 | 3,75 | -34,9% |
| 48 | KYLLINGEBRYSTFILET | 8,06 | 5,50 | 2,56 | -31,8% |
| 49 | TOMATER I BAKKE | 1,61 | 1,20 | 0,41 | -25,5% |
| 50 | TØRRET TIMIAN | 0,67 | 0,50 | 0,17 | -25,4% |
| 51 | SPÆD KÅLSALAT | 3,36 | 2,50 | 0,86 | -25,6% |
| 52 | PERSILLE | 1,21 | 1,00 | 0,21 | -17,4% |
| 53 | ØKOLOGISKE CITRONER | 0,81 | 0,70 | 0,11 | -13,6% |
| 54 | RØD PEBER | 1,07 | 0,90 | 0,17 | -15,9% |
| 55 | AGURK | 0,81 | 0,70 | 0,11 | -13,6% |
---
### **Opsummering af Prissammenligning mellem Danmark og Portugal**
| **Parameter** | **Værdi** |
|--------------------------------------|--------------------------------|
| Antal varer sammenlignet | 55 |
| Gennemsnitlig pris i Danmark (EUR) | 2,42 |
| Gennemsnitlig pris i Portugal (EUR) | 1,84 |
| Gennemsnitlig besparelse | **24,0% lavere i Portugal** |
| Antal varer billigere i Portugal | 47 |
| Antal varer dyrere i Portugal | 8 |
1. **Generel besparelse**
Priserne i Portugal er **i gennemsnit 24,0% lavere** end i Danmark på tværs af de 55 varer. Dette er særligt tydeligt for basisvarer som smør, mælk og kolonialvarer.
2. **Flest varer er billigere i Portugal**
Af de 55 varer er **47 billigere** i Portugal, hvilket tydeligt afspejler de lavere leveomkostninger og en mere konkurrencedygtig fødevaresektor.
3. **Eksempler på store besparelser**
- **Kærgården Smørbar Let**: -42,7% billigere
- **Smør Arla Økologisk**: -31,0% billigere
- **Mælk (Minimælk)**: -44,1% billigere
- **Laksefilet**: -34,9% billigere
Disse basisvarer oplever en markant prisreduktion i Portugal, hvilket giver væsentlige besparelser i dagligdagen.
4. **Få varer dyrere i Portugal**
Der er **8 varer**, hvor priserne er højere i Portugal. Eksempler inkluderer:
- **Jordbær/Solbær**: +31,6% dyrere
- **Hindbær/Appelsin**: +22,8% dyrere
Disse prisforskelle kan tilskrives sæsonvariationer og højere importomkostninger. Det kan også begrundes i det kan være danske Tilbuds vare.
5. **Frisk frugt og grønt**
Generelt er grøntsager som **tomater, agurk og røde peberfrugter** betydeligt billigere i Portugal. Dog er enkelte varer som **bær** dyrere, sandsynligvis på grund af sæsonafhængighed og øgede importomkostninger.
---
Prissammenligningen viser, at dagligvarer generelt er **betydeligt billigere i Portugal**, især når det gælder smør, mælk og kolonialvarer, som typisk har besparelser på op til **50%**. Dette gør Portugal økonomisk attraktivt for husholdninger. Dog skal man være opmærksom på, at enkelte varer som **bær** og specialvarer kan være dyrere, hvilket er en vigtig faktor i planlægningen af leveomkostninger.
---
En anden (mere proff) side kommer til næsten de samme konklusioner, man kan måske godt se jeg har brugt Rema1000s tilbuds priser - Men de er relativt tæt på hinanden
- **Leveomkostninger i Portugal er 38,5% lavere end i Danmark (eksklusive husleje).**
- **Leveomkostninger inklusive husleje i Portugal er 32,4% lavere end i Danmark.**
- **Huslejepriser i Portugal er 13,1% lavere end i Danmark.**
- **Restaurantpriser i Portugal er 53,0% lavere end i Danmark.**
- **Dagligvarepriser i Portugal er 33,4% lavere end i Danmark.**
- **Den lokale købekraft i Portugal er 54,3% lavere end i Danmark.**
{{ link_to(title="Leveomkostningssammenligning mellem Danmark og Portugal - Numbeo", url="https://www.numbeo.com/cost-of-living/compare_countries_result.jsp?country1=Denmark&country2=Portugal") }} - De siger 33.4%
{{ link_to(title="Leveomkostninger mellem Danmark og Portugal - Livingcost", url="https://livingcost.org/cost/denmark/portugal") }} - De siger 28.3% som rammer også meget godt.

View File

@@ -0,0 +1,44 @@
---
name: Supermarkedssammenligning
description: En praktisk guide til at sammenligne supermarkeder i Danmark og Portugal.
author: Henrik Jess
date: ons 11 dec 23:55:00 CET 2024
summary: Find de bedste steder at handle i Portugal, sammenlignet med danske supermarkeder.
favorite: false
image: images/supermarked.jpg
category: Økonomi
tags: [Portugal, Indkøb, Supermarkeder, Økonomi]
---
### **Sammenligning af supermarkeder: Danmark vs Portugal**
| **Danmark** | **Portugal** | **Bemærkninger** |
|--------------------------|-------------------------|------------------------------------------------------|
| **REMA 1000** | **Pingo Doce** | Budgetvenligt supermarked med mange dagligvarer. |
| **Føtex** / **Bilka** | **Continente** | Stort supermarked med bredt udvalg og gode tilbud. |
| **Netto** | **Lidl** | Lavpris supermarked. Lidl findes i begge lande. |
| **SuperBrugsen** | **Auchan** (tidl. Jumbo)| Mid-range supermarked med fokus på kvalitet. |
| **Irma** | **El Corte Inglés** | Premium supermarked med internationale varer. |
| **Meny** | **Intermarché** | God kvalitet og et stort udvalg af varer. |
| **Aldi** | **Aldi** | Ensartet kæde i begge lande, kendt for lave priser. |
| **Kvickly** | **Minipreço** | Prisvenligt med mindre butikker i lokalområder. |
---
### **Anbefalede steder at handle i Portugal**
1. {{ link_to(title="**Pingo Doce**", url="https://www.pingodoce.pt") }} Kendt for lave priser og gode tilbud, især på dagligvarer som brød, mælk og grøntsager.
2. {{ link_to(title="**Continente**", url="https://www.continente.pt") }} Portugals svar på Bilka/Føtex med alt fra mad til husholdningsartikler. Ofte har de gode rabatprogrammer.
3. {{ link_to(title="**Lidl**", url="https://www.lidl.pt") }} Samme kendte koncept som i Danmark. Billige basisvarer og et mindre udvalg.
4. {{ link_to(title="**Auchan**", url="https://www.auchan.pt") }} (tidligere Jumbo) Større supermarkeder med et bredt udvalg af varer, inklusiv internationale produkter.
5. {{ link_to(title="**Intermarché**", url="https://www.intermarche.pt") }} Fokus på frisk frugt og grønt samt lokale portugisiske produkter.
6. {{ link_to(title="**Minipreço**", url="https://www.minipreco.pt") }} Lavprisalternativ med mindre butikker, som er nemme at finde i byområder.
7. {{ link_to(title="**El Corte Inglés**", url="https://www.elcorteingles.pt") }} Perfekt til luksusvarer og specialprodukter, men til lidt højere priser.
---
### **Tips til at spare penge i Portugal**
- **Frugt og grønt** er ofte billigst på lokale markeder, som fx **"Mercado Municipal"**.
- Brug **loyalitetskort** i supermarkeder som Pingo Doce og Continente for at få rabatter.
- Køb kød og fisk på **lokale slagtere** og fiskemarkeder for bedre priser og kvalitet.
- Planlæg ugentlige indkøb i større butikker som **Continente** for at udnytte kampagner og tilbud.

View File

@@ -0,0 +1,26 @@
---
name: El- og vandregninger i Portugal
description: Hvordan el- og vandregninger påvirker leveomkostningerne
author: Henrik Jess
date: ons 11 dec 23:20:00 CET 2024
summary: Elregninger er høje, mens vand og gebyrer er moderate
favorite: false
image: images/pic08.jpg
category: Leveomkostninger
tags: [Portugal, Leveomkostninger, Elregninger, Vandregninger, Gebyrer]
---
# Hvordan påvirker el- og vandregninger leveomkostningerne?
- **Elregninger**: Elektricitet i Portugal kan være relativt dyrt sammenlignet med andre europæiske lande. Det er en omkostning, der især mærkes i vintermånederne, hvor opvarmning kan trække prisen op.
- **Vand og affaldsgebyrer**: Disse regninger er typisk moderate og overkommelige, hvilket hjælper med at holde de samlede udgifter nede.
Elektricitetspriserne i Portugal er blandt de højeste i Europa. Ifølge Eurostat var den gennemsnitlige pris for husholdninger i første halvdel af 2024 €0,2426 per kWh, en stigning på 5,52% fra det foregående halvår. Denne stigning skyldes blandt andet landets afhængighed af importerede energikilder, som udgør omkring 65% af det samlede energiforbrug.
I modsætning hertil er omkostningerne til vand og affaldshåndtering i Portugal mere moderate. For en lejlighed på 85 m² ligger de månedlige udgifter til el, vand, varme, køling og affaldsbortskaffelse mellem €54 og €150, med et gennemsnit omkring midten af dette interval. Disse omkostninger er relativt lave sammenlignet med andre europæiske lande.
For at reducere de høje eludgifter, især i vintermånederne, kan det være fordelagtigt at investere i energieffektive løsninger og apparater. Desuden kan bevidsthed om energiforbrug og valg af tidspunkter med lavere elpriser bidrage til at minimere omkostningerne.
Sammenfattende er det vigtigt at tage højde for de relativt høje elpriser i Portugal, mens vand- og affaldsgebyrer forbliver moderate. Ved at implementere energieffektive tiltag kan man opnå en mere balanceret og overkommelig samlet leveomkostning.

View File

@@ -0,0 +1,94 @@
---
name: El- og vandregninger i Portugal: Sådan påvirker de leveomkostningerne
description: Undersøg hvordan elregninger og vandgebyrer i Portugal påvirker leveomkostningerne. Få indsigt i boligudgifter, dagligvarer og sammenligning med Danmark.
author: Henrik Jess
date: ons 11 dec 23:20:00 CET 2024
summary: Leveomkostningerne i Portugal er lave, især på bolig og dagligvarer. Få indsigt i, hvordan du kan leve godt og billigt under sydens sol med et gennemtænkt budget.
image: images/pic08.jpg
category: Økonomi
tags: ["Portugal", "Leveomkostninger", "Elregninger", "Vandregninger", "Gebyrer"]
---
Her er det reviderede **husholdningsbudget i Portugal** uden kontorfællesskab og flyrejser, så du kan sammenligne med leveomkostninger i Danmark.
---
## **Månedligt Budget i Portugal**
{{ box(title="Disclaimer",content="Det skal siges jeg har ikke de faktiske tal endnu, så det er 100% på tommelfinger vudering") }}
| **Kategori** | **Udgift pr. måned (EUR)** | **Bemærkninger** |
|---------------------------------|----------------------------|---------------------------------------|
| **Boligleje (100 m²)** | 1.000 - 1.200 | Afhænger af område (fx Lissabon dyrere) |
| **El og varme** | 120 - 150 | Højt i vintermånederne |
| **Vand og affaldsgebyrer** | 30 - 50 | Moderate priser |
| **Internet og mobil** | 40 - 60 | Kombipakke til hjem og telefoner |
| **Mad og dagligvarer** | 500 - 600 | Baseret på ugentlig kvittering (~125 EUR) |
| **Transport (offentlig/benzin)** | 50 - 100 | Offentlig transport og små ture |
| **Forsikringer** | 50 - 75 | Sundhed, indbo, bil osv. |
| **Sundhed** | 50 - 100 | Egenbetaling for tandlæge og medicin |
| **Fritid og aktiviteter** | 100 - 150 | Restauranter, biograf, hobbyer |
| **Erika's skoleudgifter** | 50 - 100 | Materialer og småudgifter |
|**Samlet månedlig udgift** | **1.990 - 2.585 EUR** | **Cirka 14.800 - 19.200 DKK** |
### **Kort analyse af budgetoversigten**
1. **Boligleje**: Boligleje udgør den største udgiftspost med **1.000 - 1.200 EUR** om måneden, afhængigt af området. Lissabon og større byer er typisk dyrere, mens mindre byer tilbyder mere overkommelige priser.
2. **Energiomkostninger (El og varme)**: El og varme koster mellem **120 - 150 EUR**, og dette kan stige i vintermånederne, især hvis opvarmning ikke er energieffektiv. Dette er en vigtig post at planlægge for.
3. **Vand og affaldsgebyrer**: Disse udgifter er relativt lave på **30 - 50 EUR**, hvilket hjælper med at holde de samlede faste omkostninger nede.
4. **Dagligvarer og mad**: Madbudgettet på **500 - 600 EUR** om måneden er realistisk for to personer og er baseret på en gennemsnitlig ugentlig udgift på **125 EUR**. Dette afspejler rimelige priser på dagligvarer i Portugal.
5. **Transport**: Transportomkostninger er beskedne på **50 - 100 EUR**, hvilket inkluderer offentlig transport og mindre biludgifter. Det lave niveau skyldes de lave priser på månedskort og brændstof i Portugal sammenlignet med Nordeuropa.
6. **Sundhed og forsikringer**: Sundhedsudgifter og forsikringer varierer mellem **50 - 100 EUR** hver. Dette inkluderer egenbetaling til tandlæge, medicin og basale forsikringer som sundhed og indbo.
7. **Fritid og aktiviteter**: Fritidsbudgettet på **100 - 150 EUR** dækker restauranter, biograf og hobbyer. Portugal tilbyder generelt billigere oplevelser, hvilket giver mere økonomisk frihed til fritidsaktiviteter.
8. **Skoleudgifter**: Erika's skoleudgifter er moderate med **50 - 100 EUR**, typisk til materialer og mindre udgifter. - Dog med tanke på folkeskole med int. sproglinje (ikke privat)
---
### **Samlet vurdering**
Det samlede budget på **1.990 - 2.585 EUR** om måneden (cirka **14.800 - 19.200 DKK**) viser, at det er muligt at leve komfortabelt i Portugal til en lavere pris end i Danmark.
- **Boligleje** er den største omkostning, men stadig overkommelig sammenlignet med danske storbypriser.
- **Mad og dagligvarer** udgør en betydelig, men rimelig del af budgettet.
- **Faste udgifter** som energi og vand er relativt lave, mens sundhed og fritidsaktiviteter giver økonomisk fleksibilitet.
Dette budget giver plads til en stabil og behagelig hverdag i Portugal uden store kompromiser på livskvalitet.
---
## **Sammenligning med Danmark**
| **Kategori** | **Portugal (EUR)** | **Danmark (EUR)** | **Besparelse (%)** |
|-----------------------------------|----------------------------|-------------------------|-------------------------|
| **Boligleje (100 m²)** | 1.000 - 1.200 | 1.500 - 2.000 | -33% til -50% |
| **El og varme** | 120 - 150 | 200 - 250 | -25% til -40% |
| **Vand og affaldsgebyrer** | 30 - 50 | 60 - 100 | -50% til -70% |
| **Internet og mobil** | 40 - 60 | 50 - 70 | -20% til -30% |
| **Mad og dagligvarer** | 500 - 600 | 800 - 1.000 | -25% til -40% |
| **Transport** | 50 - 100 | 150 - 200 | -50% til -66% |
| **Forsikringer** | 50 - 75 | 100 - 150 | -50% |
| **Sundhed** | 50 - 100 | 100 - 150 | -33% til -50% |
| **Fritid og aktiviteter** | 100 - 150 | 200 - 300 | -50% |
| **Samlet månedlig udgift** | **1.990 - 2.585** | **3.360 - 4.520** | **-30% til -45%** |
---
### **Analyse**
1. **Boligudgifter**
Bolig er markant billigere i Portugal med en besparelse på **33-50%** sammenlignet med Danmark. Lejepriserne i mindre byer er endnu lavere.
2. **Dagligvarer**
Indkøb af mad og andre dagligvarer koster i gennemsnit **25-40% mindre** i Portugal, som også reflekteres i din kvittering.
3. **El og varme**
Selvom elpriserne er høje i Portugal, er de stadig **25-40% lavere** end i Danmark, hvor opvarmningsomkostninger i vintermånederne er væsentligt højere.
4. **Transport og sundhed**
Offentlig transport og sundhedsydelser er også væsentligt billigere i Portugal med besparelser på op til **50-66%**.
5. **Samlede omkostninger**
Samlet set er leveomkostningerne i Portugal **30-45% lavere** end i Danmark. Dette giver et stort økonomisk råderum og mulighed for en bedre livskvalitet.
At bo i Portugal kan give betydelige besparelser på husleje, dagligvarer og generelle leveomkostninger. Selvom elpriserne kan være en udfordring, er det samlede billede væsentligt billigere end i Danmark, især med energieffektive løsninger.

View File

@@ -0,0 +1,22 @@
---
name: Permanent ophold i Portugal
description: Krav og praktiske trin for EU-borgere til permanent ophold
author: Henrik Jess
date: ons 11 dec 23:35:00 CET 2024
summary: Registrering, NIF-nummer og socialsikring i Portugal
favorite: false
image: images/pic07.jpg
category: Flytning
tags: [Portugal, Permanent ophold, EU-borger, NIF-nummer, Socialsikring, Flytning]
---
# Hvad kræves for at bo permanent i Portugal som dansk statsborger?
Som EU-borger er det relativt nemt at få permanent ophold i Portugal. Her er, hvad du skal have styr på:
1. **Registrering af adresse**: Du skal registrere dig som bosiddende i Portugal inden for de første 90 dage.
2. **NIF-nummer**: Det portugisiske skattemæssige identifikationsnummer er nødvendigt for stort set alle økonomiske aktiviteter.
3. **Socialsikringssystemet**: For at få adgang til sundhedssystemet og sociale ydelser skal du registrere dig i det portugisiske socialsikringssystem.
4. **Arbejdsforhold**: En ansættelseskontrakt eller bevis på selvstændig virksomhed er ofte nødvendigt for at dokumentere din økonomiske stabilitet.
Det er en proces, der virker overskuelig på papiret, men fra det jeg har læst og hørt, kan der være lidt bureaukrati involveret. Hvis du planlægger at flytte, er det en god idé at sætte tid af til at få styr på papirarbejdet, så alt glider så nemt som muligt.

View File

@@ -0,0 +1,24 @@
---
name: Åbning af bankkonto i Portugal
description: En guide til krav og fleksible løsninger for bankkonti
author: Henrik Jess
date: ons 11 dec 23:25:00 CET 2024
summary: Krav for bankkonti og fleksible løsninger som Wise
favorite: false
image: images/pic05.jpg
category: Bank og Økonomi
tags: [Portugal, Bankkonto, NIF-nummer, Wise, Økonomi]
---
# Hvordan åbner man en bankkonto i Portugal?
For at åbne en bankkonto i Portugal er der nogle få krav, du skal have styr på:
- **NIF-nummer**: Dit skattemæssige identifikationsnummer, som er nødvendigt for stort set alle økonomiske aktiviteter i Portugal.
- **Gyldig adresse**: Du skal kunne dokumentere din bopæl, enten i Portugal eller et andet land.
### Fleksible løsninger
Hvis du gerne vil undgå høje gebyrer og samtidig have adgang til lokale eurokonti, anbefaler mange **Wise** (tidligere TransferWise). Wise er en god løsning til:
- Internationale overførsler med lave gebyrer.
- At få en lokal konto i euro, som fungerer godt i hverdagen.
Hvis du planlægger at blive i Portugal i længere tid, kan det være praktisk at kombinere en lokal bankkonto med en online bank som Wise for at få det bedste fra begge verdener.

View File

@@ -0,0 +1,22 @@
---
name: Leveomkostninger på lavere indkomst i Portugal
description: Hvordan lave udgifter gør livet muligt på en lavere indkomst
author: Henrik Jess
date: ons 11 dec 23:30:00 CET 2024
summary: Lav bolig og billige varer gør livet overkommeligt
favorite: false
image: images/pic05.jpg
category: Leveomkostninger
tags: [Portugal, Leveomkostninger, Lav indkomst, Bolig, Mad, Transport]
---
# Kan man leve godt på en lavere indkomst i Portugal?
Ja, alt tyder på, at man kan leve godt i Portugal, selv på en lavere indkomst. Det handler især om lave boligomkostninger og de generelt overkommelige priser på mad og transport.
### Lavere leveomkostninger
- **Bolig**: Uden for de store byer som Lissabon og Porto kan huslejen være markant lavere.
- **Mad og dagligvarer**: Lokale produkter som frugt, grøntsager og fisk er både friske og billige.
- **Transport**: Offentlig transport er væsentligt billigere end i Danmark, og den er samtidig effektiv.
Hvis du kan acceptere et mere simpelt liv væk fra de mest populære områder, er Portugal et af de steder, hvor pengene rækker længere. Det gør landet til et attraktivt valg for dem, der ønsker at prioritere livskvalitet fremfor høje indkomster.

View File

@@ -0,0 +1,25 @@
---
name: Flytte permanent til Portugal
description: Praktiske trin for EU-borgere, der vil flytte til Portugal
author: Henrik Jess
date: ons 11 dec 23:40:00 CET 2024
summary: Registrering, NIF-nummer og praktiske krav for at flytte
favorite: false
image: images/pic04.jpg
category: Flytning
tags: [Portugal, Flytning, NIF-nummer, EU-borger, Bosiddende, Bankkonto, Arbejde]
---
# Hvad kræves for at flytte permanent til Portugal?
Som EU-borger er det relativt ukompliceret at flytte permanent til Portugal, men der er nogle formelle trin, du skal gennemføre:
1. **Registrering som bosiddende**: Inden for 90 dage efter ankomst skal du registrere dig som bosiddende i Portugal.
2. **NIF-nummer (skattenummer)**: Dette er nødvendigt for stort set alt at oprette en bankkonto, købe ejendom eller få arbejde.
3. **Dokumentation for økonomisk aktivitet**: En ansættelseskontrakt, bevis på selvstændig virksomhed eller pensionsindkomst kan være nødvendigt for at bekræfte din økonomiske stabilitet.
### Praktiske råd
- Sørg for at få styr på papirarbejdet hurtigt efter ankomst, da det kan spare dig for unødvendige problemer.
- Overvej at få hjælp fra lokale rådgivere, hvis du støder på udfordringer med sprog eller bureaukrati.
Portugal byder på en forholdsvis smidig proces for EU-borgere, hvilket gør det til et oplagt valg for dem, der ønsker at starte et nyt liv i et andet land.

View File

@@ -0,0 +1,27 @@
---
name: Fritidsaktiviteter i Portugal
description: Aktiviteter og oplevelser i Portugals varierede landskab
author: Henrik Jess
date: ons 11 dec 23:50:00 CET 2024
summary: Surfing, vandreture og strande oplev Portugal
favorite: false
image: images/pic07.jpg
category: Fritid
tags: [Portugal, Fritid, Surfing, Vandreture, Strande, Aktiviteter, Vandsport]
---
# Hvilke fritidsaktiviteter kan jeg nyde i Portugal?
Portugal er et fantastisk land for både aktive og afslappende fritidsaktiviteter. Landets varierede landskab betyder, at der er noget for enhver smag.
### Aktive oplevelser
- **Surfing og vandsport**: Portugal er kendt for sine fremragende surfspots, især ved kystområder som **Nazaré**, **Ericeira** og **Peniche**.
- **Vandreture**: De smukke bjergområder som **Serra da Estrela** byder på udfordrende og naturskønne ruter.
- **Cykling**: Mange vælger at udforske landskabet på cykel, hvad enten det er langs kysten eller i det bakkede terræn.
### Afslappende aktiviteter
- **Strandliv**: Portugals kyststrækning har nogle af Europas bedste strande, perfekte til afslapning og solbadning.
- **Vinoplevelser**: Besøg vingårde i **Douro-dalen** og smag på lokalproduceret vin.
- **Kultur og historie**: Tag på opdagelse i charmerende byer som **Sintra**, **Óbidos** og **Évora**.
Uanset om du er til eventyr i naturen eller bare vil slappe af med udsigt over Atlanterhavet, har Portugal masser at byde på.

View File

@@ -0,0 +1,51 @@
---
name: Expat-fællesskaber i Portugal
description: En guide til expat-netværk og ressourcer i Portugal
author: Henrik Jess
date: ons 11 dec 23:55:00 CET 2024
summary: Netværk og fællesskaber for expats i Lissabon, Porto og Algarve
favorite: false
image: images/pic07.jpg
category: Fællesskab
tags: [Portugal, Expat, Lissabon, Porto, Algarve, Netværk, Ressourcer]
---
# Findes der expat-fællesskaber i Portugal?
Ja, der findes mange expat-fællesskaber i Portugal, særligt i områder som **Lissabon**, **Porto** og **Algarve**. De giver social støtte, netværksmuligheder og praktisk hjælp til at tilpasse sig livet i et nyt land.
Uanset om du leder efter sociale arrangementer, professionelle kontakter eller praktiske råd, findes der mange gode ressourcer.
## Populære expat-fællesskaber og platforme
### {{ link_to(title="Internations Portugal", url="https://www.internations.org/portugal-expats") }}
Internations er en af de største internationale platforme for expats og tilbyder:
- Sociale arrangementer.
- Professionelle netværk i byer som **Lissabon** og **Porto**.
### {{ link_to(title="Meetup - Expat Groups in Portugal", url="https://www.meetup.com/topics/expat/pt/") }}
En global platform, hvor expats kan finde lokale grupper i Portugal. Eksempler inkluderer:
- {{ link_to(title="Lisbon Digital Nomads Meetup", url="https://www.meetup.com/Lisbon-Digital-Nomads/") }}: Sociale og arbejdsrelaterede arrangementer i **Lissabon**.
- {{ link_to(title="Porto Expats Meetup", url="https://www.meetup.com/Porto-Expats/") }}: Netværksgrupper for expats i **Porto**.
- {{ link_to(title="Algarve Social & Community Meetup", url="https://www.meetup.com/topics/algarve/") }}: Sociale arrangementer i **Algarve**.
### Facebook-grupper
Facebook er et godt sted at finde expat-grupper. Nogle populære grupper er:
- {{ link_to(title="Expats in Portugal", url="https://www.facebook.com/groups/expatsinportugal") }}
- {{ link_to(title="Expats in Lisbon", url="https://www.facebook.com/groups/expatslisbon") }}
- {{ link_to(title="Expats in Algarve", url="https://www.facebook.com/groups/expatsinalgarve") }}
- {{ link_to(title="Danskere i Portugal", url="https://www.facebook.com/groups/danskereportugal") }} Her må jeg fremhæve, hvor venlige og hjælpsomme folk er!
### {{ link_to(title="Expat.com Portugal", url="https://www.expat.com/en/destination/europe/portugal/") }}
Expat.com tilbyder:
- {{ link_to(title="Praktiske guider og fora", url="https://www.expat.com/en/guide/europe/portugal/") }}.
- Netværksmuligheder med andre udlændinge i Portugal.
### Lokale netværksgrupper
Mange mindre fællesskaber blomstrer også op lokalt:
- {{ link_to(title="Lisbon Digital Nomads", url="https://www.facebook.com/groups/lisbondigitalnomads") }}: En gruppe for digitale nomader og remote arbejdere.
- {{ link_to(title="Porto Startup Scene", url="https://www.facebook.com/groups/portostartupscene") }}: Et netværk for iværksættere og tech-interesserede i **Porto**.
---
Portugal har noget for alle, og hvis du søger et fællesskab, vil du med sikkerhed finde det, der passer til dine behov.

View File

@@ -0,0 +1,26 @@
---
name: Fordele og ulemper ved at flytte til Portugal
description: De største fordele og ulemper ved at flytte til Portugal
author: Henrik Jess
date: tor 12 dec 01:10:00 CET 2024
summary: Lavere leveomkostninger, mildt klima og bureaukratiske udfordringer
favorite: false
image: images/pic01.jpg
category: Flytning
tags: [Portugal, Fordele, Ulemper, Leveomkostninger, Klima, Bureaukrati, Sprog]
---
# Hvad er fordelene og ulemperne ved at flytte til Portugal?
Jeg har samlet mine tanker og observationer om de største fordele og ulemper ved at flytte til Portugal, baseret på den research jeg har lavet.
### Fordele
- **Lavere leveomkostninger**: Sammenlignet med Danmark er det billigere at bo, spise og leve i Portugal. Det gælder især uden for storbyerne.
- **Mildt klima**: Med mange solskinsdage og milde vintre er vejret i Portugal et stort plus, især hvis man er træt af den danske kulde.
- **Frisk start og livsstil**: Portugal tilbyder en mere afslappet livsstil, som jeg tror, mange kunne have glæde af.
### Ulemper
- **Bureaukrati**: Der er mange historier om, at bureaukratiske processer kan være langsomme og forvirrende, især hvis man ikke taler portugisisk.
- **Sproglige barrierer**: Selvom mange portugisere taler engelsk i de større byer, kan det være en udfordring at klare sig i hverdagen uden at kunne sproget.
Portugal virker som et land med mange muligheder, men det kræver forberedelse og tålmodighed at få det hele til at fungere.

View File

@@ -0,0 +1,30 @@
---
name: Økonomiske konsekvenser ved at flytte til Portugal
description: Fordele og ulemper ved økonomi og skatteforhold i Portugal
author: Henrik Jess
date: tor 12 dec 01:20:00 CET 2024
summary: Lavere omkostninger og ændringer i skatteforhold ved flytning
favorite: false
image: images/pic01.jpg
category: Økonomi
tags: [Portugal, Økonomi, Skat, Leveomkostninger, Boligskatter, Danske ydelser]
---
# Hvilke økonomiske konsekvenser er der for mig ved at flytte til Portugal?
Jeg har gjort mig nogle tanker og undersøgelser om, hvad det økonomisk betyder at flytte til Portugal. Her er, hvad jeg er kommet frem til:
### Fordele
- **Lavere leveomkostninger**: De månedlige udgifter til bolig, mad og transport er typisk lavere end i Danmark, især hvis man vælger at bo uden for de største byer.
- **Skatteforhold**: Afhængigt af ens situation kan skatten være lavere i Portugal, men det kræver en vurdering af de portugisiske regler sammenlignet med de danske.
### Ulemper
- **Danske ydelser**: Hvis man fjerner sin danske adresse og bliver fuldt skattepligtig i Portugal, risikerer man at miste adgang til visse danske ydelser.
- **Boligskatter**: Selvom man kan spare danske boligskatter ved at fjerne sin adresse, skal man være opmærksom på de portugisiske skatter som **IMI** (ejendomsskat) og **IMT** (købsskat).
### Overvejelser
Hvis man planlægger at flytte, er det vigtigt at:
1. Undersøge de nøjagtige skatteforhold og indberetningskrav i Portugal.
2. Tage højde for, hvad det betyder at opgive sin danske bopæl, både økonomisk og administrativt.
Alt i alt ser det ud til, at Portugal kan give nogle økonomiske fordele, men det kræver planlægning og forståelse for, hvordan det påvirker skatten og andre økonomiske forhold.

View File

@@ -0,0 +1,33 @@
---
name: Uddannelsesmuligheder i Portugal for børn
description: Muligheder for gymnasier og internationale skoler i Portugal
author: Henrik Jess
date: tor 12 dec 01:15:00 CET 2024
summary: Internationale skoler og portugisiske gymnasier for børn
favorite: false
image: images/pic02.jpg
category: Uddannelse
tags: [Portugal, Uddannelse, Gymnasier, Internationale skoler, STX, Børn]
---
# Hvad er uddannelsesmulighederne i Portugal for mine børn?
Jeg har brugt tid på at undersøge, hvilke uddannelsesmuligheder der findes i Portugal, særligt fordi min datter på 16 år overvejer at tage en STX (studentereksamen) i Danmark. Her er, hvad jeg har fundet:
### Portugisiske gymnasier
Portugals gymnasiale uddannelser minder på mange måder om det danske system, men både pensum og undervisningssproget er naturligvis anderledes. Hvis man ikke taler portugisisk flydende, kan det være en udfordring, især i begyndelsen.
### Internationale skoler
Et oplagt alternativ er internationale skoler, der tilbyder undervisning på engelsk og ofte følger internationale curriculum som **International Baccalaureate (IB)**.
Nogle fordele ved internationale skoler inkluderer:
- Mulighed for at fortsætte på et globalt anerkendt pensum.
- God støtte til elever, der er nye i landet.
- Mange skoler har en stor blanding af elever fra forskellige nationaliteter.
### Overvejelser
Når man vælger mellem portugisiske gymnasier og internationale skoler, er det værd at tænke over:
- Sprogbarrierer og tilpasning til nyt pensum.
- Økonomien internationale skoler kan være dyrere.
- Hvilken uddannelsesretning der passer bedst til fremtidige planer.
Der er mange gode muligheder, men det kræver lidt research at finde det, der passer bedst til ens børn og familiens behov.

View File

@@ -0,0 +1,32 @@
---
name: Livsstilen i Portugal
description: Livsstil i Portugal med fokus på klima, kultur og dagligdag
author: Henrik Jess
date: tor 12 dec 00:10:00 CET 2024
summary: Et mildt klima, god mad og en afslappet atmosfære kendetegner Portugal
favorite: false
image: images/pic05.jpg
category: Kultur
tags: [Portugal, Livsstil, Klima, Mad, Vin, Udendørsaktiviteter]
---
# Hvordan er livsstilen i Portugal?
Livsstilen i Portugal er præget af et mildt klima, en afslappet atmosfære og overkommelige leveomkostninger især sammenlignet med Danmark.
### Klima og natur
Portugal byder på over 300 solskinsdage om året, hvilket giver rig mulighed for udendørsaktiviteter som:
- Vandreture i bjergområder som **Serra da Estrela**.
- Afslapning på de mange smukke strande langs **Algarvekysten**.
- Cykelture i det varierede landskab.
### Mad og vin
Mad og vin er en central del af den portugisiske kultur:
- Frisk fisk og skaldyr er en delikatesse langs kysten.
- Lokale vine, især fra **Douro-dalen**, er kendt verden over.
- Måltider nydes i et afslappet tempo, ofte sammen med familie og venner.
### Leveomkostninger og dagligdag
Portugal tilbyder en mere overkommelig hverdag, hvor pengene rækker længere, især uden for storbyerne. Samtidig er atmosfæren rolig og indbydende, hvilket gør det lettere at finde en god balance mellem arbejde og fritid.
Portugal er et land, hvor kultur, natur og en høj livskvalitet går hånd i hånd en ideel destination for dem, der søger en enklere og mere nydelsesfuld tilværelse.

View File

@@ -0,0 +1,28 @@
---
name: Priser på mad og dagligvarer i Portugal
description: Sammenligning af madpriser og dagligvarer mellem Portugal og Danmark
author: Henrik Jess
date: tor 12 dec 00:15:00 CET 2024
summary: Lokale produkter som grøntsager og fisk er billigere i Portugal
favorite: false
image: images/pic10.jpg
category: Leveomkostninger
tags: [Portugal, Mad, Dagligvarer, Priser, Lokale produkter, Importvarer]
---
# Hvordan er priserne på mad og dagligvarer i Portugal?
Priserne på mad og dagligvarer i Portugal er generelt lavere end i Danmark, især når det kommer til lokale produkter.
### Billigere lokale produkter
Portugal har et stort udbud af friske, lokalt producerede fødevarer, som er både billige og af høj kvalitet:
- **Grøntsager og frugt**: Friske råvarer som tomater, appelsiner og kartofler koster betydeligt mindre.
- **Fisk og skaldyr**: Som et kystland har Portugal et stort udvalg af friskfanget fisk til lave priser.
- **Vin og olivenolie**: Portugisiske vine og lokal olivenolie er meget overkommelige sammenlignet med danske priser.
### Dyrere importvarer
Visse importvarer og specialprodukter, som man måske er vant til i Danmark, kan være dyrere:
- Internationale mærkevarer.
- Specielle ingredienser, som ikke produceres lokalt.
Hvis man holder sig til lokale produkter, er madbudgettet i Portugal markant lavere end i Danmark. Det er nemt at spise godt, sundt og billigt i Portugal, især hvis man benytter sig af lokale markeder og små butikker frem for supermarkeder med mange importerede varer.

View File

@@ -0,0 +1,26 @@
---
name: Leveomkostninger i Portugal vs Danmark
description: Sammenligning af bolig, dagligvarer og transport mellem Portugal og Danmark
author: Henrik Jess
date: tor 12 dec 00:00:00 CET 2024
summary: Bolig, dagligvarer og transport er billigere i Portugal
favorite: false
image: images/pic09.jpg
category: Leveomkostninger
tags: [Portugal, Danmark, Leveomkostninger, Bolig, Dagligvarer, Transport]
---
# Er leveomkostningerne i Portugal lavere end i Danmark?
Alt peger på, at leveomkostningerne i Portugal er lavere sammenlignet med Danmark.
### Hvad er billigere i Portugal?
- **Bolig**: Udgifter til husleje og køb af bolig er væsentligt lavere, især uden for storbyer som **Lissabon** og **Porto**.
- **Dagligvarer**: Lokale fødevarer som grøntsager, fisk og vin er billigere og ofte af høj kvalitet.
- **Transport**: Offentlig transport som busser, metro og tog koster en brøkdel af, hvad det gør i Danmark.
### Hvad kan være dyrere?
- **Elregninger**: Elektricitet i Portugal kan være relativt dyrt, især i vinterperioden.
- **Importvarer**: Visse produkter, der importeres fra udlandet, kan være dyrere sammenlignet med Danmark.
Portugal tilbyder generelt en lavere leveomkostning, hvilket betyder, at man kan få mere for pengene især hvis man vælger at bosætte sig uden for de dyreste områder. Det gør landet til et oplagt valg, hvis man ønsker at kombinere økonomisk overskud med en høj livskvalitet.

View File

@@ -0,0 +1,29 @@
---
name: Omkostninger ved at bo i Portugal vs Danmark
description: Sammenligning af boligudgifter, mad og transport mellem de to lande
author: Henrik Jess
date: tor 12 dec 00:05:00 CET 2024
summary: Lavere boligudgifter, mad og transport i Portugal
favorite: false
image: images/pic06.jpg
category: Leveomkostninger
tags: [Portugal, Danmark, Bolig, Mad, Transport, Omkostninger]
---
# Hvad koster det at bo i Portugal sammenlignet med Danmark?
Portugal har generelt lavere leveomkostninger end Danmark, men der er forskelle afhængigt af, hvor man bosætter sig, og hvilken livsstil man vælger.
### Lavere boligudgifter
- Uden for storbyer som **Lissabon** og **Porto** er boligudgifterne markant lavere.
- Selvom boligpriserne i storbyerne stiger, er de stadig mere overkommelige sammenlignet med danske forhold.
### Mad og dagligvarer
- Lokale fødevarer som grøntsager, fisk og vin er billigere end i Danmark.
- Importvarer og specialprodukter kan dog være dyrere, afhængigt af region og butik.
### Transport
- Offentlig transport som metro, tog og busser er langt billigere og meget udbredt i Portugal.
- Brændstofpriserne er ofte på niveau med eller lidt højere end i Danmark.
Hvis du er åben for at bo uden for de dyreste områder og tilpasse din livsstil, er Portugal et økonomisk fordelagtigt sted at bo. Leveomkostningerne er lavere, og du får typisk mere for pengene, især når det gælder bolig og dagligvarer.

View File

@@ -0,0 +1,23 @@
---
name: Pendling mellem Danmark og Portugal
description: Overvejelser om rejseudgifter, arbejdstid og praktiske udfordringer ved pendling
author: Henrik Jess
date: tor 12 dec 01:25:00 CET 2024
summary: Rejseudgifter og tidsplanlægning ved pendling mellem Danmark og Portugal
favorite: false
image: images/pic05.jpg
category: Pendling
tags: [Portugal, Danmark, Pendling, Rejseudgifter, Arbejdstid, Fritid, Økonomi]
---
# Hvordan håndterer jeg pendling mellem Danmark og Portugal?
Mit mål er at arbejde i Danmark hver anden uge og bruge resten af tiden i Portugal. Det giver en unik mulighed for at kombinere arbejde med en ny livsstil, men det kræver også en del planlægning.
### Praktiske aspekter ved pendling
- **Rejseudgifter**: Flybilletter og lokal transport mellem hjem og arbejde kan hurtigt løbe op. Jeg planlægger at undersøge lavprisselskaber og tilbud for at holde omkostningerne nede.
- **Tidsplanlægning**: Det kræver nøje planlægning at balancere arbejdstid i Danmark med fritid og dagligdag i Portugal. Jeg arbejder på at skabe en fast rytme for at minimere stress.
- **Økonomiske konsekvenser**: Udgifter til rejser og ophold i Danmark skal vejes op mod de lavere leveomkostninger i Portugal.
### Overvejelser
Pendling mellem to lande er ikke kun praktisk krævende, men også en livsstilsændring. For mig handler det om at finde en balance, hvor rejseplaner ikke går ud over livskvaliteten, og hvor økonomien stadig hænger sammen. Det er vigtigt at have realistiske forventninger til, hvad pendling kræver, både fysisk og økonomisk.

View File

@@ -0,0 +1,30 @@
---
name: Nattelivet i Portugal
description: En guide til Portugals varierede og festlige natteliv
author: Henrik Jess
date: tor 12 dec 00:25:00 CET 2024
summary: Oplev barer, klubber og musikscener i Lissabon og Porto
favorite: false
image: images/pic04.jpg
category: Kultur
tags: [Portugal, Natteliv, Lissabon, Porto, Barer, Klub, Musik]
---
# Hvordan er nattelivet i Portugal?
Nattelivet i Portugal er både levende og mangfoldigt med noget for enhver smag. Særligt i byer som **Lissabon** og **Porto** er der rig mulighed for at opleve en festlig og international atmosfære.
### Barer og klubber
- **Lissabon**: Bairro Alto og Cais do Sodré er to af de mest populære områder. Bairro Alto er kendt for sine små, hyggelige barer, mens Cais do Sodré byder på større klubber som **Lux Fragil**, en af Portugals mest kendte natklubber.
- **Porto**: Området omkring Galerias de Paris er centrum for nattelivet i Porto, hvor barerne ligger tæt og festen fortsætter til langt ud på natten.
### Musik og live-scener
Portugal har en stærk musikscene med alt fra live-fado til moderne musik:
- **Fado i Lissabon**: Oplev den traditionelle fado-musik i en af byens intime fado-restauranter.
- **Koncerter**: Store koncertsteder i både Lissabon og Porto tiltrækker internationale kunstnere året rundt.
### Strandbarer og afslappede vibes
Langs kysten, især i Algarve, finder man strandbarer, hvor man kan nyde drinks med fødderne i sandet og en smuk solnedgang.
### Konklusion
Portugals natteliv har alt fra rolige barer og livemusik til energiske natklubber. Uanset om du vil danse natten væk i en storby eller nyde en cocktail med udsigt over havet, har Portugal noget at byde på.

View File

@@ -0,0 +1,76 @@
---
name: Portugals unikke kultur
description: Portugals særlige kulturarv med fokus på musik, mad og historie
author: Henrik Jess
date: tor 12 dec 00:20:00 CET 2024
summary: Portugals fado-musik, historie og madkultur gør landet unikt
favorite: false
image: img/pic04.jpg
category: Kultur
tags: [Portugal, Kultur, Fado, Musik, Historie, Mad, Monumenter]
---
# Hvad gør Portugal kulturelt unikt?
## Oplev Kulturlivet i Lissabon: Biografer, Teater, Mad og Historie
Lissabon er en by, der har det hele fra intime biografer og smukke teatre til gastronomiske oplevelser og storslåede historiske monumenter. Hvis du er vant til kulturlivet i København, vil du opdage både genkendelige tilbud og unikke oplevelser, som gør Lissabon til noget ganske særligt.
### En aften i biografen fra nostalgiske sale til moderne filmoplevelser
Ligesom i København, hvor du finder både små biografer som Grand Teatret og større kæder som Nordisk Film Biografer, tilbyder Lissabon noget for enhver filmelsker.
### En aften i biografen fra nostalgiske sale til moderne filmoplevelser
Ligesom i København, hvor du finder både små biografer som Grand Teatret og større kæder som Nordisk Film Biografer, tilbyder Lissabon noget for enhver filmelsker.
- **Cinemateca Portuguesa**: Hvis du elsker en biograf med sjæl, er Cinemateca stedet. Det minder lidt om Cinemateket i København, men her ligger fokus ofte på portugisiske og internationale klassikere. Biografen viser film fra hele verden og arrangerer ofte retrospektiver. [Se programmet her](https://www.cinemateca.pt/).
- **Cinema São Jorge**: Centralt placeret på Avenida da Liberdade og kendt for at være vært for filmfestivaler. Her får du følelsen af både kulturhus og biograf på én gang. Det kan minde om Park Bio i København med et bredt og kvalitetspræget udvalg af film. [Læs mere her](https://www.cinemasaojorge.pt/).
- **UCI El Corte Inglés**: Den moderne biografoplevelse med de nyeste blockbusters. Sammenligneligt med Palads i København, men med en lidt mere luksuriøs oplevelse i selve komplekset.
{{ slider(options={"width": 500, "height": 500}, images=["158170.jpg", "Fachada_SaoJorge.jpg"]) }}
Biografbesøg i Lissabon er en afslappet måde at tilbringe tid med familien. Jeg nyder selv at tage afsted med Erika for at dele både film og hygge.
### Teatrets verden: Fra klassikere til moderne eksperimenter
Hvor København har Det Kongelige Teater og mindre eksperimentelle scener som Husets Teater, finder du i Lissabon et lignende spektrum med tilbud, der rummer både det storslåede og det intime.
• Teatro Nacional D. Maria II: Byens mest ikoniske teater ligger smukt placeret på Rossio-pladsen. Bygningen er storslået på linje med Det Kongelige Teater i København og her opføres både portugisiske klassikere og internationale opsætninger. Læs mere her.
• Teatro São Luiz: Centralt og intimt, perfekt til en alsidig oplevelse. Her præsenteres teater, musik og dans for både voksne og familier. Det minder om Skuespilhuset med sit smukke interiør og blandede program. Læs mere her.
• Teatro Aberto: Kendt for at præsentere moderne og nyskabende forestillinger. Hvis du elsker små eksperimentelle teatre som Teater Grob, vil Teatro Aberto være et hit. Se mere her.
Teatrene i Lissabon gør det muligt at opleve portugisisk kultur og kreativitet på tæt hold ofte til meget overkommelige priser sammenlignet med København.
### Smagen af Portugal: Unikke gastronomiske oplevelser
Maden er uden tvivl noget af det, der adskiller Lissabon fra København. Her handler det om en kombination af friske råvarer, historiske traditioner og autentiske smagsoplevelser.
• Bacalhau: Den portugisiske nationalret tørret og saltet torsk serveres på utallige måder. I København er vi vant til stegt fisk og smørrebrød, men bacalhau er en unik oplevelse, der skal prøves.
• Pastéis de Nata: Lissabons berømte vaniljetærter kan ikke sammenlignes med noget i Danmark. Besøg Pastéis de Belém i Belém-kvarteret, hvor de originale opskrifter stadig holdes hemmelige. Læs mere her.
• Cervejaria Ramiro: Denne klassiske skaldyrsrestaurant er et paradis for fiskelskere. Tænk Fiskebaren i Kødbyen i København, men med endnu mere autentisk og rustik atmosfære. Læs mere her.
### Historiske højdepunkter: Lissabon versus København
Lissabon er fyldt med historie, og mens København byder på smukke steder som Rosenborg Slot og Nyhavn, har Lissabon sine egne historiske perler.
• Jerónimos-klosteret og Torre de Belém: To unikke UNESCO-steder, der vidner om Portugals storhedstid under opdagelsesrejserne. Intet i København kan helt sammenlignes med disse monumenter. Læs mere her.
• Castelo de São Jorge: Dette middelalderlige slot på toppen af byen giver en betagende udsigt. Det er lidt som at besøge Kronborg men med en helt anden sydlandsk atmosfære.
• Alfama-kvarteret: Oplev smalle brostensgader og historisk charme på en måde, som minder lidt om Christianshavn, men med mere farve og liv.
### Musikken: Den uundgåelige fado og moderne vibes
Musik spiller en central rolle i portugisisk kultur, og fado er noget, du ikke finder magen til i København. Fado er en melankolsk musikgenre, der ofte fremføres i intime restauranter og små scener i Alfama. Prøv steder som Clube de Fado eller O Faia.
For mere moderne musik kan du besøge Coliseu dos Recreios og Altice Arena, hvor både lokale og internationale kunstnere optræder året rundt.
### Livet i Lissabon: Kultur, sjæl og afslapning
Lissabon føles på mange måder som København med fokus på kultur, kvalitet og hygge men her tilføjes en sydlandsk lethed og varme. Uanset om du tager i biografen, nyder en teaterforestilling eller smager på den lokale mad, vil du opleve, at kulturen er lige så rig og mangfoldig som byen selv.
Samtidig er mange oplevelser billigere end i København, og her er en atmosfære, der er svær at finde nordpå: Lissabon har en unik blanding af tradition og modernitet, som gør den til en by, der bliver ved med at fascinere.
### Lidt billeder
{{ slider(options={"width": 500, "height": 500}, images=["158170.jpg", "Fachada_SaoJorge.jpg"]) }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 MiB

View File

@@ -0,0 +1,32 @@
---
name: Livskvalitet i Portugal
description: Høj livskvalitet i Portugal med sol, strande og en god balance mellem arbejde og fritid
author: Henrik Jess
date: tor 12 dec 00:35:00 CET 2024
summary: Sol, strande og en afslappet livsstil giver høj livskvalitet i Portugal
favorite: false
image: images/pic05.jpg
category: Livskvalitet
tags: [Portugal, Livskvalitet, Solskinsdage, Strande, Balance, Arbejde, Fritid]
---
# Hvorfor er livskvaliteten høj i Portugal?
Portugal tilbyder en livskvalitet, der kan være svær at matche andre steder i Europa. Det handler ikke kun om vejret, men også om den afslappede livsstil og mulighederne for at nyde livet.
### Over 300 solskinsdage om året
Med et mildt klima og mere end **300 solskinsdage** om året er vejret i Portugal en stor faktor for livskvaliteten. De lange solrige perioder skaber gode rammer for udendørsaktiviteter hele året.
### Smukke strande og natur
Portugal byder på nogle af de smukkeste strande i Europa, især langs **Algarvekysten** og **Costa Vicentina**. For naturelskere er der også:
- Vandreture i bjergene.
- Udforskning af nationalparker.
- Cykelruter gennem det varierede landskab.
### Balance mellem arbejde og fritid
Den portugisiske kultur lægger vægt på at nyde livet, og tempoet er generelt mere afslappet end i Danmark. Folk prioriterer tid med familie og venner, og det er lettere at finde en god balance mellem arbejde og fritid.
### Lavere leveomkostninger
Den høje livskvalitet forstærkes af, at pengene rækker længere. Mad, bolig og transport er billigere, hvilket giver mulighed for at leve komfortabelt uden at gå på kompromis med livsstilen.
Portugal kombinerer sol, smuk natur og en rolig livsstil med lavere leveomkostninger. Det skaber et miljø, hvor det er lettere at nyde livet og finde en sund balance mellem arbejde og fritid.

View File

@@ -0,0 +1,26 @@
---
name: Portugiserne som mennesker
description: Portugisernes venlighed og gæstfrihed gør det let at føle sig velkommen
author: Henrik Jess
date: tor 12 dec 00:30:00 CET 2024
summary: Venlige og imødekommende mennesker gør Portugal til et varmt sted at bo
favorite: false
image: images/pic02.jpg
category: Kultur
tags: [Portugal, Portugisere, Gæstfrihed, Venlighed, Tilflyttere, Kultur]
---
# Hvordan er portugiserne som mennesker?
En af de ting, der har gjort størst indtryk på mig under min research om Portugal, er portugisernes venlighed og gæstfrihed.
### Venlige og imødekommende
Portugiserne er kendt for deres rolige og varme personlighed. De møder både tilflyttere og turister med åbne arme, hvilket gør det langt lettere at føle sig velkommen i et nyt land.
### Hjælpsomme og nysgerrige
Jeg har bemærket, at portugiserne ofte er hjælpsomme og nysgerrige over for nye mennesker. Selvom ikke alle taler engelsk flydende, vil de gerne gøre en indsats for at hjælpe og forstå.
### Traditioner og fællesskab
Den portugisiske kultur lægger vægt på fællesskab og familie. Det kan ses i de lokale byfester, markeder og den afslappede måde, folk samles på caféer og restauranter. Det skaber en følelse af nærhed og samhørighed.
Hvis du overvejer at flytte til Portugal, kan du se frem til at møde mennesker, der er venlige, åbne og hjælpsomme. Portugisernes gæstfrihed gør det nemmere at skabe relationer og føle sig hjemme, selv som nytilflytter.

View File

@@ -0,0 +1,60 @@
---
name: Skattesystemet i Portugal
description: En guide til Portugals skattesystem, NHR-programmets afskaffelse og dobbeltbeskatning
author: Henrik Jess
date: tor 12 dec 00:40:00 CET 2024
summary: Information om Portugals skat, NHR-ordningens afskaffelse og lokale skatter
favorite: false
image: images/pic07.jpg
category: SKAT
tags: [Portugal, SKAT, NHR, Dobbeltbeskatning, IMI, IMT, Indkomstskat]
---
# En lille smule om SKAT
## Portugal og skattesystemet for tilflyttere
Portugal har længe været attraktivt for tilflyttere, særligt på grund af det tidligere **Non-Habitual Resident (NHR)**-skatteregime. Programmet blev introduceret i **2009** for at tiltrække højtkvalificerede arbejdstagere, pensionister og investorer med skattefordele i op til 10 år.
### NHR-programmet er afskaffet
I oktober 2024 blev NHR-programmet officielt afskaffet for nye ansøgere. For eksisterende deltagere fortsætter ordningen frem til det oprindelige udløb.
### Hvordan fungerede NHR?
NHR-ordningen tilbød:
- En **fast indkomstskat på 20%** for visse højtkvalificerede erhverv som IT-specialister, læger og ingeniører.
- Skattefritagelse på visse udenlandske indkomster afhængigt af dobbeltbeskatningsaftaler mellem Portugal og bopælslandet.
Regeringens beslutning om at afskaffe programmet kom som følge af bekymringer om økonomisk ulighed.
---
## Det portugisiske skattesystem
Portugal anvender et **progressivt skattesystem**:
- **14,5%** for årlige indkomster op til 7.479 EUR.
- Skatten stiger gradvist til en **toprate på 48%** for indkomster over 78.834 EUR.
Derudover skal man være opmærksom på lokale skatter:
- **IMI (ejendomsskat)**: Varierer mellem 0,3% og 0,45% af boligens værdi årligt.
- **IMT (købsskat)**: En engangsafgift, der betales ved køb af fast ejendom.
---
## Dobbeltbeskatning og skattemæssig bopæl
Danmark og Portugal har en **dobbeltbeskatningsaftale**, der sikrer, at man ikke betaler skat to gange. For at undgå dobbeltbeskatning kræves det:
1. At du dokumenterer din primære bopæl i Portugal (f.eks. ved at afmelde dig CPR-registeret i Danmark).
2. At du opgør, hvor du har din "tætteste økonomiske og personlige tilknytning".
Når du flytter permanent og ikke bevarer en dansk adresse, bliver du som udgangspunkt kun skattepligtig i Portugal. Dog gælder dansk kildeindkomst stadig, såsom udlejning af ejendomme eller udbytter.
---
## Fordele ved at flytte til Portugal
Udover skatteforholdene tilbyder Portugal:
- **Lavere leveomkostninger** sammenlignet med nordeuropæiske lande.
- Et **mildt klima** og høj livskvalitet.
- Et velfungerende sundheds- og uddannelsessystem, som tilflyttere også kan benytte.
Skattesystemet kræver, at man sætter sig grundigt ind i reglerne, men for mange er fordelene ved at bo i Portugal stadig til at få øje på.

View File

@@ -0,0 +1,41 @@
---
name: Internationale skoler i Porto
description: En guide til internationale skoler i Porto med britisk, fransk og tysk curriculum
author: Henrik Jess
date: tor 12 dec 01:30:00 CET 2024
summary: Oversigt over internationale skoler i Porto med fokus på curriculum og faciliteter
favorite: false
image: images/pic08.jpg
category: Uddannelse
tags: [Portugal, Porto, Skoler, International, British School, Lycée Français, Deutsche Schule]
---
# Hvilke skoler ser fede ud i Porto?
I min research har jeg fundet flere gode internationale skoler i Porto. De tilbyder forskellige curriculums og sprogkombinationer, som gør dem attraktive for tilflyttere med børn.
---
### Oporto British School
- **Adresse**: {{ link_to(title="Rua Cerca 326, 4150-201 Porto", url="http://www.obs.edu.pt") }}
- **Beskrivelse**: Grundlagt i **1894**, hvilket gør den til den ældste britiske skole i Portugal. Skolen tilbyder undervisning fra **3 til 18 år** og følger det britiske curriculum. For ældre elever tilbydes også **International Baccalaureate (IB) Diploma Programme**.
- **Fordele**: Godt ry, stærkt curriculum og en multikulturel elevbase.
---
### Lycée Français International de Porto
- **Adresse**: {{ link_to(title="Rua Gil Eanes, 27, Porto, 4150-348 Porto", url="https://www.lfip.pt") }}
- **Beskrivelse**: En fransk international skole, der tilbyder undervisning fra **børnehave** til **gymnasium**. Hovedsproget er fransk, men skolen tilbyder også undervisning i **engelsk** og **portugisisk**.
- **Fordele**: En stærk fransk uddannelsestradition og fokus på flere sprog. Skolen er ideel for fransktalende familier og dem, der ønsker en flersproget uddannelse.
---
### Externato Deutsche Schule Zu Porto
- **Adresse**: {{ link_to(title="Rua Guerra Junqueiro 162, 4150-386 Porto", url="http://www.dsporto.de") }}
- **Beskrivelse**: En tysk skole, der tilbyder undervisning fra **børnehave** til **gymnasium**. Hovedsproget er tysk, men eleverne får også undervisning i **engelsk** og **portugisisk**.
- **Fordele**: En høj standard inden for tysk uddannelse, som er kendt for sin struktur og kvalitet.
---
### Konklusion
Porto har flere internationale skoler af høj kvalitet, som imødekommer behovene hos tilflyttere. Valget afhænger af curriculum, sprog og ens egne prioriteter. Uanset om det er britisk, fransk eller tysk uddannelse, findes der gode muligheder i Porto.

View File

@@ -0,0 +1,26 @@
---
name: Klarer man sig på engelsk i Portugal?
description: Hvordan engelskkundskaber i Portugal gør det nemt for tilflyttere at kommunikere
author: Henrik Jess
date: tor 12 dec 00:45:00 CET 2024
summary: Mange portugisere taler engelsk, især i turistområder og blandt yngre generationer
favorite: false
image: images/pic06.jpg
category: Sprog
tags: [Portugal, Engelsk, Portugisisk, Sprog, Tilflyttere, Kommunikation]
---
# Kan jeg klare mig på engelsk i Portugal?
Under min research er det blevet tydeligt, at man som tilflytter godt kan klare sig på engelsk i Portugal, især i bestemte situationer og områder.
### Engelskkundskaber i turistområder
I storbyer som **Lissabon**, **Porto** og langs **Algarvekysten** taler mange portugisere engelsk, især i områder med mange turister. Restauranter, butikker og hoteller har ofte engelsktalende personale, hvilket gør hverdagen nemmere.
### Yngre generationer og professionelle
Blandt yngre portugisere og i erhvervslivet er engelskkundskaber generelt gode. Mange har lært engelsk i skolen, og i internationale virksomheder er det ofte arbejdssproget.
### Begrænset engelsk uden for byerne
Uden for de større byer og turistområder kan det dog være sværere at finde folk, der taler engelsk flydende. Her er det en klar fordel at kunne grundlæggende portugisisk til daglig kommunikation.
Selvom portugisisk er det officielle sprog, gør engelskkundskaberne i Portugal det muligt at klare sig i hverdagen som tilflytter. Dog er det en god idé at lære lidt portugisisk, hvis man planlægger at bosætte sig permanent, især hvis man ønsker at integrere sig i det lokale samfund.

View File

@@ -0,0 +1,34 @@
---
name: Sundhedssystemet i Portugal
description: En oversigt over Portugals offentlige og private sundhedsydelser for EU-borgere
author: Henrik Jess
date: tor 12 dec 00:50:00 CET 2024
summary: Portugals sundhedssystem tilbyder både offentlige tjenester og private alternativer
favorite: false
image: images/pic10.jpg
category: Sundhed
tags: [Portugal, Sundhedssystem, EHIC, Privat forsikring, Offentlige sundhedsydelser, EU-borgere]
---
# Hvad med sundhedssystemet?
En af de vigtigste overvejelser ved at flytte til Portugal er sundhedssystemet. Her tilbyder landet både offentlige og private sundhedsydelser, der dækker forskellige behov.
### Offentligt sundhedssystem
Som EU-borger har du ret til at bruge det portugisiske sundhedssystem ved hjælp af dit **EU-sygesikringskort (EHIC)**. Det offentlige system er generelt pålideligt og omfatter:
- Grundlæggende sundhedsydelser som lægebesøg, hospitalsbehandling og akutpleje.
- Lave brugerbetalinger for medicin og behandlinger sammenlignet med Danmark.
**Ulemper**: Ventetiden kan dog være lang, især hvis du har brug for specialister eller ikke-akutte behandlinger.
### Privat sundhedssystem
Mange tilflyttere vælger at tegne **privat sundhedsforsikring** for at få hurtigere adgang til behandling og specialister. Det private system er ofte mere fleksibelt og har kortere ventetider, men koster naturligvis ekstra.
**Fordele**:
- Hurtigere adgang til specialister.
- Moderne faciliteter og engelsktalende personale på private klinikker og hospitaler.
### Kombination af offentligt og privat
Nogle vælger en kombination, hvor de benytter det offentlige system til dagligdags sundhedsbehov og tegner en privat forsikring til mere specialiserede ydelser.
Sundhedssystemet i Portugal er både tilgængeligt og pålideligt for EU-borgere. Det offentlige system fungerer godt til de fleste behov, men privat forsikring er en god løsning, hvis man ønsker kortere ventetider og mere fleksibilitet.

48
data/Tech/apps.md Normal file
View File

@@ -0,0 +1,48 @@
---
name: Bedste apps til expats i Portugal
description: En oversigt over nyttige apps, der gør hverdagen lettere for expats i Portugal
author: Henrik Jess
date: tor 12 dec 00:55:00 CET 2024
summary: De 9 mest anbefalede apps til transport, kommunikation og praktisk hjælp i Portugal
favorite: false
image: images/pic07.jpg
category: Apps og Teknologi
tags: [Portugal, Apps, Expat, Teknologi, Dagligdag, Transport, Kommunikation]
---
# 9 bedste apps til expats i Portugal
Hvis du planlægger at bo i Portugal som expat, er der en række apps, der kan gøre din hverdag langt nemmere. Her er mine anbefalinger:
---
1. **Google Maps**
Perfekt til navigation og offentlig transport. Google Maps giver realtidsinformation om ruter, trafik og transportmidler i byer som Lissabon, Porto og Algarve.
2. **Uber/Bolt**
To af de mest populære apps til nem og billig transport. Både Uber og Bolt er udbredt i Portugal og giver et godt alternativ til taxaer.
3. **MB Way**
En uundværlig app til mobile betalinger og hurtig håndtering af portugisiske bankkonti. MB Way bruges bredt i butikker og til pengeoverførsler.
4. **Idealista**
En af de bedste apps og hjemmesider til **boligsøgning** i Portugal. Den er overskuelig og fungerer godt, både til køb og leje af bolig.
5. **Duolingo**
Har du brug for at lære portugisisk? Duolingo er en effektiv og sjov måde at lære sproget hurtigt, hvilket er en stor hjælp i hverdagen.
6. **Meetup**
Find lokale grupper og sociale begivenheder, der matcher dine interesser. Perfekt til at møde nye mennesker og netværke som expat.
7. **Internations**
En global platform for expats, hvor du kan deltage i netværksarrangementer og møde ligesindede.
8. **Revolut/Wise**
To fremragende apps til **international bankoverførsel** og håndtering af valuta. De gør det nemt at sende penge mellem lande og undgå høje gebyrer.
9. **WhatsApp**
Meget udbredt i Portugal til både lokal og international kommunikation. De fleste portugisere og expats bruger WhatsApp til beskeder og opkald.
---
Disse apps dækker alt fra transport og økonomi til sprog og sociale aktiviteter. Hvis du som expat lærer at bruge dem, vil du hurtigt opleve, hvor meget lettere det bliver at navigere i din nye hverdag i Portugal.

View File

@@ -0,0 +1,49 @@
---
name: Flyselskaber mellem Lissabon og København
description: Direkte flyselskaber og rejsetid mellem Lissabon og København
author: Henrik Jess
date: tor 12 dec 01:05:00 CET 2024
summary: Direkte fly mellem Lissabon og København med TAP, SAS, easyJet og Norwegian
favorite: false
image: images/pic11.jpg
category: Transport
tags: [Portugal, København, Lissabon, Flyselskaber, TAP, SAS, Norwegian, easyJet]
---
# Flyselskaber mellem Lissabon og København
Hvis du planlægger at pendle eller rejse mellem **Lissabon (LIS)** og **København (CPH)**, er der flere gode muligheder for direkte flyforbindelser.
---
### Direkte flyselskaber
Flere flyselskaber tilbyder direkte fly på ruten:
- **TAP Air Portugal**
Portugals nationale flyselskab med regelmæssige afgange og ofte god service på ruten.
- **Scandinavian Airlines (SAS)**
Et oplagt valg for skandinaviske rejsende med direkte forbindelser mellem Lissabon og København.
- **easyJet**
Lavprisselskabet easyJet opererer direkte flyvninger, hvilket gør det til en økonomisk mulighed.
- **Norwegian Air International**
Et andet budgetvenligt alternativ med direkte fly mellem de to byer.
---
### Rejseinformation
- **Antal ugentlige flyvninger**: Ca. **106 flyvninger** om ugen mellem Lissabon og København.
- **Flyvetid**: Den gennemsnitlige flyvetid er ca. **3 timer og 42 minutter**.
---
### Tips til booking
For at finde de bedste priser og afgange anbefaler jeg at bruge populære søgemaskiner som:
- **{{ link_to(title="Skyscanner", url="https://www.skyscanner.dk") }}**
- **{{ link_to(title="Momondo", url="https://www.momondo.dk") }}**
Det kan være en god idé at sammenligne priser på tværs af platforme og holde øje med kampagner fra de nævnte flyselskaber.
✈️ **God rejse!**

View File

@@ -0,0 +1,48 @@
---
name: Er transport billigere i Portugal?
description: En sammenligning af transportomkostninger og nyttige rejseapps i Portugal
author: Henrik Jess
date: tor 12 dec 01:00:00 CET 2024
summary: Offentlig transport er billigere i Portugal med flere apps til nem rejseplanlægning
favorite: false
image: images/pic08.jpg
category: Transport
tags: [Portugal, Transport, Apps, Offentlig transport, Tog, Bus, Rejseplanlægning]
---
# Er transport billigere i Portugal?
Ja, offentlig transport i Portugal, såsom busser, metro og tog, er betydeligt billigere end i Danmark. For eksempel koster en månedlig billet i storbyer som Lissabon typisk langt mindre end en tilsvarende billet i København. **Benzinpriserne** er dog ofte på niveau med eller højere end i Danmark, hvilket kan påvirke udgifterne til biltransport.
---
## Apps til rejseplanlægning i Portugal
For at gøre det nemt at navigere i Portugals offentlige transportsystem findes der flere gode apps. De fungerer som den portugisiske udgave af **Rejseplanen** og hjælper dig med at planlægge ture med bus, metro, tog og mere.
---
### {{ link_to(title="Moovit", url="https://moovitapp.com/portugal") }}
- **Beskrivelse**: En alsidig transportapp, der fungerer i de fleste portugisiske byer og regioner, såsom Lissabon, Porto, Portimão og Funchal.
- **Funktioner**: Planlæg ruter med flere transportmidler (gang, cykling, bus, sporvogn), og få realtidsopdateringer om afgangstider. Perfekt til at navigere som en lokal.
---
### {{ link_to(title="CP Comboios de Portugal", url="https://www.cp.pt/passageiros/pt/apps") }}
- **Beskrivelse**: Den officielle app fra Portugals statsbaner (**CP**) er ideel, hvis du ofte rejser med tog.
- **Funktioner**:
- Køb togbilletter direkte i appen.
- Planlæg rejser og se opdaterede køreplaner.
- Få rabatter og information om forsinkelser.
---
### {{ link_to(title="Carris", url="https://www.carris.pt/en/") }}
- **Beskrivelse**: Din guide til **Lissabons** offentlige transportsystem, herunder busser og sporvogne.
- **Funktioner**:
- Planlæg dine ruter og køb billetter digitalt.
- Modtag opdateringer om driftsstatus og afgangstider.
- Tilbyder nem adgang til kundeservice.
---
Med billigere priser på offentlig transport og nyttige apps som Moovit, CP og Carris er det let og økonomisk at rejse rundt i Portugal. For pendlere og rejsende er disse apps uundværlige værktøjer til at planlægge ture og spare tid.

View File

@@ -0,0 +1,22 @@
---
name: Fordele ved offentlig transport i Portugal
description: Effektivitet og økonomi ved Portugals offentlige transport
author: Henrik Jess
date: ons 11 dec 23:45:00 CET 2024
summary: Offentlig transport er billig, effektiv og landsdækkende
favorite: false
image: images/pic10.jpg
category: Transport
tags: [Portugal, Offentlig transport, Tog, Metro, Bus, Lissabon, Porto]
---
# Hvilke fordele er der ved at bruge offentlig transport i Portugal?
Portugal har et veludviklet og økonomisk overkommeligt offentligt transportsystem, især i større byer som **Lissabon** og **Porto**.
### Fordele ved offentlig transport:
- **Pris**: Offentlig transport i Portugal er langt billigere end i Danmark. Det gælder både togrejser og bytransport som metro og busser.
- **Effektivitet**: Byernes metrosystemer er moderne og punktlige, mens tog forbinder store dele af landet effektivt.
- **Dækning**: Togene forbinder de fleste regioner, og i byerne findes der gode alternativer som sporvogne, busser og metro.
Hvis du planlægger at bosætte dig i Portugal, er det værd at overveje offentlig transport som en praktisk og økonomisk løsning til daglig transport.

View File

@@ -0,0 +1,18 @@
# Min Drøm om Portugal
## En Frisk Start for Henrik og Erika
{{ image('thumbnails', 'pic11.jpg', alt='Mit fantatiske billed') }}
---
Portugal er blevet symbolet på den nye begyndelse, jeg drømmer om en mulighed for at skabe et liv med mere ro, plads og mening. Som Henrik, far til min 16-årige datter Erika, har tanken om at flytte fra Danmark til Portugal vokset sig stærkere de seneste år. Det er ikke bare et ønske om sol og varme, men om en hverdag, hvor livskvalitet betyder mere end stress og travlhed.
Drømmen er konkret: en hverdag med tid til både arbejde og familieliv, omgivet af Portugals smukke landskaber og venlige kultur. Med Lissabon som udgangspunkt forestiller jeg mig et liv, hvor jeg arbejder struktureret, pendler til Danmark i de lige uger og tilbringer de øvrige uger i vores nye hjem. Det handler ikke om at efterlade alt bag sig, men om at finde en bedre balance mellem mine forpligtelser i Danmark og en ny tilværelse, hvor solen skinner lidt længere.
For Erika er det også en chance for at opleve en ny kultur, møde nye mennesker og måske starte på en ungdomsuddannelse i Portugal. Vi ved begge, at det ikke bliver uden udfordringer med praktiske overvejelser som bolig, skat, pendling og skolegang men fordelene vejer tungere. Leveomkostningerne er lavere, menneskerne er imødekommende, og tempoet giver plads til at trække vejret.
Denne hjemmeside er en samling af mine tanker, forberedelser og erfaringer på vejen mod at gøre drømmen til virkelighed. Måske overvejer du selv en lignende rejse, eller måske er du bare nysgerrig på, hvordan det er at tage springet. Her deler jeg min rejse fra de første spæde idéer til de praktiske skridt mod et nyt liv i Portugal. For mig og for Erika.
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1 +0,0 @@
<P> Lidt om Bolig </P>

View File

@@ -1,7 +0,0 @@
<h1>BoligBolig Bolig Bolig - Hvor skal sengen placeres</h1>
<p>Nu bliver det spænde!</p>
<p>
<div class="note">
<p>Dette er stadig en test side</p>
</div>
</p>

View File

@@ -1,5 +0,0 @@
# BoligBolig Bolig Bolig - Hvor skal sengen placeres
Nu bliver det spænde!
{{ note("Dette er stadig en test side") }}

View File

@@ -1,7 +0,0 @@
<h1>Lidt mere info om job</h1>
<p>Der skal langt mere tekst her</p>
<p>
<div class="note">
<p>Husk alpha side</p>
</div>
</p>

View File

@@ -1,6 +0,0 @@
# Lidt mere info om job
Der skal langt mere tekst her
{{ note("Husk alpha side") }}

View File

@@ -1 +0,0 @@
<P> Lidt om job </P>

View File

@@ -1 +0,0 @@
<P> Lidt om skat </P>

View File

@@ -1 +0,0 @@
<P> Lidt om skole </P>

97
generated_data.json Normal file
View File

@@ -0,0 +1,97 @@
{
"categories": [
{
"name": "Arbejde i Portugal",
"path": "Arbejde",
"author": "Henrik Jess"
},
{
"name": "Bolig i Portugal",
"path": "Bolig",
"author": "Henrik Jess"
},
{
"name": "El- og vandregninger i Portugal: Sådan påvirker de leveomkostningerne",
"path": "Budget",
"author": "Henrik Jess"
},
{
"name": "Permanent ophold i Portugal",
"path": "Flytning",
"author": "Henrik Jess"
},
{
"name": "Fritidsaktiviteter i Portugal",
"path": "Fritid",
"author": "Henrik Jess"
},
{
"name": "Expat-fællesskaber i Portugal",
"path": "Fællesskab",
"author": "Henrik Jess"
},
{
"name": "Uddannelsesmuligheder i Portugal for børn",
"path": "General",
"author": "Henrik Jess"
},
{
"name": "Omkostninger ved at bo i Portugal vs Danmark",
"path": "Hverdag",
"author": "Henrik Jess"
},
{
"name": "Pendling mellem Danmark og Portugal",
"path": "Job",
"author": "Henrik Jess"
},
{
"name": "Portugals unikke kultur",
"path": "Kultur",
"author": "Henrik Jess"
},
{
"name": "Livskvalitet i Portugal",
"path": "Livskvalitet",
"author": "Henrik Jess"
},
{
"name": "Skattesystemet i Portugal",
"path": "Skat",
"author": "Henrik Jess"
},
{
"name": "Internationale skoler i Porto",
"path": "Skole",
"author": "Henrik Jess"
},
{
"name": "Klarer man sig på engelsk i Portugal?",
"path": "Sprog",
"author": "Henrik Jess"
},
{
"name": "Sundhedssystemet i Portugal",
"path": "Sundhed",
"author": "Henrik Jess"
},
{
"name": "Bedste apps til expats i Portugal",
"path": "Tech",
"author": "Henrik Jess"
},
{
"name": "Er transport billigere i Portugal?",
"path": "Transport",
"author": "Henrik Jess"
}
],
"favorites": [
{
"name": "Budget - Indkøb",
"image": "images/budget2.jpg",
"description": "Fødevarer er markant billigere i Portugal med få undtagelser som bær og specialvarer.",
"path": "Budget"
}
]
}

BIN
html5up-parallelism.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,63 @@
Creative Commons Attribution 3.0 Unported
http://creativecommons.org/licenses/by/3.0/
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
1. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
2. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
3. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
4. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
5. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
6. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
7. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
8. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
9. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
1. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
2. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
3. to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
4. to Distribute and Publicly Perform Adaptations.
5.
For the avoidance of doubt:
1. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
2. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
3. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
1. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested.
2. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
3. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
1. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
2. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous
1. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
2. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
3. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
4. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
5. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
6. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.

View File

@@ -0,0 +1,46 @@
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
It's done! Parallelism was an idea I mocked up a few months ago but, due to other
committments, didn't get around to putting to code until recently. Different to
my more general-purpose freebies, this one's specifically built for portfolio use
with a very unique look/layout. Hope you enjoy it!
Demo images* are courtesy of the supremely talented photographer Felicia Simion.
If you like photography or just enjoy being blown away by awesome stuff, check
out her portfolio for more stunning images:
http://ineedchemicalx.deviantart.com/
(* = Not included! Only meant for use with my own on-site demo, so please do NOT download
and/or use any of Felicia's work without her explicit permission!)
Feedback, bug reports, and comments are not only welcome, but strongly encouraged :)
AJ
aj@lkn.io | @ajlkn
Credits:
Demo Images:
Felicia Simion (ineedchemicalx.deviantart.com)
"Pasadena" (ineedchemicalx.deviantart.com/art/Pasadena-357650036)
"Your really got me" (ineedchemicalx.deviantart.com/art/You-really-got-me-345249340)
"Ad Infinitum" (ineedchemicalx.deviantart.com/art/Ad-infinitum-354203162)
"Different." (ineedchemicalx.deviantart.com/art/Different-353708988)
"Elysium" (ineedchemicalx.deviantart.com/art/Elysium-355393900)
"Kingdom of the Wind" (ineedchemicalx.deviantart.com/art/Kingdom-of-the-Wind-348268044)
"The Pursuit" (ineedchemicalx.deviantart.com/art/The-Pursuit-355003425)
"Boundless" (ineedchemicalx.deviantart.com/art/Boundless-291831118)
"The Spectators" (ineedchemicalx.deviantart.com/art/The-Spectators-342155982)
Icons:
Font Awesome (fontawesome.io)
Other:
jQuery (jquery.com)
normalizeWheel (@miorel + @pieterv of Facebook)
Responsive Tools (github.com/ajlkn/responsive-tools)

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,833 @@
@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600");
@import url("fontawesome-all.min.css");
/*
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
html, body, div, span, applet, object,
iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, a, abbr, acronym, address, big, cite,
code, del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var, b,
u, i, center, dl, dt, dd, ol, ul, li, fieldset,
form, label, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside,
canvas, details, embed, figure, figcaption,
footer, header, hgroup, menu, nav, output, ruby,
section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after, q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
-webkit-text-size-adjust: none;
}
mark {
background-color: transparent;
color: inherit;
}
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input, select, textarea {
-moz-appearance: none;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
}
/* Basic */
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
background-image: url("images/overlay.png"), -moz-linear-gradient(top, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0.65)), url("../../images/bg.jpg");
background-image: url("images/overlay.png"), -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0.65)), url("../../images/bg.jpg");
background-image: url("images/overlay.png"), -ms-linear-gradient(top, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0.65)), url("../../images/bg.jpg");
background-image: url("images/overlay.png"), linear-gradient(top, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0.65)), url("../../images/bg.jpg");
background-position: top left, bottom left, auto;
background-size: auto, 100% 100%, cover;
background-attachment: fixed;
background-repeat: repeat, no-repeat, auto;
position: relative;
background-color: #150C07;
line-height: 1.75em;
overflow-x: hidden;
overflow-y: auto;
}
body.is-preload *, body.is-preload *:before, body.is-preload *:after {
-moz-animation: none !important;
-webkit-animation: none !important;
-ms-animation: none !important;
animation: none !important;
-moz-transition: none !important;
-webkit-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}
@media screen and (max-width: 1280px) {
body {
background-attachment: scroll;
}
}
@media screen and (max-width: 736px) {
body {
background-attachment: scroll;
background-size: auto, 100% 100%, 250% auto;
background-repeat: repeat, no-repeat, no-repeat;
background-position: top left, bottom left, 50% 0%;
}
}
body, input, select, textarea {
font-family: 'Source Sans Pro';
font-weight: 400;
padding: 0;
font-size: 13pt;
color: rgba(255, 255, 255, 0.6);
}
@media screen and (max-width: 1680px) {
body, input, select, textarea {
font-size: 11pt;
}
}
@media screen and (max-width: 1280px) {
body, input, select, textarea {
font-size: 10pt;
}
}
@media screen and (max-width: 980px) {
body, input, select, textarea {
font-size: 11pt;
}
}
a {
color: #fff;
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
-moz-transition: color 0.25s ease-in-out;
-webkit-transition: color 0.25s ease-in-out;
-ms-transition: color 0.25s ease-in-out;
transition: color 0.25s ease-in-out;
}
a:hover {
color: #d64760;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 400;
color: #fff;
letter-spacing: -0.05em;
}
strong, b {
color: #fff;
color: rgba(255, 255, 255, 0.85);
font-weight: 600;
}
/* List */
ul.icons {
cursor: default;
margin: 0 0 0.5em 0;
}
ul.icons li {
display: inline-block;
font-size: 1.5em;
margin-left: 1em;
}
ul.icons li span {
display: none;
}
ul.icons li a {
opacity: 0.35;
color: #fff;
-moz-transition: opacity 0.25s ease-in-out;
-webkit-transition: opacity 0.25s ease-in-out;
-ms-transition: opacity 0.25s ease-in-out;
transition: opacity 0.25s ease-in-out;
}
ul.icons li a:hover {
opacity: 1.0;
}
ul.icons li:first-child {
margin-left: 0;
}
@media screen and (max-width: 736px) {
ul.icons {
margin: 0 0 2em 0;
}
}
/* Icons */
.icon {
text-decoration: none;
position: relative;
text-decoration: none;
}
.icon:before {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
font-family: 'Font Awesome 5 Free';
font-weight: 400;
}
.icon:before {
line-height: inherit;
}
.icon > .label {
display: none;
}
.icon.solid:before {
font-weight: 900;
}
.icon.brands:before {
font-family: 'Font Awesome 5 Brands';
}
/* Wrapper */
#wrapper {
min-height: 100vh;
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
-moz-flex-direction: column;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-moz-justify-content: space-between;
-webkit-justify-content: space-between;
-ms-justify-content: space-between;
justify-content: space-between;
-moz-align-items: center;
-webkit-align-items: center;
-ms-align-items: center;
align-items: center;
-moz-transition: -moz-filter 0.5s ease-in-out;
-webkit-transition: -webkit-filter 0.5s ease-in-out;
-ms-transition: -ms-filter 0.5s ease-in-out;
transition: filter 0.5s ease-in-out;
}
#wrapper:before {
content: '';
display: block;
}
body.is-poptrox-visible #wrapper {
-moz-filter: blur(0.25em);
-webkit-filter: blur(0.25em);
-ms-filter: blur(0.25em);
filter: blur(0.25em);
}
@media screen and (max-width: 736px) {
#wrapper {
min-height: 0;
}
}
/* Scroll Zone */
.scrollZone {
position: fixed;
width: 6rem;
height: 100vh;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: -ms-grab;
cursor: grab;
z-index: 10001;
}
.scrollZone.left {
left: 0;
}
.scrollZone.right {
right: 0;
}
@media screen and (max-width: 736px) {
.scrollZone {
display: none;
}
}
/* Main */
#main {
-moz-transition: opacity 1s ease-in-out;
-webkit-transition: opacity 1s ease-in-out;
-ms-transition: opacity 1s ease-in-out;
transition: opacity 1s ease-in-out;
-moz-transition-delay: 0.5s;
-webkit-transition-delay: 0.5s;
-ms-transition-delay: 0.5s;
transition-delay: 0.5s;
position: relative;
z-index: 1;
width: -moz-min-content;
width: -webkit-min-content;
width: -ms-min-content;
width: min-content;
max-width: 100vw;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
padding: 5px;
}
#main .items {
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
}
#main .items > :last-child {
border-right: solid 10px #ffffff;
}
#main .item {
-moz-flex-grow: 0;
-webkit-flex-grow: 0;
-ms-flex-grow: 0;
flex-grow: 0;
-moz-flex-shrink: 0;
-webkit-flex-shrink: 0;
-ms-flex-shrink: 0;
flex-shrink: 0;
margin: 5px;
height: 16em;
box-shadow: 0 0 0 10px #ffffff;
}
#main .item.span-1 {
width: 20em;
}
#main .item.span-2 {
width: 30em;
}
#main .item.span-3 {
width: 40em;
}
#main .item.intro {
background-color: #d64760;
background-image: url("images/overlay.png");
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
-moz-flex-direction: column;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-moz-justify-content: center;
-webkit-justify-content: center;
-ms-justify-content: center;
justify-content: center;
padding: 1em 3em;
}
#main .item.intro h1 {
font-size: 3em;
line-height: 1em;
}
#main .item.intro p {
font-size: 1.25em;
line-height: 1.5em;
margin: 0.5em 0 0 0;
opacity: 0.65;
}
#main .item.thumb {
display: block;
position: relative;
background: rgba(255, 255, 255, 0.25);
cursor: default;
}
#main .item.thumb h2 {
position: absolute;
bottom: 0;
left: 0;
background: rgba(18, 21, 29, 0.85);
width: 100%;
padding: 1em;
font-weight: 400;
line-height: 1em;
z-index: 2;
}
#main .item.thumb img {
display: block;
z-index: 1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
-moz-object-fit: cover;
-webkit-object-fit: cover;
-ms-object-fit: cover;
object-fit: cover;
-moz-object-position: center;
-webkit-object-position: center;
-ms-object-position: center;
object-position: center;
-moz-transition: opacity 0.75s ease-in-out;
-webkit-transition: opacity 0.75s ease-in-out;
-ms-transition: opacity 0.75s ease-in-out;
transition: opacity 0.75s ease-in-out;
-moz-transition-delay: 1.25s;
-webkit-transition-delay: 1.25s;
-ms-transition-delay: 1.25s;
transition-delay: 1.25s;
}
#main .item.thumb.delay-1 img {
-moz-transition-delay: 1.375s;
-webkit-transition-delay: 1.375s;
-ms-transition-delay: 1.375s;
transition-delay: 1.375s;
}
#main .item.thumb.delay-2 img {
-moz-transition-delay: 1.5s;
-webkit-transition-delay: 1.5s;
-ms-transition-delay: 1.5s;
transition-delay: 1.5s;
}
#main .item.thumb.delay-3 img {
-moz-transition-delay: 1.625s;
-webkit-transition-delay: 1.625s;
-ms-transition-delay: 1.625s;
transition-delay: 1.625s;
}
#main .item.thumb.delay-4 img {
-moz-transition-delay: 1.75s;
-webkit-transition-delay: 1.75s;
-ms-transition-delay: 1.75s;
transition-delay: 1.75s;
}
#main .item.thumb.delay-5 img {
-moz-transition-delay: 1.875s;
-webkit-transition-delay: 1.875s;
-ms-transition-delay: 1.875s;
transition-delay: 1.875s;
}
#main .item.thumb.delay-6 img {
-moz-transition-delay: 2s;
-webkit-transition-delay: 2s;
-ms-transition-delay: 2s;
transition-delay: 2s;
}
body.is-preload #main {
opacity: 0;
}
body.is-preload #main .item.thumb img {
opacity: 0;
}
@media screen and (max-width: 1680px) {
#main .item {
height: 14em;
}
#main .item.span-1 {
width: 17em;
}
#main .item.span-2 {
width: 25.5em;
}
#main .item.span-3 {
width: 34em;
}
}
@media screen and (max-width: 736px) {
#main {
width: 100%;
overflow-x: hidden;
overflow-y: auto;
padding: 2.5px;
}
#main .items {
-moz-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-moz-justify-content: center;
-webkit-justify-content: center;
-ms-justify-content: center;
justify-content: center;
}
#main .items > :last-child {
border-right: 0;
}
#main .item {
-moz-flex-grow: 1;
-webkit-flex-grow: 1;
-ms-flex-grow: 1;
flex-grow: 1;
-moz-flex-shrink: 1;
-webkit-flex-shrink: 1;
-ms-flex-shrink: 1;
flex-shrink: 1;
width: calc(50vw - 15px) !important;
margin: 2.5px;
box-shadow: 0 0 0 5px #ffffff;
}
#main .item.intro {
width: calc(100vw - 20px) !important;
text-align: center;
height: auto;
padding: 3em;
}
#main .item.intro h1 {
font-size: 2em;
}
#main .item.intro p {
font-size: 1em;
}
#main .item.thumb h2 {
display: none;
}
}
@media screen and (max-width: 480px) {
#main .item {
height: 10em;
}
}
/* Header */
#footer {
width: 100%;
padding: 1.5em;
display: -moz-flex;
display: -webkit-flex;
display: -ms-flex;
display: flex;
-moz-justify-content: space-between;
-webkit-justify-content: space-between;
-ms-justify-content: space-between;
justify-content: space-between;
position: relative;
z-index: 10002;
}
#footer > section:first-child {
text-align: left;
max-width: 36em;
padding-right: 2em;
}
#footer > section:last-child {
text-align: right;
max-width: 36em;
}
#footer h2 {
font-size: 2.25em;
margin: 0 0 1em 0;
}
#footer .copyright li {
display: inline-block;
margin-left: 1em;
padding-left: 1em;
border-left: solid 1px rgba(255, 255, 255, 0.25);
line-height: 1em;
}
#footer .copyright li:first-child {
border-left: 0;
margin-left: 0;
padding-left: 0;
}
@media screen and (max-width: 980px) {
#footer {
-moz-flex-direction: column;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
#footer > section:first-child {
width: 100%;
padding-right: 0;
}
#footer > section:last-child {
text-align: left;
margin: 1.5em 0 0 0;
width: 100%;
}
}
@media screen and (max-width: 736px) {
#footer {
text-align: center;
padding: 3em;
}
#footer > section:first-child {
text-align: center;
max-width: 100%;
}
#footer > section:last-child {
text-align: center;
max-width: 100%;
}
#footer .copyright li {
display: block;
margin: 0.75em 0 0 0;
padding-left: 0;
border-left: 0;
}
#footer .copyright li:first-child {
margin-top: 0;
}
}
/* Popup */
.poptrox-popup {
background: #1a1f2c;
background: rgba(18, 21, 29, 0.9);
box-shadow: 0px 0px 0px 10px #fff, 0px 10px 60px 10px rgba(8, 11, 19, 0.55);
cursor: default;
}
.poptrox-popup .loader {
display: block;
width: 48px;
height: 48px;
position: absolute;
top: 50%;
left: 50%;
margin: -24px 0 0 -24px;
background: url("images/loader.gif");
opacity: 0.25;
}
.poptrox-popup .caption {
position: absolute;
bottom: 0;
left: 0;
background: #1a1f2c;
background: rgba(18, 21, 29, 0.85);
display: block;
width: 100%;
line-height: 75px;
text-align: center;
font-size: 1.25em;
color: #fff;
}
.poptrox-popup .nav-next, .poptrox-popup .nav-previous {
text-decoration: none;
font-weight: normal;
font-style: normal;
-moz-transition: opacity 0.25s ease-in-out;
-webkit-transition: opacity 0.25s ease-in-out;
-ms-transition: opacity 0.25s ease-in-out;
transition: opacity 0.25s ease-in-out;
opacity: 0.35;
}
.poptrox-popup .nav-next:hover, .poptrox-popup .nav-previous:hover {
opacity: 1.0;
}
.poptrox-popup .nav-next, .poptrox-popup .nav-previous {
text-decoration: none;
text-transform: none !important;
width: 150px;
height: 75px;
position: absolute;
bottom: 0;
cursor: pointer;
font-size: 3em;
line-height: 75px;
}
.poptrox-popup .nav-next:before, .poptrox-popup .nav-previous:before {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
.poptrox-popup .nav-next {
right: 0;
text-align: right;
padding-right: 30px;
}
.poptrox-popup .nav-next:before {
content: '\f105';
}
.poptrox-popup .nav-previous {
left: 0;
text-align: left;
padding-left: 30px;
}
.poptrox-popup .nav-previous:before {
content: '\f104';
}
@media screen and (max-width: 736px) {
.poptrox-popup {
background: #0a0f1c;
box-shadow: 0px 0px 30px 10px rgba(8, 11, 19, 0.85);
border: solid 2.5px #fff;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
-ms-box-sizing: content-box;
box-sizing: content-box;
}
.poptrox-popup .caption {
display: none !important;
}
.poptrox-popup .nav-next {
display: none !important;
}
.poptrox-popup .nav-previous {
display: none !important;
}
}

View File

@@ -0,0 +1,16 @@
/*
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
/* Main */
#main {
opacity: 1 !important;
overflow-x: auto !important;
}
#main .item.thumb img {
opacity: 1 !important;
}

View File

@@ -0,0 +1,2 @@
/* breakpoints.js v1.0 | @ajlkn | MIT licensed */
var breakpoints=function(){"use strict";function e(e){t.init(e)}var t={list:null,media:{},events:[],init:function(e){t.list=e,window.addEventListener("resize",t.poll),window.addEventListener("orientationchange",t.poll),window.addEventListener("load",t.poll),window.addEventListener("fullscreenchange",t.poll)},active:function(e){var n,a,s,i,r,d,c;if(!(e in t.media)){if(">="==e.substr(0,2)?(a="gte",n=e.substr(2)):"<="==e.substr(0,2)?(a="lte",n=e.substr(2)):">"==e.substr(0,1)?(a="gt",n=e.substr(1)):"<"==e.substr(0,1)?(a="lt",n=e.substr(1)):"!"==e.substr(0,1)?(a="not",n=e.substr(1)):(a="eq",n=e),n&&n in t.list)if(i=t.list[n],Array.isArray(i)){if(r=parseInt(i[0]),d=parseInt(i[1]),isNaN(r)){if(isNaN(d))return;c=i[1].substr(String(d).length)}else c=i[0].substr(String(r).length);if(isNaN(r))switch(a){case"gte":s="screen";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: -1px)";break;case"not":s="screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (max-width: "+d+c+")"}else if(isNaN(d))switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen";break;case"gt":s="screen and (max-width: -1px)";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+")";break;default:s="screen and (min-width: "+r+c+")"}else switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+"), screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (min-width: "+r+c+") and (max-width: "+d+c+")"}}else s="("==i.charAt(0)?"screen and "+i:i;t.media[e]=!!s&&s}return t.media[e]!==!1&&window.matchMedia(t.media[e]).matches},on:function(e,n){t.events.push({query:e,handler:n,state:!1}),t.active(e)&&n()},poll:function(){var e,n;for(e=0;e<t.events.length;e++)n=t.events[e],t.active(n.query)?n.state||(n.state=!0,n.handler()):n.state&&(n.state=!1)}};return e._=t,e.on=function(e,n){t.on(e,n)},e.active=function(e){return t.active(e)},e}();!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.breakpoints=t()}(this,function(){return breakpoints});

View File

@@ -0,0 +1,2 @@
/* browser.js v1.0.1 | @ajlkn | MIT licensed */
var browser=function(){"use strict";var t={name:null,version:null,os:null,osVersion:null,touch:null,mobile:null,_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,r=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+r in n||"Webkit"+r in n||"O"+r in n||"ms"+r in n},init:function(){for(var e=navigator.userAgent,n="other",r=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],o=0;o<i.length;o++)if(e.match(i[o][1])){n=i[o][0],r=parseFloat(RegExp.$1);break}for(t.name=n,t.version=r,n="other",i=[["ios",/([0-9_]+) like Mac OS X/,function(e){return e.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(e){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(e){return e.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null],["linux",/Linux/,null],["bsd",/BSD/,null],["unix",/X11/,null]],o=r=0;o<i.length;o++)if(e.match(i[o][1])){n=i[o][0],r=parseFloat(i[o][2]?i[o][2](RegExp.$1):RegExp.$1);break}"mac"==n&&"ontouchstart"in window&&(1024==screen.width&&1366==screen.height||834==screen.width&&1112==screen.height||810==screen.width&&1080==screen.height||768==screen.width&&1024==screen.height)&&(n="ios"),t.os=n,t.osVersion=r,t.touch="wp"==t.os?0<navigator.msMaxTouchPoints:!!("ontouchstart"in window),t.mobile="wp"==t.os||"android"==t.os||"ios"==t.os||"bb"==t.os}};return t.init(),t}();!function(e,n){"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?module.exports=n():e.browser=n()}(this,function(){return browser});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,390 @@
/*
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
(function($) {
var $window = $(window),
$body = $('body'),
$wrapper = $('#wrapper'),
$main = $('#main'),
settings = {
// Keyboard shortcuts.
keyboardShortcuts: {
// If true, enables scrolling via keyboard shortcuts.
enabled: true,
// Sets the distance to scroll when using the left/right arrow keys.
distance: 50
},
// Scroll wheel.
scrollWheel: {
// If true, enables scrolling via the scroll wheel.
enabled: true,
// Sets the scroll wheel factor. (Ideally) a value between 0 and 1 (lower = slower scroll, higher = faster scroll).
factor: 1
},
// Scroll zones.
scrollZones: {
// If true, enables scrolling via scroll zones on the left/right edges of the scren.
enabled: true,
// Sets the speed at which the page scrolls when a scroll zone is active (higher = faster scroll, lower = slower scroll).
speed: 15
}
};
// Breakpoints.
breakpoints({
xlarge: [ '1281px', '1680px' ],
large: [ '981px', '1280px' ],
medium: [ '737px', '980px' ],
small: [ '481px', '736px' ],
xsmall: [ null, '480px' ],
});
// Tweaks/fixes.
// Mobile: Revert to native scrolling.
if (browser.mobile) {
// Disable all scroll-assist features.
settings.keyboardShortcuts.enabled = false;
settings.scrollWheel.enabled = false;
settings.scrollZones.enabled = false;
// Re-enable overflow on main.
$main.css('overflow-x', 'auto');
}
// IE: Fix min-height/flexbox.
if (browser.name == 'ie')
$wrapper.css('height', '100vh');
// iOS: Compensate for address bar.
if (browser.os == 'ios')
$wrapper.css('min-height', 'calc(100vh - 30px)');
// Play initial animations on page load.
$window.on('load', function() {
window.setTimeout(function() {
$body.removeClass('is-preload');
}, 100);
});
// Items.
// Assign a random "delay" class to each thumbnail item.
$('.item.thumb').each(function() {
$(this).addClass('delay-' + Math.floor((Math.random() * 6) + 1));
});
// IE: Fix thumbnail images.
if (browser.name == 'ie')
$('.item.thumb').each(function() {
var $this = $(this),
$img = $this.find('img');
$this
.css('background-image', 'url(' + $img.attr('src') + ')')
.css('background-size', 'cover')
.css('background-position', 'center');
$img
.css('opacity', '0');
});
// Poptrox.
$main.poptrox({
onPopupOpen: function() { $body.addClass('is-poptrox-visible'); },
onPopupClose: function() { $body.removeClass('is-poptrox-visible'); },
overlayColor: '#1a1f2c',
overlayOpacity: 0.75,
popupCloserText: '',
popupLoaderText: '',
selector: '.item.thumb a.image',
caption: function($a) {
return $a.prev('h2').html();
},
usePopupDefaultStyling: false,
usePopupCloser: false,
usePopupCaption: true,
usePopupNav: true,
windowMargin: 50
});
breakpoints.on('>small', function() {
$main[0]._poptrox.windowMargin = 50;
});
breakpoints.on('<=small', function() {
$main[0]._poptrox.windowMargin = 0;
});
// Keyboard shortcuts.
if (settings.keyboardShortcuts.enabled)
(function() {
$window
// Keypress event.
.on('keydown', function(event) {
var scrolled = false;
if ($body.hasClass('is-poptrox-visible'))
return;
switch (event.keyCode) {
// Left arrow.
case 37:
$main.scrollLeft($main.scrollLeft() - settings.keyboardShortcuts.distance);
scrolled = true;
break;
// Right arrow.
case 39:
$main.scrollLeft($main.scrollLeft() + settings.keyboardShortcuts.distance);
scrolled = true;
break;
// Page Up.
case 33:
$main.scrollLeft($main.scrollLeft() - $window.width() + 100);
scrolled = true;
break;
// Page Down, Space.
case 34:
case 32:
$main.scrollLeft($main.scrollLeft() + $window.width() - 100);
scrolled = true;
break;
// Home.
case 36:
$main.scrollLeft(0);
scrolled = true;
break;
// End.
case 35:
$main.scrollLeft($main.width());
scrolled = true;
break;
}
// Scrolled?
if (scrolled) {
// Prevent default.
event.preventDefault();
event.stopPropagation();
// Stop link scroll.
$main.stop();
}
});
})();
// Scroll wheel.
if (settings.scrollWheel.enabled)
(function() {
// Based on code by @miorel + @pieterv of Facebook (thanks guys :)
// github.com/facebook/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
var normalizeWheel = function(event) {
var pixelStep = 10,
lineHeight = 40,
pageHeight = 800,
sX = 0,
sY = 0,
pX = 0,
pY = 0;
// Legacy.
if ('detail' in event)
sY = event.detail;
else if ('wheelDelta' in event)
sY = event.wheelDelta / -120;
else if ('wheelDeltaY' in event)
sY = event.wheelDeltaY / -120;
if ('wheelDeltaX' in event)
sX = event.wheelDeltaX / -120;
// Side scrolling on FF with DOMMouseScroll.
if ('axis' in event
&& event.axis === event.HORIZONTAL_AXIS) {
sX = sY;
sY = 0;
}
// Calculate.
pX = sX * pixelStep;
pY = sY * pixelStep;
if ('deltaY' in event)
pY = event.deltaY;
if ('deltaX' in event)
pX = event.deltaX;
if ((pX || pY)
&& event.deltaMode) {
if (event.deltaMode == 1) {
pX *= lineHeight;
pY *= lineHeight;
}
else {
pX *= pageHeight;
pY *= pageHeight;
}
}
// Fallback if spin cannot be determined.
if (pX && !sX)
sX = (pX < 1) ? -1 : 1;
if (pY && !sY)
sY = (pY < 1) ? -1 : 1;
// Return.
return {
spinX: sX,
spinY: sY,
pixelX: pX,
pixelY: pY
};
};
// Wheel event.
$body.on('wheel', function(event) {
// Disable on <=small.
if (breakpoints.active('<=small'))
return;
// Prevent default.
event.preventDefault();
event.stopPropagation();
// Stop link scroll.
$main.stop();
// Calculate delta, direction.
var n = normalizeWheel(event.originalEvent),
x = (n.pixelX != 0 ? n.pixelX : n.pixelY),
delta = Math.min(Math.abs(x), 150) * settings.scrollWheel.factor,
direction = x > 0 ? 1 : -1;
// Scroll page.
$main.scrollLeft($main.scrollLeft() + (delta * direction));
});
})();
// Scroll zones.
if (settings.scrollZones.enabled)
(function() {
var $left = $('<div class="scrollZone left"></div>'),
$right = $('<div class="scrollZone right"></div>'),
$zones = $left.add($right),
paused = false,
intervalId = null,
direction,
activate = function(d) {
// Disable on <=small.
if (breakpoints.active('<=small'))
return;
// Paused? Bail.
if (paused)
return;
// Stop link scroll.
$main.stop();
// Set direction.
direction = d;
// Initialize interval.
clearInterval(intervalId);
intervalId = setInterval(function() {
$main.scrollLeft($main.scrollLeft() + (settings.scrollZones.speed * direction));
}, 25);
},
deactivate = function() {
// Unpause.
paused = false;
// Clear interval.
clearInterval(intervalId);
};
$zones
.appendTo($wrapper)
.on('mouseleave mousedown', function(event) {
deactivate();
});
$left
.css('left', '0')
.on('mouseenter', function(event) {
activate(-1);
});
$right
.css('right', '0')
.on('mouseenter', function(event) {
activate(1);
});
$body
.on('---pauseScrollZone', function(event) {
// Pause.
paused = true;
// Unpause after delay.
setTimeout(function() {
paused = false;
}, 500);
});
})();
})(jQuery);

View File

@@ -0,0 +1,587 @@
(function($) {
/**
* Generate an indented list of links from a nav. Meant for use with panel().
* @return {jQuery} jQuery object.
*/
$.fn.navList = function() {
var $this = $(this);
$a = $this.find('a'),
b = [];
$a.each(function() {
var $this = $(this),
indent = Math.max(0, $this.parents('li').length - 1),
href = $this.attr('href'),
target = $this.attr('target');
b.push(
'<a ' +
'class="link depth-' + indent + '"' +
( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
'>' +
'<span class="indent-' + indent + '"></span>' +
$this.text() +
'</a>'
);
});
return b.join('');
};
/**
* Panel-ify an element.
* @param {object} userConfig User config.
* @return {jQuery} jQuery object.
*/
$.fn.panel = function(userConfig) {
// No elements?
if (this.length == 0)
return $this;
// Multiple elements?
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i]).panel(userConfig);
return $this;
}
// Vars.
var $this = $(this),
$body = $('body'),
$window = $(window),
id = $this.attr('id'),
config;
// Config.
config = $.extend({
// Delay.
delay: 0,
// Hide panel on link click.
hideOnClick: false,
// Hide panel on escape keypress.
hideOnEscape: false,
// Hide panel on swipe.
hideOnSwipe: false,
// Reset scroll position on hide.
resetScroll: false,
// Reset forms on hide.
resetForms: false,
// Side of viewport the panel will appear.
side: null,
// Target element for "class".
target: $this,
// Class to toggle.
visibleClass: 'visible'
}, userConfig);
// Expand "target" if it's not a jQuery object already.
if (typeof config.target != 'jQuery')
config.target = $(config.target);
// Panel.
// Methods.
$this._hide = function(event) {
// Already hidden? Bail.
if (!config.target.hasClass(config.visibleClass))
return;
// If an event was provided, cancel it.
if (event) {
event.preventDefault();
event.stopPropagation();
}
// Hide.
config.target.removeClass(config.visibleClass);
// Post-hide stuff.
window.setTimeout(function() {
// Reset scroll position.
if (config.resetScroll)
$this.scrollTop(0);
// Reset forms.
if (config.resetForms)
$this.find('form').each(function() {
this.reset();
});
}, config.delay);
};
// Vendor fixes.
$this
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
.css('-webkit-overflow-scrolling', 'touch');
// Hide on click.
if (config.hideOnClick) {
$this.find('a')
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
$this
.on('click', 'a', function(event) {
var $a = $(this),
href = $a.attr('href'),
target = $a.attr('target');
if (!href || href == '#' || href == '' || href == '#' + id)
return;
// Cancel original event.
event.preventDefault();
event.stopPropagation();
// Hide panel.
$this._hide();
// Redirect to href.
window.setTimeout(function() {
if (target == '_blank')
window.open(href);
else
window.location.href = href;
}, config.delay + 10);
});
}
// Event: Touch stuff.
$this.on('touchstart', function(event) {
$this.touchPosX = event.originalEvent.touches[0].pageX;
$this.touchPosY = event.originalEvent.touches[0].pageY;
})
$this.on('touchmove', function(event) {
if ($this.touchPosX === null
|| $this.touchPosY === null)
return;
var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
th = $this.outerHeight(),
ts = ($this.get(0).scrollHeight - $this.scrollTop());
// Hide on swipe?
if (config.hideOnSwipe) {
var result = false,
boundary = 20,
delta = 50;
switch (config.side) {
case 'left':
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
break;
case 'right':
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
break;
case 'top':
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
break;
case 'bottom':
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
break;
default:
break;
}
if (result) {
$this.touchPosX = null;
$this.touchPosY = null;
$this._hide();
return false;
}
}
// Prevent vertical scrolling past the top or bottom.
if (($this.scrollTop() < 0 && diffY < 0)
|| (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
event.preventDefault();
event.stopPropagation();
}
});
// Event: Prevent certain events inside the panel from bubbling.
$this.on('click touchend touchstart touchmove', function(event) {
event.stopPropagation();
});
// Event: Hide panel if a child anchor tag pointing to its ID is clicked.
$this.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.removeClass(config.visibleClass);
});
// Body.
// Event: Hide panel on body click/tap.
$body.on('click touchend', function(event) {
$this._hide(event);
});
// Event: Toggle.
$body.on('click', 'a[href="#' + id + '"]', function(event) {
event.preventDefault();
event.stopPropagation();
config.target.toggleClass(config.visibleClass);
});
// Window.
// Event: Hide on ESC.
if (config.hideOnEscape)
$window.on('keydown', function(event) {
if (event.keyCode == 27)
$this._hide(event);
});
return $this;
};
/**
* Apply "placeholder" attribute polyfill to one or more forms.
* @return {jQuery} jQuery object.
*/
$.fn.placeholder = function() {
// Browser natively supports placeholders? Bail.
if (typeof (document.createElement('input')).placeholder != 'undefined')
return $(this);
// No elements?
if (this.length == 0)
return $this;
// Multiple elements?
if (this.length > 1) {
for (var i=0; i < this.length; i++)
$(this[i]).placeholder();
return $this;
}
// Vars.
var $this = $(this);
// Text, TextArea.
$this.find('input[type=text],textarea')
.each(function() {
var i = $(this);
if (i.val() == ''
|| i.val() == i.attr('placeholder'))
i
.addClass('polyfill-placeholder')
.val(i.attr('placeholder'));
})
.on('blur', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
return;
if (i.val() == '')
i
.addClass('polyfill-placeholder')
.val(i.attr('placeholder'));
})
.on('focus', function() {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
return;
if (i.val() == i.attr('placeholder'))
i
.removeClass('polyfill-placeholder')
.val('');
});
// Password.
$this.find('input[type=password]')
.each(function() {
var i = $(this);
var x = $(
$('<div>')
.append(i.clone())
.remove()
.html()
.replace(/type="password"/i, 'type="text"')
.replace(/type=password/i, 'type=text')
);
if (i.attr('id') != '')
x.attr('id', i.attr('id') + '-polyfill-field');
if (i.attr('name') != '')
x.attr('name', i.attr('name') + '-polyfill-field');
x.addClass('polyfill-placeholder')
.val(x.attr('placeholder')).insertAfter(i);
if (i.val() == '')
i.hide();
else
x.hide();
i
.on('blur', function(event) {
event.preventDefault();
var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') {
i.hide();
x.show();
}
});
x
.on('focus', function(event) {
event.preventDefault();
var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
x.hide();
i
.show()
.focus();
})
.on('keypress', function(event) {
event.preventDefault();
x.val('');
});
});
// Events.
$this
.on('submit', function() {
$this.find('input[type=text],input[type=password],textarea')
.each(function(event) {
var i = $(this);
if (i.attr('name').match(/-polyfill-field$/))
i.attr('name', '');
if (i.val() == i.attr('placeholder')) {
i.removeClass('polyfill-placeholder');
i.val('');
}
});
})
.on('reset', function(event) {
event.preventDefault();
$this.find('select')
.val($('option:first').val());
$this.find('input,textarea')
.each(function() {
var i = $(this),
x;
i.removeClass('polyfill-placeholder');
switch (this.type) {
case 'submit':
case 'reset':
break;
case 'password':
i.val(i.attr('defaultValue'));
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
if (i.val() == '') {
i.hide();
x.show();
}
else {
i.show();
x.hide();
}
break;
case 'checkbox':
case 'radio':
i.attr('checked', i.attr('defaultValue'));
break;
case 'text':
case 'textarea':
i.val(i.attr('defaultValue'));
if (i.val() == '') {
i.addClass('polyfill-placeholder');
i.val(i.attr('placeholder'));
}
break;
default:
i.val(i.attr('defaultValue'));
break;
}
});
});
return $this;
};
/**
* Moves elements to/from the first positions of their respective parents.
* @param {jQuery} $elements Elements (or selector) to move.
* @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
*/
$.prioritize = function($elements, condition) {
var key = '__prioritize';
// Expand $elements if it's not already a jQuery object.
if (typeof $elements != 'jQuery')
$elements = $($elements);
// Step through elements.
$elements.each(function() {
var $e = $(this), $p,
$parent = $e.parent();
// No parent? Bail.
if ($parent.length == 0)
return;
// Not moved? Move it.
if (!$e.data(key)) {
// Condition is false? Bail.
if (!condition)
return;
// Get placeholder (which will serve as our point of reference for when this element needs to move back).
$p = $e.prev();
// Couldn't find anything? Means this element's already at the top, so bail.
if ($p.length == 0)
return;
// Move element to top of parent.
$e.prependTo($parent);
// Mark element as moved.
$e.data(key, $p);
}
// Moved already?
else {
// Condition is true? Bail.
if (condition)
return;
$p = $e.data(key);
// Move element back to its original location (using our placeholder).
$e.insertAfter($p);
// Unmark element as moved.
$e.removeData(key);
}
});
};
})(jQuery);

View File

@@ -0,0 +1,223 @@
// breakpoints.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Breakpoints.
/// @var {list}
$breakpoints: () !global;
// Mixins.
/// Sets breakpoints.
/// @param {map} $x Breakpoints.
@mixin breakpoints($x: ()) {
$breakpoints: $x !global;
}
/// Wraps @content in a @media block targeting a specific orientation.
/// @param {string} $orientation Orientation.
@mixin orientation($orientation) {
@media screen and (orientation: #{$orientation}) {
@content;
}
}
/// Wraps @content in a @media block using a given query.
/// @param {string} $query Query.
@mixin breakpoint($query: null) {
$breakpoint: null;
$op: null;
$media: null;
// Determine operator, breakpoint.
// Greater than or equal.
@if (str-slice($query, 0, 2) == '>=') {
$op: 'gte';
$breakpoint: str-slice($query, 3);
}
// Less than or equal.
@elseif (str-slice($query, 0, 2) == '<=') {
$op: 'lte';
$breakpoint: str-slice($query, 3);
}
// Greater than.
@elseif (str-slice($query, 0, 1) == '>') {
$op: 'gt';
$breakpoint: str-slice($query, 2);
}
// Less than.
@elseif (str-slice($query, 0, 1) == '<') {
$op: 'lt';
$breakpoint: str-slice($query, 2);
}
// Not.
@elseif (str-slice($query, 0, 1) == '!') {
$op: 'not';
$breakpoint: str-slice($query, 2);
}
// Equal.
@else {
$op: 'eq';
$breakpoint: $query;
}
// Build media.
@if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
$a: map-get($breakpoints, $breakpoint);
// Range.
@if (type-of($a) == 'list') {
$x: nth($a, 1);
$y: nth($a, 2);
// Max only.
@if ($x == null) {
// Greater than or equal (>= 0 / anything)
@if ($op == 'gte') {
$media: 'screen';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< 0 / invalid)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: -1px)';
}
// Not (> y)
@elseif ($op == 'not') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (<= y)
@else {
$media: 'screen and (max-width: ' + $y + ')';
}
}
// Min only.
@else if ($y == null) {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= inf / anything)
@elseif ($op == 'lte') {
$media: 'screen';
}
// Greater than (> inf / invalid)
@elseif ($op == 'gt') {
$media: 'screen and (max-width: -1px)';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Equal (>= x)
@else {
$media: 'screen and (min-width: ' + $x + ')';
}
}
// Min and max.
@else {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x and > y)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (>= x and <= y)
@else {
$media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
}
}
}
// String.
@else {
// Missing a media type? Prefix with "screen".
@if (str-slice($a, 0, 1) == '(') {
$media: 'screen and ' + $a;
}
// Otherwise, use as-is.
@else {
$media: $a;
}
}
}
// Output.
@media #{$media} {
@content;
}
}

View File

@@ -0,0 +1,90 @@
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Gets a value from a map.
/// @author Hugo Giraudel
/// @param {map} $map Map.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function val($map, $keys...) {
@if nth($keys, 1) == null {
$keys: remove-nth($keys, 1);
}
@each $key in $keys {
$map: map-get($map, $key);
}
@return $map;
}
/// Gets a duration value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _duration($keys...) {
@return val($duration, $keys...);
}
/// Gets a font value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _font($keys...) {
@return val($font, $keys...);
}
/// Gets a misc value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _misc($keys...) {
@return val($misc, $keys...);
}
/// Gets a palette value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _palette($keys...) {
@return val($palette, $keys...);
}
/// Gets a size value.
/// @param {string} $keys Key(s).
/// @return {string} Value.
@function _size($keys...) {
@return val($size, $keys...);
}

View File

@@ -0,0 +1,78 @@
/// Makes an element's :before pseudoelement a FontAwesome icon.
/// @param {string} $content Optional content value to use.
/// @param {string} $category Optional category to use.
/// @param {string} $where Optional pseudoelement to target (before or after).
@mixin icon($content: false, $category: regular, $where: before) {
text-decoration: none;
&:#{$where} {
@if $content {
content: $content;
}
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
text-transform: none !important;
@if ($category == brands) {
font-family: 'Font Awesome 5 Brands';
}
@elseif ($category == solid) {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
@else {
font-family: 'Font Awesome 5 Free';
font-weight: 400;
}
}
}
/// Applies padding to an element, taking the current element-margin value into account.
/// @param {mixed} $tb Top/bottom padding.
/// @param {mixed} $lr Left/right padding.
/// @param {list} $pad Optional extra padding (in the following order top, right, bottom, left)
/// @param {bool} $important If true, adds !important.
@mixin padding($tb, $lr, $pad: (0,0,0,0), $important: null) {
@if $important {
$important: '!important';
}
$x: 0.1em;
@if unit(_size(element-margin)) == 'rem' {
$x: 0.1rem;
}
padding: ($tb + nth($pad,1)) ($lr + nth($pad,2)) max($x, $tb - _size(element-margin) + nth($pad,3)) ($lr + nth($pad,4)) #{$important};
}
/// Encodes a SVG data URL so IE doesn't choke (via codepen.io/jakob-e/pen/YXXBrp).
/// @param {string} $svg SVG data URL.
/// @return {string} Encoded SVG data URL.
@function svg-url($svg) {
$svg: str-replace($svg, '"', '\'');
$svg: str-replace($svg, '%', '%25');
$svg: str-replace($svg, '<', '%3C');
$svg: str-replace($svg, '>', '%3E');
$svg: str-replace($svg, '&', '%26');
$svg: str-replace($svg, '#', '%23');
$svg: str-replace($svg, '{', '%7B');
$svg: str-replace($svg, '}', '%7D');
$svg: str-replace($svg, ';', '%3B');
@return url("data:image/svg+xml;charset=utf8,#{$svg}");
}

View File

@@ -0,0 +1,20 @@
// Misc.
$misc: (
z-index-base: 10000
);
// Duration.
$duration: (
);
// Size.
$size: (
);
// Font.
$font: (
);
// Palette.
$palette: (
);

View File

@@ -0,0 +1,376 @@
// vendor.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Vendor prefixes.
/// @var {list}
$vendor-prefixes: (
'-moz-',
'-webkit-',
'-ms-',
''
);
/// Properties that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-properties: (
// Animation.
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-fill-mode',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
// Appearance.
'appearance',
// Backdrop filter.
'backdrop-filter',
// Background image options.
'background-clip',
'background-origin',
'background-size',
// Box sizing.
'box-sizing',
// Clip path.
'clip-path',
// Filter effects.
'filter',
// Flexbox.
'align-content',
'align-items',
'align-self',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'justify-content',
'order',
// Font feature.
'font-feature-settings',
'font-language-override',
'font-variant-ligatures',
// Font kerning.
'font-kerning',
// Fragmented borders and backgrounds.
'box-decoration-break',
// Grid layout.
'grid-column',
'grid-column-align',
'grid-column-end',
'grid-column-start',
'grid-row',
'grid-row-align',
'grid-row-end',
'grid-row-start',
'grid-template-columns',
'grid-template-rows',
// Hyphens.
'hyphens',
'word-break',
// Masks.
'mask',
'mask-border',
'mask-border-outset',
'mask-border-repeat',
'mask-border-slice',
'mask-border-source',
'mask-border-width',
'mask-clip',
'mask-composite',
'mask-image',
'mask-origin',
'mask-position',
'mask-repeat',
'mask-size',
// Multicolumn.
'break-after',
'break-before',
'break-inside',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
// Object fit.
'object-fit',
'object-position',
// Regions.
'flow-from',
'flow-into',
'region-fragment',
// Scroll snap points.
'scroll-snap-coordinate',
'scroll-snap-destination',
'scroll-snap-points-x',
'scroll-snap-points-y',
'scroll-snap-type',
// Shapes.
'shape-image-threshold',
'shape-margin',
'shape-outside',
// Tab size.
'tab-size',
// Text align last.
'text-align-last',
// Text decoration.
'text-decoration-color',
'text-decoration-line',
'text-decoration-skip',
'text-decoration-style',
// Text emphasis.
'text-emphasis',
'text-emphasis-color',
'text-emphasis-position',
'text-emphasis-style',
// Text size adjust.
'text-size-adjust',
// Text spacing.
'text-spacing',
// Transform.
'transform',
'transform-origin',
// Transform 3D.
'backface-visibility',
'perspective',
'perspective-origin',
'transform-style',
// Transition.
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
// Unicode bidi.
'unicode-bidi',
// User select.
'user-select',
// Writing mode.
'writing-mode',
);
/// Values that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-values: (
// Cross fade.
'cross-fade',
// Element function.
'element',
// Filter function.
'filter',
// Flexbox.
'flex',
'inline-flex',
// Grab cursors.
'grab',
'grabbing',
// Gradients.
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient',
// Grid layout.
'grid',
'inline-grid',
// Image set.
'image-set',
// Intrinsic width.
'max-content',
'min-content',
'fit-content',
'fill',
'fill-available',
'stretch',
// Sticky position.
'sticky',
// Transform.
'transform',
// Zoom cursors.
'zoom-in',
'zoom-out',
);
// Functions.
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Replaces a substring within another string.
/// @author Hugo Giraudel
/// @param {string} $string String.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {string} Updated string.
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/// Replaces a substring within each string in a list.
/// @param {list} $strings List of strings.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {list} Updated list of strings.
@function str-replace-all($strings, $search, $replace: '') {
@each $string in $strings {
$strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
}
@return $strings;
}
// Mixins.
/// Wraps @content in vendorized keyframe blocks.
/// @param {string} $name Name.
@mixin keyframes($name) {
@-moz-keyframes #{$name} { @content; }
@-webkit-keyframes #{$name} { @content; }
@-ms-keyframes #{$name} { @content; }
@keyframes #{$name} { @content; }
}
/// Vendorizes a declaration's property and/or value(s).
/// @param {string} $property Property.
/// @param {mixed} $value String/list of value(s).
@mixin vendor($property, $value) {
// Determine if property should expand.
$expandProperty: index($vendor-properties, $property);
// Determine if value should expand (and if so, add '-prefix-' placeholder).
$expandValue: false;
@each $x in $value {
@each $y in $vendor-values {
@if $y == str-slice($x, 1, str-length($y)) {
$value: set-nth($value, index($value, $x), '-prefix-' + $x);
$expandValue: true;
}
}
}
// Expand property?
@if $expandProperty {
@each $vendor in $vendor-prefixes {
#{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Expand just the value?
@elseif $expandValue {
@each $vendor in $vendor-prefixes {
#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Neither? Treat them as a normal declaration.
@else {
#{$property}: #{$value};
}
}

View File

@@ -0,0 +1,681 @@
@import 'libs/vars';
@import 'libs/functions';
@import 'libs/mixins';
@import 'libs/vendor';
@import 'libs/breakpoints';
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600');
@import url('fontawesome-all.min.css');
/*
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
// Breakpoints.
@include breakpoints((
xlarge: ( 1281px, 1680px ),
large: ( 981px, 1280px ),
medium: ( 737px, 980px ),
small: ( 481px, 736px ),
xsmall: ( null, 480px )
));
// Reset.
// Based on meyerweb.com/eric/tools/css/reset (v2.0 | 20110126 | License: public domain)
html, body, div, span, applet, object,
iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
pre, a, abbr, acronym, address, big, cite,
code, del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var, b,
u, i, center, dl, dt, dd, ol, ul, li, fieldset,
form, label, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside,
canvas, details, embed, figure, figcaption,
footer, header, hgroup, menu, nav, output, ruby,
section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
&:before,
&:after {
content: '';
content: none;
}
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
-webkit-text-size-adjust: none;
}
mark {
background-color: transparent;
color: inherit;
}
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input, select, textarea {
-moz-appearance: none;
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
}
/* Basic */
// Set box model to border-box.
// Based on css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
@include vendor('background-image', ('url("images/overlay.png")', 'linear-gradient(top, rgba(0, 0, 0, 0) 75%, rgba(0, 0, 0, 0.65))', 'url("../../images/bg.jpg")'));
background-position: top left, bottom left, auto;
background-size: auto, 100% 100%, cover;
background-attachment: fixed;
background-repeat: repeat, no-repeat, auto;
position: relative;
background-color: #150C07;
line-height: 1.75em;
overflow-x: hidden;
overflow-y: auto;
// Stops initial animations until page loads.
&.is-preload {
*, *:before, *:after {
@include vendor('animation', 'none !important');
@include vendor('transition', 'none !important');
}
}
@include breakpoint('<=large') {
background-attachment: scroll;
}
@include breakpoint('<=small') {
background-attachment: scroll;
background-size: auto, 100% 100%, 250% auto;
background-repeat: repeat, no-repeat, no-repeat;
background-position: top left, bottom left, 50% 0%;
}
}
body, input, select, textarea {
font-family: 'Source Sans Pro';
font-weight: 400;
padding: 0;
font-size: 13pt;
color: rgba(255, 255, 255, 0.6);
@include breakpoint('<=xlarge') {
font-size: 11pt;
}
@include breakpoint('<=large') {
font-size: 10pt;
}
@include breakpoint('<=medium') {
font-size: 11pt;
}
}
a {
color: #fff;
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
@include vendor('transition', 'color 0.25s ease-in-out');
&:hover {
color: #d64760;
}
}
h1, h2, h3, h4, h5, h6 {
font-weight: 400;
color: #fff;
letter-spacing: -0.05em;
}
strong, b {
color: #fff;
color: rgba(255, 255, 255, 0.85);
font-weight: 600;
}
/* List */
ul {
&.icons {
cursor: default;
margin: 0 0 0.5em 0;
li {
display: inline-block;
font-size: 1.5em;
margin-left: 1em;
span {
display: none;
}
a {
opacity: 0.35;
color: #fff;
@include vendor('transition', 'opacity 0.25s ease-in-out');
&:hover {
opacity: 1.0;
}
}
&:first-child {
margin-left: 0;
}
}
@include breakpoint('<=small') {
margin: 0 0 2em 0;
}
}
}
/* Icons */
.icon {
@include icon;
position: relative;
text-decoration: none;
&:before {
line-height: inherit;
}
> .label {
display: none;
}
&.solid {
&:before {
font-weight: 900;
}
}
&.brands {
&:before {
font-family: 'Font Awesome 5 Brands';
}
}
}
/* Wrapper */
#wrapper {
min-height: 100vh;
@include vendor('display', 'flex');
@include vendor('flex-direction', 'column');
@include vendor('justify-content', 'space-between');
@include vendor('align-items', 'center');
@include vendor('transition', 'filter 0.5s ease-in-out');
&:before {
content: '';
display: block;
}
body.is-poptrox-visible & {
@include vendor('filter', 'blur(0.25em)');
}
@include breakpoint('<=small') {
min-height: 0;
}
}
/* Scroll Zone */
.scrollZone {
position: fixed;
width: 6rem;
height: 100vh;
cursor: -moz-grab;
cursor: -webkit-grab;
cursor: -ms-grab;
cursor: grab;
z-index: _misc(z-index-base) + 1;
&.left {
left: 0;
}
&.right {
right: 0;
}
@include breakpoint('<=small') {
display: none;
}
}
/* Main */
#main {
$border: 10px;
@include vendor('transition', 'opacity 1s ease-in-out');
@include vendor('transition-delay', '0.5s');
position: relative;
z-index: 1;
width: -moz-min-content;
width: -webkit-min-content;
width: -ms-min-content;
width: min-content;
max-width: 100vw;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
padding: ($border * 0.5);
.items {
@include vendor('display', 'flex');
> :last-child {
border-right: solid $border #ffffff;
}
}
.item {
$width: 20em;
@include vendor('flex-grow', '0');
@include vendor('flex-shrink', '0');
margin: ($border * 0.5);
height: 16em;
box-shadow: 0 0 0 $border #ffffff;
&.span-1 {
width: $width;
}
&.span-2 {
width: $width * 1.5;
}
&.span-3 {
width: $width * 2;
}
&.intro {
background-color: #d64760;
background-image: url('images/overlay.png');
@include vendor('display', 'flex');
@include vendor('flex-direction', 'column');
@include vendor('justify-content', 'center');
padding: 1em 3em;
h1 {
font-size: 3em;
line-height: 1em;
}
p {
font-size: 1.25em;
line-height: 1.5em;
margin: 0.5em 0 0 0;
opacity: 0.65;
}
}
&.thumb {
display: block;
position: relative;
background: rgba(255, 255, 255, 0.25);
cursor: default;
h2 {
position: absolute;
bottom: 0;
left: 0;
background: rgba(18, 21, 29, 0.85);
width: 100%;
padding: 1em;
font-weight: 400;
line-height: 1em;
z-index: 2;
}
img {
display: block;
z-index: 1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
@include vendor('object-fit', 'cover');
@include vendor('object-position', 'center');
@include vendor('transition', 'opacity 0.75s ease-in-out');
@include vendor('transition-delay', '1.25s');
}
@for $i from 1 through 6 {
&.delay-#{$i} {
img {
@include vendor('transition-delay', '#{1.25 + ($i * 0.125)}s');
}
}
}
}
}
body.is-preload & {
opacity: 0;
.item {
&.thumb {
img {
opacity: 0;
}
}
}
}
@include breakpoint('<=xlarge') {
.item {
$width: 17em;
height: 14em;
&.span-1 {
width: $width;
}
&.span-2 {
width: $width * 1.5;
}
&.span-3 {
width: $width * 2;
}
}
}
@include breakpoint('<=small') {
$border: 5px;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
padding: ($border * 0.5);
.items {
@include vendor('flex-wrap', 'wrap');
@include vendor('justify-content', 'center');
> :last-child {
border-right: 0;
}
}
.item {
@include vendor('flex-grow', '1');
@include vendor('flex-shrink', '1');
width: calc(50vw - #{$border * 3}) !important;
margin: ($border * 0.5);
box-shadow: 0 0 0 $border #ffffff;
&.intro {
width: calc(100vw - #{$border * 4}) !important;
text-align: center;
height: auto;
padding: 3em;
h1 {
font-size: 2em;
}
p {
font-size: 1em;
}
}
&.thumb {
h2 {
display: none;
}
}
}
}
@include breakpoint('<=xsmall') {
.item {
//width: calc(100vw - #{$border * 4}) !important;
height: 10em;
}
}
}
/* Header */
#footer {
width: 100%;
padding: 1.5em;
@include vendor('display', 'flex');
@include vendor('justify-content', 'space-between');
position: relative;
z-index: _misc(z-index-base) + 2;
> section {
&:first-child {
text-align: left;
max-width: 36em;
padding-right: 2em;
}
&:last-child {
text-align: right;
max-width: 36em;
}
}
h2 {
font-size: 2.25em;
margin: 0 0 1em 0;
}
.copyright {
li {
display: inline-block;
margin-left: 1em;
padding-left: 1em;
border-left: solid 1px rgba(255, 255, 255, 0.25);
line-height: 1em;
&:first-child {
border-left: 0;
margin-left: 0;
padding-left: 0;
}
}
}
@include breakpoint('<=medium') {
@include vendor('flex-direction', 'column');
> section {
&:first-child {
width: 100%;
padding-right: 0;
}
&:last-child {
text-align: left;
margin: 1.5em 0 0 0;
width: 100%;
}
}
}
@include breakpoint('<=small') {
text-align: center;
padding: 3em;
> section {
&:first-child {
text-align: center;
max-width: 100%;
}
&:last-child {
text-align: center;
max-width: 100%;
}
}
.copyright {
li {
display: block;
margin: 0.75em 0 0 0;
padding-left: 0;
border-left: 0;
&:first-child {
margin-top: 0;
}
}
}
}
}
/* Popup */
.poptrox-popup {
background: #1a1f2c;
background: rgba(18, 21, 29, 0.9);
box-shadow: 0px 0px 0px 10px #fff, 0px 10px 60px 10px rgba(8, 11, 19, 0.55);
cursor: default;
.loader {
display: block;
width: 48px;
height: 48px;
position: absolute;
top: 50%;
left: 50%;
margin: -24px 0 0 -24px;
background: url('images/loader.gif');
opacity: 0.25;
}
.caption {
position: absolute;
bottom: 0;
left: 0;
background: #1a1f2c;
background: rgba(18, 21, 29, 0.85);
display: block;
width: 100%;
line-height: 75px;
text-align: center;
font-size: 1.25em;
color: #fff;
}
.nav-next, .nav-previous {
text-decoration: none;
font-weight: normal;
font-style: normal;
@include vendor('transition', 'opacity 0.25s ease-in-out');
opacity: 0.35;
&:hover {
opacity: 1.0;
}
}
.nav-next, .nav-previous {
@include icon(false, solid);
text-transform: none !important;
width: 150px;
height: 75px;
position: absolute;
bottom: 0;
cursor: pointer;
font-size: 3em;
line-height: 75px;
}
.nav-next {
right: 0;
text-align: right;
padding-right: 30px;
&:before {
content: '\f105';
}
}
.nav-previous {
left: 0;
text-align: left;
padding-left: 30px;
&:before {
content: '\f104';
}
}
@include breakpoint('<=small') {
background: #0a0f1c;
box-shadow: 0px 0px 30px 10px rgba(8, 11, 19, 0.85);
border: solid 2.5px #fff;
@include vendor('box-sizing', 'content-box');
.caption {
display: none !important;
}
.nav-next {
display: none !important;
}
.nav-previous {
display: none !important;
}
}
}

View File

@@ -0,0 +1,26 @@
@import 'libs/vars';
@import 'libs/functions';
@import 'libs/mixins';
@import 'libs/vendor';
@import 'libs/breakpoints';
/*
Parallelism by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
/* Main */
#main {
opacity: 1 !important;
overflow-x: auto !important;
.item {
&.thumb {
img {
opacity: 1 !important;
}
}
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More