Compare commits

...

6 Commits

Author SHA1 Message Date
1f091cf1bd Doppelten Seitenordner content/1_photography entfernen
1_photography war eine vollstaendige Kopie von 1_lamps: gleicher Titel,
identische UUID EgfXk155nyUvqqA1 und byte-gleiche Unterordner - lediglich
1_trees war die Umbenennung von 1_vintage.

Zwei Kirby-Seiten mit derselben UUID fuehren dazu, dass interne
Verweise unvorhersehbar aufgeloest werden; zusaetzlich war die Seite
unter zwei URLs erreichbar. 1_lamps bleibt, weil Titel und URL-Slug
dazu passen.

Pruefung vor dem Loeschen: alle acht Unterordner per Pruefsumme mit
1_lamps verglichen, keine eindeutigen Dateien in 1_photography.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:37:42 +02:00
823f04fc05 Enhance CSS: magnetic menu hover text, home grid captions, responsive improvements 2026-08-30 00:40:20 +02:00
3012e5b994 improve deployment process 2025-10-23 11:06:52 +02:00
5edd9dedb6 add transaltions 2025-10-23 11:06:36 +02:00
62719ef9c3 deployment improvments 2025-10-16 01:50:43 +02:00
f4c5dd483d Fix publish script Docker context path 2025-10-15 23:08:43 +02:00
134 changed files with 1004 additions and 475 deletions

8
.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
.git
.gitignore
docker-compose.yml
docker-compose.registry.yml
docker-compose.traefik.yml
id.env
scripts/publish-image.sh
README.md

57
Dockerfile Normal file
View File

