Initial commit: static site + Docker setup for Marszalek Architekten

- Single-page HTML website (no CMS) with portfolio grid, services,
  team and contact sections; content extracted from marszalekarchitekten.at
- 2-page PDF info sheet generated via Puppeteer from HTML template
- nginx:alpine Docker image with custom nginx.conf (caching, gzip, headers)
- docker-compose.yml for Portainer deployment on port 3080

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 11:37:52 +02:00
commit af02d4bd66
9 changed files with 1655 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
package-lock.json
marszalek-architekten-infosheet-v2.pdf

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY index.html /usr/share/nginx/html/
COPY marszalek-architekten-infosheet.pdf /usr/share/nginx/html/
EXPOSE 80

7
docker-compose.yml Normal file
View File

@@ -0,0 +1,7 @@
services:
web:
build: .
container_name: marszalek-architekten
ports:
- "3080:80"
restart: unless-stopped

30
generate-pdf.js Normal file
View File

@@ -0,0 +1,30 @@
const puppeteer = require('puppeteer-core');
const path = require('path');
(async () => {
const browser = await puppeteer.launch({
executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
const templatePath = 'file:///' + path.join(__dirname, 'infosheet-template.html').replace(/\\/g, '/');
await page.goto(templatePath, { waitUntil: 'networkidle0', timeout: 15000 });
// Wait for Google Fonts to load
await new Promise(r => setTimeout(r, 1500));
const outPath = path.join(__dirname, 'marszalek-architekten-infosheet.pdf');
await page.pdf({
path: outPath,
format: 'A4',
printBackground: true,
margin: { top: 0, right: 0, bottom: 0, left: 0 },
});
await browser.close();
console.log('PDF written to', outPath);
})();

1067
index.html Normal file

File diff suppressed because it is too large Load Diff

489
infosheet-template.html Normal file
View File

@@ -0,0 +1,489 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300;1,400&family=Inter:wght@300;400;500&display=swap');
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--green: #99cc00;
--black: #0f0f0f;
--white: #ffffff;
--off: #f5f5f3;
--mid: #888;
--dark: #444;
--serif: 'Cormorant Garamond', Georgia, serif;
--sans: 'Inter', sans-serif;
}
html, body {
width: 210mm;
font-family: var(--sans);
font-weight: 300;
color: var(--black);
background: var(--white);
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* ── PAGE ── */
.page {
width: 210mm;
min-height: 297mm;
display: flex;
flex-direction: column;
page-break-after: always;
break-after: page;
}
.page:last-child {
page-break-after: avoid;
break-after: avoid;
}
/* ── HEADER (shared) ── */
.header {
background: var(--green);
padding: 26px 44px 24px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.logo-main {
font-family: var(--sans);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--black);
display: block;
margin-bottom: 5px;
}
.logo-sub {
font-family: var(--sans);
font-size: 9px;
font-weight: 300;
letter-spacing: 0.22em;
text-transform: uppercase;
color: rgba(0,0,0,0.45);
display: block;
}
.header-tagline {
font-family: var(--sans);
font-size: 8.5px;
font-weight: 300;
letter-spacing: 0.1em;
color: rgba(0,0,0,0.45);
text-align: right;
line-height: 1.7;
}
/* ── BODY ── */
.body {
padding: 36px 44px 0;
flex: 1;
}
/* ── EYEBROW / SECTION LABEL ── */
.eyebrow {
font-size: 8px;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--mid);
margin-bottom: 10px;
}
/* ── RULE ── */
.rule {
border: none;
border-top: 1px solid #e2e2e2;
margin: 28px 0;
}
/* ── INTRO ── */
.intro-title {
font-family: var(--serif);
font-size: 34px;
font-weight: 300;
line-height: 1.08;
margin-bottom: 16px;
}
.intro-title em { font-style: italic; }
.intro-text {
font-size: 10px;
color: var(--dark);
line-height: 1.8;
max-width: 500px;
}
/* ── SERVICES ── */
.services {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px 44px;
}
.svc-num {
font-family: var(--serif);
font-size: 40px;
font-weight: 300;
color: #e5e5e5;
line-height: 1;
margin-bottom: 6px;
}
.svc-title {
font-size: 9.5px;
font-weight: 500;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--black);
margin-bottom: 3px;
}
.svc-sub {
font-family: var(--serif);
font-size: 13px;
font-style: italic;
color: #666;
margin-bottom: 10px;
}
.svc-list {
list-style: none;
}
.svc-list li {
font-size: 9px;
color: var(--dark);
padding: 4px 0 4px 12px;
border-bottom: 1px solid #f0f0f0;
position: relative;
line-height: 1.4;
}
.svc-list li::before {
content: '—';
position: absolute;
left: 0;
color: #bbb;
font-size: 8px;
}
.svc-list li:last-child { border-bottom: none; }
/* ── SUSTAINABILITY ── */
.sustain {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 3px;
}
.sustain-card {
background: var(--off);
padding: 16px 14px;
}
.sustain-card h4 {
font-size: 8px;
font-weight: 500;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--black);
margin-bottom: 7px;
}
.sustain-card p {
font-size: 8.5px;
color: var(--dark);
line-height: 1.7;
}
/* ── TEAM ── */
.team {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 36px;
}
.member {
border-top: 1.5px solid var(--black);
padding-top: 16px;
}
.member-name {
font-family: var(--serif);
font-size: 22px;
font-weight: 400;
margin-bottom: 4px;
}
.member-role {
font-size: 8px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mid);
margin-bottom: 14px;
}
.facts { list-style: none; }
.facts li {
display: flex;
gap: 10px;
padding: 6px 0;
border-bottom: 1px solid #f0f0f0;
font-size: 9px;
}
.facts li:last-child { border-bottom: none; }
.fk {
font-size: 7.5px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mid);
min-width: 72px;
flex-shrink: 0;
padding-top: 1px;
}
.fv { color: var(--dark); line-height: 1.5; }
/* ── CONTACT ── */
.contact {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0;
border-top: 1px solid #e2e2e2;
}
.contact-cell {
padding: 16px 14px 16px 0;
border-right: 1px solid #ebebeb;
}
.contact-cell:last-child { border-right: none; }
.ck {
font-size: 7.5px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--mid);
margin-bottom: 6px;
}
.cv {
font-size: 9px;
color: var(--black);
line-height: 1.55;
}
.cv a { color: var(--black); text-decoration: none; }
/* ── FOOTER ── */
.footer {
background: var(--black);
padding: 14px 44px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin-top: 28px;
}
.footer p, .footer a {
font-size: 8px;
letter-spacing: 0.08em;
color: rgba(255,255,255,0.35);
text-decoration: none;
}
/* ── PAGE 2 HEADER LABEL ── */
.page2-label {
font-size: 8px;
letter-spacing: 0.25em;
text-transform: uppercase;
color: rgba(0,0,0,0.4);
}
</style>
</head>
<body>
<!-- ══════════════════════════════════════════════════════
PAGE 1 — Intro + Services
══════════════════════════════════════════════════════ -->
<div class="page">
<div class="header">
<div>
<span class="logo-main">Marszalek Architekten</span>
<span class="logo-sub">Perchtoldsdorf · Wien</span>
</div>
<div class="header-tagline">
Ihr Architekt für<br>Neubau und Umbau
</div>
</div>
<div class="body">
<!-- Intro -->
<div class="eyebrow">Über das Büro</div>
<h1 class="intro-title">Architektur aus<br><em>zwei Generationen.</em></h1>
<p class="intro-text">
Marszalek Architekten ist ein inhabergeführtes Architekturbüro mit Sitz in Perchtoldsdorf, südlich von Wien.
Gegründet auf der Erfahrung zweier Generationen verbindet das Büro gestalterische Qualität mit unabhängiger
Planung, transparenter Projektabwicklung und nachhaltigem Bauen.<br><br>
Von der ersten Skizze bis zur schlüsselfertigen Übergabe begleiten wir unsere Auftraggeber
durch alle Leistungsphasen — als verlässlicher Partner für Wohn- und Gewerbeprojekte
in Wien und Niederösterreich.
</p>
<hr class="rule">
<!-- Services -->
<div class="eyebrow" style="margin-bottom:18px;">Leistungen</div>
<div class="services">
<div class="svc">
<div class="svc-num">01</div>
<p class="svc-title">Planen</p>
<p class="svc-sub">Von der Idee zum Plan</p>
<ul class="svc-list">
<li>Neubau &amp; Umbau</li>
<li>Denkmalschutz &amp; Revitalisierung</li>
<li>Innenraumplanung &amp; Möbeldesign</li>
<li>Dachgeschossausbau</li>
<li>Büro- &amp; Gewerbebau</li>
<li>Wellness- &amp; Freizeitanlagen</li>
</ul>
</div>
<div class="svc">
<div class="svc-num">02</div>
<p class="svc-title">Koordinieren</p>
<p class="svc-sub">Alles im Überblick</p>
<ul class="svc-list">
<li>Projektmanagement</li>
<li>Bauleitung</li>
<li>Kostenkontrolle</li>
<li>Terminplanung &amp; Koordination</li>
</ul>
</div>
<div class="svc">
<div class="svc-num">03</div>
<p class="svc-title">Überwachen</p>
<p class="svc-sub">Alles im Plan</p>
<ul class="svc-list">
<li>Qualitätssicherung</li>
<li>Rechnungsprüfung</li>
<li>Baustellenbesichtigungen</li>
<li>Transparente Dokumentation</li>
</ul>
</div>
<div class="svc">
<div class="svc-num">04</div>
<p class="svc-title">Beraten</p>
<p class="svc-sub">Auf gutem Grund bauen</p>
<ul class="svc-list">
<li>Liegenschaftsbewertung</li>
<li>Baurechtsberatung</li>
<li>Mediation</li>
<li>Sachverständigengutachten</li>
<li>Erstgespräch kostenlos (60 Min.)</li>
</ul>
</div>
</div>
</div><!-- /body -->
<div class="footer">
<p>© 2025 Marszalek Architekten · Perchtoldsdorf</p>
<p>Seite 1 / 2</p>
</div>
</div>
<!-- ══════════════════════════════════════════════════════
PAGE 2 — Sustainability + Team + Contact
══════════════════════════════════════════════════════ -->
<div class="page">
<div class="header">
<div>
<span class="logo-main">Marszalek Architekten</span>
<span class="logo-sub">Perchtoldsdorf · Wien</span>
</div>
<div class="header-tagline page2-label">Leistungsübersicht &amp; Kontakt</div>
</div>
<div class="body">
<!-- Sustainability -->
<div class="eyebrow" style="margin-bottom:14px;">Nachhaltiges Bauen</div>
<div class="sustain">
<div class="sustain-card">
<h4>Sanieren</h4>
<p>Historischen Charakter erhalten, moderne Standards erreichen.</p>
</div>
<div class="sustain-card">
<h4>Ökologie</h4>
<p>Ausschließlich umweltschonende Materialien.</p>
</div>
<div class="sustain-card">
<h4>Energie</h4>
<p>Passivhäuser, Geothermie, Wärmepumpen, Lüftung mit WRG.</p>
</div>
<div class="sustain-card">
<h4>Solar</h4>
<p>Solaranlagen &amp; Photovoltaik für nachhaltige Energieerzeugung.</p>
</div>
</div>
<hr class="rule">
<!-- Team -->
<div class="eyebrow" style="margin-bottom:18px;">Ihre Architekten</div>
<div class="team">
<div class="member">
<h2 class="member-name">Florian Marszalek</h2>
<p class="member-role">Dipl.-Ing. Architekt · Ziviltechniker</p>
<ul class="facts">
<li><span class="fk">Geboren</span><span class="fv">1977, Wien</span></li>
<li><span class="fk">Studium</span><span class="fv">Architektur, TU Wien</span></li>
<li><span class="fk">Befugt seit</span><span class="fv">2010</span></li>
<li><span class="fk">Schwerpunkte</span><span class="fv">Neubau, Umbau, Innenraumplanung</span></li>
</ul>
</div>
<div class="member">
<h2 class="member-name">Herbert Marszalek</h2>
<p class="member-role">Dipl.-Ing. Architekt · Ziviltechniker (ruhend)</p>
<ul class="facts">
<li><span class="fk">Geboren</span><span class="fv">1949, Wien</span></li>
<li><span class="fk">Studium</span><span class="fv">Architektur, TU Wien</span></li>
<li><span class="fk">Befugt seit</span><span class="fv">1983</span></li>
<li><span class="fk">Zusatz</span><span class="fv">Zertifizierter Mediator</span></li>
<li><span class="fk">Auszeichnung</span><span class="fv">Goldenes Ehrenzeichen für Verdienste um das Bundesland Niederösterreich</span></li>
</ul>
</div>
</div>
<hr class="rule">
<!-- Contact -->
<div class="eyebrow" style="margin-bottom:14px;">Kontakt</div>
<div class="contact">
<div class="contact-cell">
<p class="ck">Büro</p>
<p class="cv">Architekt Dipl.Ing.<br>Florian Marszalek</p>
</div>
<div class="contact-cell">
<p class="ck">Adresse</p>
<p class="cv">Elisabethstraße 14<br>2380 Perchtoldsdorf</p>
</div>
<div class="contact-cell">
<p class="ck">Telefon / Fax</p>
<p class="cv">+43 (1) 869 32 64</p>
</div>
<div class="contact-cell">
<p class="ck">E-Mail</p>
<p class="cv"><a href="mailto:office@marszalekarchitekten.at">office@marszalek<br>architekten.at</a></p>
</div>
<div class="contact-cell">
<p class="ck">Erstgespräch</p>
<p class="cv">Kostenlos<br>60 Minuten</p>
</div>
</div>
</div><!-- /body -->
<div class="footer">
<p>© 2025 Marszalek Architekten · Perchtoldsdorf</p>
<p>Seite 2 / 2</p>
</div>
</div>
</body>
</html>

Binary file not shown.

36
nginx.conf Normal file
View File

@@ -0,0 +1,36 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
charset utf-8;
# Security headers
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# HTML — no cache (easy updates)
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
add_header X-Content-Type-Options "nosniff";
}
# PDF — short cache, force download header
location ~* \.pdf$ {
add_header Content-Disposition "attachment";
add_header Cache-Control "public, max-age=86400";
add_header X-Content-Type-Options "nosniff";
}
# Static assets — long cache
location ~* \.(js|css|woff2?|png|jpg|jpeg|svg|ico|webp)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
# Gzip
gzip on;
gzip_types text/html text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
}

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "dev_web_marszalek",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"pdfkit": "^0.18.0",
"puppeteer-core": "^24.43.1"
}
}