@@ -0,0 +1,57 @@
# Use latest official Ubuntu image
FROM ubuntu:latest
# Set timezone
ENV TZ=Europe/Berlin
# Configure timezone data during build
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Avoid interactive prompts during apt install
ARG DEBIAN_FRONTEND=noninteractive
# Install Apache, PHP and required extensions
RUN apt-get update && apt-get install -y \
apache2 \
apache2-utils \
ca-certificates \
php \
libapache2-mod-php \
php-curl \
php-dom \
php-gd \
php-intl \
php-json \
php-mbstring \
php-xml \
php-zip && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Ensure shared group ownership for collaborative editing
RUN groupadd -g 1111 kirbygroup && usermod -aG kirbygroup www-data
# Provide Apache vhost configuration and entrypoint
COPY default.conf /etc/apache2/sites-available/000-default.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Remove default Apache placeholder page
RUN rm -rf /var/www/html/*
# Copy project into the image
COPY --chown=www-data:kirbygroup . /var/www/html
# Enable required Apache modules
RUN a2enmod headers rewrite
# Ensure collaborative permissions at runtime
RUN chmod -R g+rw /var/www/html && find /var/www/html -type d -exec chmod g+xs {} \;
# Expose web port
EXPOSE 80
# Allow UID remapping before Apache starts
ENTRYPOINT ["/entrypoint.sh"]
# Start Apache in foreground
CMD ["/usr/sbin/apache2ctl", "-DFOREGROUND"]

29
Jenkinsfile vendored
View File

@@ -15,18 +15,7 @@ pipeline {
}
}
stage('Build image') {
steps {
sh """
docker build \
--file kirby/Dockerfile \
--tag ${IMAGE_REF} \
kirby
"""
}
}
stage('Push image') {
stage('Build & Push image') {
steps {
withCredentials([usernamePassword(
credentialsId: 'gitea-container-registry',
@@ -34,9 +23,23 @@ pipeline {
passwordVariable: 'REGISTRY_PASSWORD'
)]) {
sh """
if ! docker buildx inspect jenkins-builder >/dev/null 2>&1; then
docker buildx create --name jenkins-builder --use
else
docker buildx use jenkins-builder
fi
docker buildx inspect --bootstrap
echo "${REGISTRY_PASSWORD}" | docker login ${REGISTRY_HOST} \
--username "${REGISTRY_USER}" --password-stdin
docker push ${IMAGE_REF}
docker buildx build \
--platform linux/amd64 \
--file Dockerfile \
--tag ${IMAGE_REF} \
--push \
.
docker logout ${REGISTRY_HOST}
"""
}

View File

@@ -1,43 +1,56 @@
<img src="http://getkirby.com/assets/images/github/starterkit.jpg" width="300">
# Lichterei Web Kirby Projekt
**Kirby: the CMS that adapts to any project, loved by developers and editors alike.**
The Starterkit is a full-blown Kirby installation with a lot of example content, blueprints, templates and more.
It is ideal for new users to explore many of Kirby's options and get to know the Panel.
Dieses Repository bündelt die Kirby-Installation sowie die Docker-Infrastruktur, damit du das Projekt lokal ausprobieren und im Server-Setup mit Traefik deployen kannst.
You can learn more about Kirby at [getkirby.com](https://getkirby.com).
## Voraussetzungen
- Docker Engine ≥ 20.10 und `docker compose`
- Optional: Composer, falls du Abhängigkeiten aktualisieren möchtest
- Passe vor dem ersten Start den Wert `USERID` in `id.env` an deinen lokalen Benutzer an so vermeidest du Rechteprobleme beim Schreiben ins gemountete Volume.
<img src="http://getkirby.com/assets/images/github/starterkit-screen.png" />
## Lokale Ausführung
1. `USERID` in `id.env` prüfen/anpassen (`id -u` liefert deinen Wert).
2. Container starten:
```bash
docker compose up --build
```
3. Die Seite ist anschließend unter http://localhost:8080 erreichbar, das Kirby Panel unter http://localhost:8080/panel.
### Try Kirby for free
Mit `docker compose down` stoppst du die Umgebung wieder. Inhalte bleiben erhalten, da der Projektordner als Volume eingebunden ist.
You can try Kirby and the Starterkit on your local machine or on a test server as long as you need to make sure it is the right tool for your next project. … and when youre convinced, [buy your license](https://getkirby.com/buy).
## Deployment mit Traefik
1. Domain, Router- und Zertifikatsnamen in `docker-compose.traefik.yml` anpassen.
2. Sicherstellen, dass das externe Netzwerk `proxy` existiert (`docker network create proxy`).
3. Deployment starten:
```bash
docker compose \
-f docker-compose.yml \
-f docker-compose.traefik.yml \
up -d
```
Passe bei Bedarf den Volume-Eintrag in `docker-compose.yml` an (z.B. benanntes Volume oder separater Persistenzpfad).
4. Für den Registry-Workflow steht `scripts/publish-image.sh` bereit. Das Skript baut das Image aus dem aktuellen Stand und pusht es zum konfigurierten Registry-Host.
The Starterkit is a demo of basic Kirby features. It's not recommended to be used "as is" in production. Please, follow our documentation closely for more features and guides on how to build secure, high-quality websites with Kirby.
## Nutzung eines Registry-Images
- Setze vor dem Start die Umgebungsvariable `KIRBY_IMAGE` auf das exakte Image samt Tag oder Digest, z.B.:
```bash
export KIRBY_IMAGE=gitea.mindboost.team/interkollektives/lichterei-web:0.0.1
```
Mit `docker image ls gitea.mindboost.team/interkollektives/lichterei-web` siehst du, welche Tags lokal vorhanden sind. Fehlt der gewünschte Tag, kannst du ihn vergeben: `docker tag <IMAGE_ID> gitea.mindboost.team/...:<tag>`.
- Container aus Registry starten:
```bash
docker compose -f docker-compose.registry.yml up -d
```
`--build` wird hier nicht benötigt, da keine lokale Build-Definition verwendet wird.
- Falls du das Image aktualisierst, entferne das benannte Volume vorher (`docker compose down -v`), damit die neuen Dateien aus dem Image erneut in `/var/www/html` kopiert werden.
- Kombination mit Traefik ist ebenfalls möglich:
```bash
docker compose -f docker-compose.registry.yml -f docker-compose.traefik.yml up -d
```
While Kirby as the CMS software itself requires you to purchase a license, we consider the files primarily connected to this Starterkit (assets, templates, snippets...) free to use under the MIT license. Feel free to start building your own project with them.
## Contribution Guidelines
- Forken oder Branch erstellen, bevor du Änderungen beginnst.
- Lokal mit `docker compose up` arbeiten oder falls du ohne Container entwickeln willst `composer install` ausführen.
- Bitte prüfe, ob deine Änderungen im Frontend sichtbar funktionieren und keine PHP-Fehler im Log landen.
- Pull Request eröffnen und kurz beschreiben, was angepasst wurde und wie getestet wurde.
### Get going
Read our guide on [how to get started with Kirby](https://getkirby.com/docs/guide/quickstart).
You can download the latest version of the Starterkit from https://download.getkirby.com/.
If you are familiar with Git, you can clone Kirby's Starterkit repository from Github.
git clone https://github.com/getkirby/starterkit.git
## What's Kirby?
- **[getkirby.com](https://getkirby.com)** Get to know the CMS.
- **[Try it](https://getkirby.com/try)** Take a test ride with our online demo. Or download one of our kits to get started.
- **[Documentation](https://getkirby.com/docs/guide)** Read the official guide, reference and cookbook recipes.
- **[Issues](https://github.com/getkirby/kirby/issues)** Report bugs and other problems.
- **[Feedback](https://feedback.getkirby.com)** You have an idea for Kirby? Share it.
- **[Forum](https://forum.getkirby.com)** Whenever you get stuck, don't hesitate to reach out for questions and support.
- **[Discord](https://chat.getkirby.com)** Hang out and meet the community.
- **[YouTube](https://youtube.com/kirbyCasts)** - Watch the latest video tutorials visually with Bastian.
- **[Mastodon](https://mastodon.social/@getkirby)** Spread the word.
- **[Bluesky](https://bsky.app/profile/getkirby.com)** Tell a friend.
---
© 2009 Bastian Allgeier
[getkirby.com](https://getkirby.com) · [License agreement](https://getkirby.com/license)
Vielen Dank fürs Mitmachen! Bei Fragen melde dich einfach per Issue oder direkt im Team-Chat.

View File

@@ -1,16 +1,14 @@
* {
margin: 0;
padding: 0;
}
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
/* ===== CSS Variables ===== */
:root {
--padding: 1.5rem;
--color-black: #000;
--color-white: #fff;
--color-grey: #777;
--color-light: #efefef;
--color-text: var(--color-black);
--color-text-grey: var(--color-grey);
--color-text: #e8f4ff;
--color-text-grey: #cfd9e4;
--color-background: var(--color-white);
--color-code-light-grey: #cacbd1;
--color-code-comment: #a9aaad;
@@ -22,52 +20,237 @@
--color-code-aqua: #8abeb7;
--color-code-blue: #7e9abf;
--color-code-purple: #b294bb;
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-family-sans: 'Poppins', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--font-family-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
html {
/* ===== Background overlay ===== */
body {
font-family: var(--font-family-sans);
color: var(--color-text);
background: var(--color-background);
}
img {
width: 100%;
}
body {
padding: var(--padding);
max-width: 70rem;
margin: 0 auto;
}
li {
list-style: none;
}
a {
color: currentColor;
text-decoration: none;
}
button {
font: inherit;
background: none;
border: 0;
color: currentColor;
position: relative;
min-height: 100vh;
z-index: 0;
}
body::before {
content: "";
position: fixed;
top: 50%;
left: 50%;
width: 320vmax;
height: 320vmax;
background: linear-gradient(135deg, #3f0e00, #a63b00, #ffbe6e);
z-index: -1;
animation: rotate-gradient 60s linear infinite;
pointer-events: none;
}
.is-blackout body::before {
animation: none;
background: #000;
}
.blackout-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.95);
color: #fff;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
z-index: 100;
font-size: 2rem;
letter-spacing: 0.2em;
pointer-events: none;
}
.is-blackout .blackout-overlay {
display: flex;
}
/* ===== Magnetic Menu (improved) ===== */
.magnetic-menu {
min-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
padding: 4rem var(--padding) 2rem;
text-align: center;
}
.magnetic-menu__inner {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2.5rem;
width: min(60rem, 100%);
}
.magnetic-menu__item {
width: clamp(8rem, 18vw, 12rem);
flex: 0 0 auto;
aspect-ratio: 1 / 1;
border-radius: 50%;
position: relative;
overflow: visible;
display: grid;
place-items: center;
text-decoration: none;
color: var(--color-text);
transition: transform 200ms ease-out;
will-change: transform;
cursor: pointer;
}
strong, b {
.magnetic-menu__item:hover {
transform: scale(1.1) rotate(5deg);
}
.magnetic-menu__image {
width: 62%;
aspect-ratio: 1 / 1;
border-radius: 50%;
overflow: hidden;
position: relative;
z-index: 1;
}
.magnetic-menu__image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.magnetic-menu__circle {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.magnetic-menu__circle text {
font-size: clamp(0.55rem, 1.6vw, 0.75rem);
letter-spacing: 0.4em;
text-transform: uppercase;
fill: var(--color-text);
font-weight: 600;
}
small {
font-size: inherit;
color: var(--color-text-grey);
.magnetic-menu__item.is-placeholder {
pointer-events: none;
opacity: 0.45;
}
.bg-light {
background-color: var(--color-light);
/* ===== NEW: Hover text on magnetic menu items ===== */
.magnetic-menu__item::before {
content: attr(data-lamp-desc);
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 200ms ease;
pointer-events: none;
background: rgba(0, 0, 0, 0.7);
color: var(--color-white);
font-size: clamp(0.4rem, 1.2vw, 0.5rem);
letter-spacing: 0.2em;
text-transform: uppercase;
z-index: 10;
width: 100%;
height: 100%;
}
.color-grey {
color: var(--color-text-grey);
.magnetic-menu__item:hover::before {
opacity: 1;
}
/* ===== Footer magnet ===== */
.footer-magnet {
display: inline-block;
transition: transform 280ms cubic-bezier(.25, .8, .25, 1);
will-change: transform;
user-select: none;
}
.footer-dim-image {
display: block;
margin-top: 1rem;
filter: brightness(1);
transition: filter 250ms ease;
cursor: pointer;
}
/* ===== Responsive ===== */
@media (max-width: 800px) {
.magnetic-menu {
min-height: auto;
padding: 3rem var(--padding) 1rem;
justify-content: flex-start;
}
.magnetic-menu__inner {
width: 100%;
justify-content: space-between;
gap: 1.5rem;
}
.magnetic-menu__item {
flex: 1 1 calc((100% / 3) - 1rem);
max-width: none;
width: calc((100% / 3) - 1rem);
}
.has-top-menu .magnetic-menu__inner {
justify-content: space-between;
}
}
@media (max-width: 520px) {
.magnetic-menu {
min-height: auto;
padding: 1rem var(--padding) 0.5rem;
}
.magnetic-menu__inner {
gap: 0.75rem;
}
.magnetic-menu__item {
flex: 1 1 calc((100% / 3) - 0.5rem);
width: calc((100% / 3) - 0.5rem);
}
.magnetic-menu__circle text {
font-size: clamp(0.45rem, 3vw, 0.6rem);
}
}
/* ===== Top menu mode ===== */
.has-top-menu .magnetic-menu {
position: sticky;
top: 0;
z-index: 20;
min-height: auto;
justify-content: center;
padding: 1rem 0 0.5rem;
}
.has-top-menu .magnetic-menu__inner {
justify-content: center;
max-width: 70rem;
margin: 0 auto;
padding: 0 var(--padding);
}
.has-top-menu .main {
padding-top: calc(var(--padding) + 6rem);
}
/* ===== Main layout ===== */
.main {
max-width: 70rem;
margin: 0 auto;
padding: var(--padding);
padding-bottom: calc(8rem + var(--padding));
}
/* ===== Base elements ===== */
li { list-style: none; }
a { color: currentColor; text-decoration: none; }
button { font: inherit; background: none; border: 0; color: currentColor; cursor: pointer; }
strong, b { font-weight: 600; }
small { font-size: inherit; color: var(--color-text-grey); }
/* ===== Background helpers ===== */
.bg-light { background-color: var(--color-light); }
.color-grey { color: var(--color-text-grey); }
/* ===== Header ===== */
.header {
position: relative;
display: flex;
@@ -77,7 +260,6 @@ small {
margin-left: -1rem;
margin-bottom: 6rem;
}
.logo {
padding: 1rem;
display: flex;
@@ -86,189 +268,56 @@ small {
cursor: pointer;
}
.menu {
display: flex;
}
.menu a {
padding: 1rem;
display: block;
}
.menu a[aria-current] {
text-decoration: underline;
}
/* ===== Menu ===== */
.menu { display: flex; }
.menu a { padding: 1rem; display: block; }
.menu a[aria-current] { text-decoration: underline; }
.social {
display: flex;
padding: 0 .5rem;
}
.social a {
padding: 1rem .5rem;
}
/* ===== Social ===== */
.social { display: flex; padding: 0 .5rem; }
.social a { padding: 1rem .5rem; }
.section {
padding: 3rem 0;
}
/* ===== Sections ===== */
.section { padding: 3rem 0; }
.grid {
--columns: 12;
--gutter: 3rem;
display: grid;
grid-gap: var(--gutter);
grid-template-columns: 1fr;
}
.grid > .column {
margin-bottom: var(--gutter);
}
/* ===== Grid ===== */
.grid { --columns: 12; --gutter: 3rem; display: grid; grid-gap: var(--gutter); grid-template-columns: 1fr; }
.grid > .column { margin-bottom: var(--gutter); }
.autogrid {
--gutter: 3rem;
--min: 10rem;
display: grid;
grid-gap: var(--gutter);
grid-template-columns: repeat(auto-fit, minmax(var(--min), 1fr));
grid-auto-flow: dense;
}
/* ===== Auto-grid ===== */
.autogrid { --gutter: 3rem; --min: 10rem; display: grid; grid-gap: var(--gutter); grid-template-columns: repeat(auto-fit, minmax(var(--min), 1fr)); grid-auto-flow: dense; }
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
line-height: inherit;
}
/* ===== Headings ===== */
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; line-height: inherit; }
.text {
line-height: 1.5em;
}
.text a {
text-decoration: underline;
}
.text :first-child {
margin-top: 0;
}
.text :last-child {
margin-bottom: 0;
}
.text p,
.text ul,
.text ol {
margin-bottom: 1.5rem;
}
.text ul,
.text ol {
margin-left: 1rem;
}
.text ul p,
.text ol p {
margin-bottom: 0;
}
.text ul > li {
list-style: disc;
}
.text ol > li {
list-style: decimal;
}
.text ul ol,
.text ul ul,
.text ol ul,
.text ol ol {
margin-bottom: 0;
}
.text h1,
.h1,
.intro {
font-size: 2rem;
margin-bottom: 3rem;
line-height: 1.25em;
}
.text h2,
.h2 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1.25rem;
}
.text h3,
.h3 {
font-weight: 600;
}
.text .codeblock {
display: grid;
}
.text code {
font-family: var(--font-family-mono);
font-size: 1em;
background: var(--color-light);
padding: 0 .5rem;
display: inline-block;
color: var(--color-black);
}
.text pre {
margin: 3rem 0;
background: var(--color-black);
color: var(--color-white);
padding: 1.5rem;
overflow-x: scroll;
overflow-y: hidden;
line-height: 1.5rem;
}
.text pre code {
padding: 0;
background: none;
color: inherit;
}
.text hr {
margin: 6rem 0;
}
.text dt {
font-weight: 600;
}
.text blockquote {
font-size: 1.25rem;
line-height: 1.325em;
border-left: 2px solid var(--color-black);
padding-left: 1rem;
margin: 3rem 0;
max-width: 25rem;
}
.text blockquote footer {
font-size: .875rem;
font-style: italic;
}
.text figure {
margin: 3rem 0;
}
.text figcaption {
padding-top: .75rem;
color: var(--color-text-grey);
}
.text figure ul {
line-height: 0;
display: grid;
gap: 1.5rem;
margin: 0;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
.text figure ul li {
list-style: none;
}
/* ===== Text ===== */
.text { line-height: 1.5em; }
.text a { text-decoration: underline; }
.text :first-child { margin-top: 0; }
.text :last-child { margin-bottom: 0; }
.text p, .text ul, .text ol { margin-bottom: 1.5rem; }
.text ul, .text ol { margin-left: 1rem; }
.text ul p, .text ol p { margin-bottom: 0; }
.text ul > li { list-style: disc; }
.text ol > li { list-style: decimal; }
.text ul ol, .text ul ul, .text ol ul, .text ol ol { margin-bottom: 0; }
.text h1, .h1, .intro { font-size: 2rem; margin-bottom: 3rem; line-height: 1.25em; }
.text h2, .h2 { font-size: 1.25rem; font-weight: 600; margin-bottom: 1.25rem; }
.text h3, .h3 { font-weight: 600; }
.text .codeblock { display: grid; }
.text code { font-family: var(--font-family-mono); font-size: 1em; background: var(--color-light); padding: 0 .5rem; display: inline-block; color: var(--color-black); }
.text pre { margin: 3rem 0; background: var(--color-black); color: var(--color-white); padding: 1.5rem; overflow-x: scroll; overflow-y: hidden; line-height: 1.5rem; }
.text pre code { padding: 0; background: none; color: inherit; }
hr {
border: 0;
background: currentColor;
height: 2px;
width: 1.5rem;
margin: 3rem auto;
}
/* ===== HR & Alignment ===== */
hr { border: 0; background: currentColor; height: 2px; width: 1.5rem; margin: 3rem auto; }
.align-center { text-align: center; }
.align-center {
text-align: center;
}
.intro {
max-width: 40rem;
}
.intro *:not(:last-child) {
margin-bottom: 1em;
}
/* ===== Intro ===== */
.intro { max-width: 40rem; }
.intro *:not(:last-child) { margin-bottom: 1em; }
/* ===== CTA ===== */
.cta {
background: var(--color-black);
color: var(--color-white);
@@ -279,6 +328,7 @@ hr {
outline: 2px solid var(--color-black);
}
/* ===== Box ===== */
.box {
background: var(--color-light);
padding: 1.5rem;
@@ -286,149 +336,5 @@ hr {
outline: 2px solid var(--color-light);
}
.video,
.img {
position: relative;
display: block;
--w: 1;
--h: 1;
padding-bottom: calc(100% / var(--w) * var(--h));
background: var(--color-black);
}
.img img,
.video iframe {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
}
.img[data-contain] img {
object-fit: contain;
}
.img-caption,
.video-caption {
padding-top: .75rem;
line-height: 1.5em;
}
.footer {
padding: 9rem 0 6rem;
line-height: 1.5em;
}
.footer:before {
content: "";
display: block;
width: 1.5rem;
height: 2px;
background: var(--color-black);
margin-bottom: 1.5rem;
}
.footer h2 {
font-weight: 600;
margin-bottom: .75rem;
}
.footer ul,
.footer p {
color: var(--color-text-grey);
}
.footer p {
max-width: 15rem;
}
.footer a:hover {
color: var(--color-text);
}
.map {
--w: 2;
--h: 1;
padding-bottom: calc(100% / var(--w) * var(--h));
position: relative;
overflow: hidden;
background: var(--color-black);
}
.map iframe {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
.margin-s {
margin-bottom: .75rem;
}
.margin-m {
margin-bottom: 1.5rem;
}
.margin-l {
margin-bottom: 3rem;
}
.margin-xl {
margin-bottom: 4.5rem;
}
.margin-xxl {
margin-bottom: 6rem;
}
@media screen and (min-width: 60rem) {
body {
--padding: 3rem;
}
.grid {
grid-template-columns: repeat(12, 1fr);
}
.grid > .column {
grid-column: span var(--columns);
}
}
.pagination {
display: flex;
padding-top: 6rem;
}
.pagination > span {
color: var(--color-text-grey);
}
.pagination > * {
padding: .5rem;
width: 3rem;
text-align: center;
border: 2px solid currentColor;
margin-right: 1.5rem;
}
.pagination > a:hover {
background: var(--color-black);
color: var(--color-white);
border-color: var(--color-black);
}
.note-excerpt {
line-height: 1.5em;
}
.note-excerpt header {
margin-bottom: 1.5rem;
}
.note-excerpt figure {
margin-bottom: .5rem;
}
.note-excerpt-title {
font-weight: 600;
}
.note-excerpt-date {
color: var(--color-text-grey);
}
/* ===== Video ===== */
.video,

View File

@@ -10,7 +10,6 @@
position: relative;
--cols: 1;
--rows: 1;
overflow: hidden;
background: #000;
line-height: 0;
@@ -55,9 +54,15 @@
left: 0;
line-height: 1;
text-align: center;
background: rgba(0,0,0, .5);
background: rgba(0,0,0, .7);
opacity: 0;
transition: opacity .3s;
}
.home-grid li:hover figcaption {
opacity: 1;
}
/* Medium screens */
@media screen and (min-width: 45em) {
.home-grid {
grid-template-columns: repeat(3, 1fr);
@@ -69,4 +74,4 @@
.home-grid a {
padding-bottom: 52.65%;
}
}
}

View File

@@ -5,3 +5,150 @@ Array.from(document.querySelectorAll("[data-lightbox]")).forEach(element => {
basicLightbox.create(`<img src="${element.href}">`).show();
};
});
let lastPointerType = "mouse";
window.addEventListener("pointerdown", event => {
lastPointerType = event.pointerType || "mouse";
});
// Central magnetic menu
const menuContainer = document.querySelector("[data-magnetic-menu]");
if (menuContainer) {
const menuItems = Array.from(menuContainer.querySelectorAll(".magnetic-menu__item"));
const hoverRadius = 240;
const shiftStrength = 45;
const spread = 18;
const isHome = document.body.classList.contains("is-home");
const applySpreadOnly = () => {
menuItems.forEach((item, index) => {
const neighborSpread = (index - (menuItems.length - 1) / 2) * spread;
item.style.transform = `translate(${neighborSpread}px, 0px)`;
});
};
const handleMenuPointer = event => {
const pointerX = event?.clientX ?? null;
const pointerY = event?.clientY ?? null;
menuItems.forEach((item, index) => {
const rect = item.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
let offsetX = 0;
let offsetY = 0;
if (pointerX !== null) {
const dx = pointerX - centerX;
const dy = pointerY - centerY;
const distance = Math.hypot(dx, dy);
if (distance < hoverRadius && distance > 0.001) {
const force = 1 - distance / hoverRadius;
offsetX = -(dx / distance) * force * shiftStrength;
offsetY = -(dy / distance) * force * shiftStrength;
}
}
const neighborSpread = (index - (menuItems.length - 1) / 2) * spread;
item.style.transform = `translate(${offsetX + neighborSpread}px, ${offsetY}px)`;
});
};
menuContainer.addEventListener("pointermove", handleMenuPointer);
menuContainer.addEventListener("pointerleave", applySpreadOnly);
applySpreadOnly();
if (isHome) {
const pinMenu = () => {
document.body.classList.add("has-top-menu");
};
menuItems.forEach(item => {
item.addEventListener("pointerdown", pinMenu, { once: true });
item.addEventListener("click", pinMenu, { once: true });
item.addEventListener("touchstart", pinMenu, { once: true });
});
}
}
// Footer magnet hover
const footer = document.querySelector(".footer");
const footerMagnets = footer ? Array.from(footer.querySelectorAll("[data-footer-magnet]")) : [];
if (footer && footerMagnets.length) {
const radius = 240;
const maxOffset = 60;
let pointerX = null;
let pointerY = null;
let rafId = null;
const updateFooterMagnets = () => {
footerMagnets.forEach(item => {
if (pointerX === null || pointerY === null) {
item.style.transform = "";
return;
}
const rect = item.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = centerX - pointerX;
const dy = centerY - pointerY;
const distance = Math.hypot(dx, dy);
if (distance < radius && distance > 0.001) {
const force = 1 - distance / radius;
const offsetX = (dx / distance) * force * maxOffset;
const offsetY = (dy / distance) * force * maxOffset;
item.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
} else {
item.style.transform = "";
}
});
rafId = null;
};
const requestFooterUpdate = () => {
if (rafId === null) {
rafId = requestAnimationFrame(updateFooterMagnets);
}
};
footer.addEventListener("pointermove", event => {
pointerX = event.clientX;
pointerY = event.clientY;
requestFooterUpdate();
});
footer.addEventListener("pointerleave", () => {
pointerX = null;
pointerY = null;
footerMagnets.forEach(item => {
item.style.transform = "";
});
});
}
// Footer image brightness cycle
const footerDimImage = document.querySelector(".footer-dim-image");
if (footerDimImage) {
const brightnessSteps = [1, 0.75, 0.5, 0.25];
let brightnessIndex = Number(footerDimImage.dataset.brightnessIndex || 0);
footerDimImage.style.filter = `brightness(${brightnessSteps[brightnessIndex]})`;
footerDimImage.addEventListener("click", () => {
brightnessIndex = (brightnessIndex + 1) % brightnessSteps.length;
footerDimImage.dataset.brightnessIndex = String(brightnessIndex);
footerDimImage.style.filter = `brightness(${brightnessSteps[brightnessIndex]})`;
if (!document.body.classList.contains("is-blackout") && Math.random() < 0.15) {
document.body.classList.add("is-blackout");
}
});
}
// Blackout toggle based on click
const blackoutOverlay = document.querySelector("[data-blackout-overlay]");
document.addEventListener("pointerdown", () => {
if (document.body.classList.contains("is-blackout")) {
document.body.classList.remove("is-blackout");
}
}, true);

View File

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 238 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

View File

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 251 KiB

View File

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 206 KiB

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 179 KiB

View File

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 218 KiB

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