Redesign site and modernize tooling
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 4m4s

Replace the HTML5-UP "Strata" theme with a Swiss-minimal single-page
design (plain CSS, Space Grotesk / Hanken Grotesk / Space Mono). Rewrite
copy for current positioning: Staff Data, Analytics & AI Engineer at
Swisscom, open to new work. Experience shown as a pipeline timeline;
contact simplified to email + LinkedIn (drop the broken form).

Prune unused deps (lightbox, FontAwesome, sass/gatsby-plugin-sass) and
the old SCSS, fonts and gallery assets. Fix package.json metadata.

Harden the Gitea deploy: merge build+deploy into one job (removes the
deprecated artifact upload/download handoff), bump Node 18 -> 20, use
npx gatsby build. Rename .nvrmc -> .nvmrc and refresh the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:53:32 +02:00
parent 0c68d6dfb0
commit 8856e7c273
47 changed files with 690 additions and 5825 deletions
+183 -276
View File
@@ -2,294 +2,201 @@ import React from 'react'
import { Helmet } from 'react-helmet'
import Layout from '../components/layout'
// import Lightbox from 'react-images'
import Gallery from '../components/Gallery'
import avatar from '../assets/images/avatar.jpg'
import thumb01 from '../assets/images/thumbs/01.jpg'
import thumb02 from '../assets/images/thumbs/02.jpg'
import thumb03 from '../assets/images/thumbs/03.jpg'
import thumb04 from '../assets/images/thumbs/04.jpg'
import thumb05 from '../assets/images/thumbs/05.jpg'
import thumb06 from '../assets/images/thumbs/06.jpg'
import thumb07 from '../assets/images/thumbs/07.jpg'
import thumb08 from '../assets/images/thumbs/08.jpg'
import thumb09 from '../assets/images/thumbs/09.jpg'
const SITE_TITLE = 'Dennis Thiessen — Data, Analytics & AI Engineer'
const SITE_DESCRIPTION =
'Dennis Thiessen is a Staff Data, Analytics & AI Engineer in Bern, Switzerland, building the data pipelines and ML infrastructure that production systems run on.'
import full01 from '../assets/images/fulls/01.jpg'
import full02 from '../assets/images/fulls/02.jpg'
import full03 from '../assets/images/fulls/03.jpg'
import full04 from '../assets/images/fulls/04.jpg'
import full05 from '../assets/images/fulls/05.jpg'
import full06 from '../assets/images/fulls/06.jpg'
import full07 from '../assets/images/fulls/07.jpg'
import full08 from '../assets/images/fulls/08.jpg'
import full09 from '../assets/images/fulls/09.jpg'
const EMAIL = 'dennis@thiessen.io'
const LINKEDIN = 'https://www.linkedin.com/in/dennis-thiessen'
const DEFAULT_IMAGES = [
{ id: '9', src: full09, thumbnail: thumb09, caption: 'Swisscom (Switzerland) AG, Bern, Switzerland', description: 'Working as a (Senior) Data, Analytics and AI Engineer.'},
{ id: '8', src: full08, thumbnail: thumb08, caption: 'Robert Bosch GmbH, Dresden, Germany', description: 'Working as a (Senior) Data Engineer for 300mm Semiconductor Lab Startup.'},
{ id: '6', src: full06, thumbnail: thumb06, caption: 'Fraunhofer CML, Hamburg, Germany', description: 'Worked as Software Engineer and Research Associate with development tasks in C#, .NET and Python for workforce scheduling and machine learning.'},
{ id: '5', src: full05, thumbnail: thumb05, caption: 'Vizrt, Bergen, Norway', description: 'Worked as DevOps-Engineer with development tasks in Python and C++ for a backend transcoding engine. Video & Audio Output Test Automation as well as CI & CD with Jenkins.'},
{ id: '4', src: full04, thumbnail: thumb04, caption: 'Generali, Hamburg, Germany', description: 'Worked as Software-Developer and IT Consultant for a web-based workflow application written in Java, using OracleDB while migrating from BPM Process Engine to Camunda BPM. Moreover doing a (successful) PoC with Behaviour Driven Development.'},
{ id: '3', src: full03, thumbnail: thumb03, caption: 'Capgemini, Hamburg, Germany', description: 'Worked as Software-Developer in a multi-national distributed team for a international logistics client. Development of a package track & trace application in Java and with great focus on Test Automation.'},
{ id: '1', src: full01, thumbnail: thumb01, caption: 'Freelance - RiskAhead.net', description: 'Side-project where I developed a native android app in Java using REST services in PHP with access to a MySQL database. Huge focus on performance and scalability.'},
{ id: '2', src: full02, thumbnail: thumb02, caption: 'Freelance - MyIdealAd.com', description: 'Freelance side-project with process automation in ZAPIER, Python scripting, development and hosting of a Wordpress Web-Site.'}
];
// Career as a pipeline — most recent first. Each node is a role.
const ROLES = [
{
year: '2023 — PRESENT',
company: 'Swisscom',
location: 'Bern, CH',
title: 'Staff Data, Analytics & AI Engineer',
description:
'ETL and streaming pipelines in Python, Kafka and SAP BODS feeding a Teradata warehouse — and migrating the legacy stack onto AWS (Glue, Redshift, Lambda, Step Functions, Airflow).',
live: true,
},
{
year: '2020 — 2023',
company: 'Bosch Semiconductor',
location: 'Dresden, DE',
title: 'Senior Data Engineer',
description:
'Containerised ML inference (Docker, Kubernetes, Ansible) into 24/7 semiconductor production lines, and built Python, Java and C# data services over Oracle and Hadoop/Impala.',
},
{
year: '2018 — 2020',
company: 'Fraunhofer CML',
location: 'Hamburg, DE',
title: 'Research Software Engineer',
description:
'Optimisation-based crew-scheduling decision support in C#/.NET, plus research microservices and a Jenkins CI/CD pipeline with quality gates.',
},
{
year: '2017 — 2018',
company: 'Vizrt',
location: 'Bergen, NO',
title: 'DevOps Engineer',
description:
'A distributed video-transcoding backend in Python and C++, with automated audio/video integration testing for reliable releases.',
},
{
year: '2015 — 2017',
company: 'Generali',
location: 'Hamburg, DE',
title: 'Software Engineer & IT Consultant',
description:
'A Java/J2EE workflow portal on Oracle, and introduced behaviour-driven development and RPA automation (UiPath, Camunda).',
},
]
class HomeIndex extends React.Component {
const STACK = [
{ label: 'Languages', items: ['Python', 'SQL', 'Java', 'C#', 'TypeScript', 'C++'] },
{ label: 'Data & Pipelines', items: ['ETL / ELT', 'Kafka', 'Airflow', 'SAP BODS', 'Teradata', 'Hadoop / Impala'] },
{ label: 'Cloud & Infra', items: ['AWS', 'Docker', 'Kubernetes', 'Ansible', 'CI/CD'] },
{ label: 'ML & Observability', items: ['PyTorch', 'scikit-learn', 'ELK', 'Grafana', 'Prometheus'] },
{ label: 'Credentials', items: ['M.Eng., UniBw München', 'AWS Solutions Architect Associate', 'iSAQB CPSA-F'] },
]
constructor() {
super();
const Tags = ({ items }) =>
items.map((item, i) => (
<React.Fragment key={item}>
{i > 0 && ' · '}
<span className="t">{item}</span>
</React.Fragment>
))
this.state = {
invalid: false,
displayErrors: false,
res: null
}
this.handleSubmit = this.handleSubmit.bind(this)
this.closeLightbox = this.closeLightbox.bind(this);
this.gotoNext = this.gotoNext.bind(this);
this.gotoPrevious = this.gotoPrevious.bind(this);
this.openLightbox = this.openLightbox.bind(this);
this.handleClickImage = this.handleClickImage.bind(this);
}
const IndexPage = () => (
<Layout>
<Helmet>
<html lang="en" />
<meta charSet="utf-8" />
<title>{SITE_TITLE}</title>
<meta name="description" content={SITE_DESCRIPTION} />
<meta name="theme-color" content="#15171c" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap"
/>
</Helmet>
handleSubmit(event) {
event.preventDefault();
if (!event.target.checkValidity()) {
this.setState({
invalid: true,
displayErrors: true,
});
return;
}
const form = event.target;
const data = new FormData(form);
<div className="wrap">
<header className="topbar">
<span className="topbar__name">Dennis Thiessen</span>
<span className="status">
<span className="status__dot" aria-hidden="true" />
Open to new work
</span>
</header>
for (let name of data.keys()) {
const input = form.elements[name];
const parserName = input.dataset.parse;
console.log('parser name is', parserName);
if (parserName) {
const parsedValue = inputParsers[parserName](data.get(name))
data.set(name, parsedValue);
}
}
<section className="hero">
<div className="hero__text">
<p className="eyebrow">Data · Analytics · AI Engineering</p>
<h1 className="hero__name">Dennis Thiessen</h1>
<p className="hero__lead">
I build the pipelines and platforms that move data and run models in production.
</p>
<p className="hero__meta">
<strong>Staff Data, Analytics &amp; AI Engineer at Swisscom</strong> based in Bern,
Switzerland, with 8+ years across data engineering, ML infrastructure and DevOps.
</p>
<nav className="actions">
<a href={`mailto:${EMAIL}`}>{EMAIL}</a>
<a href={LINKEDIN} target="_blank" rel="noopener noreferrer">
LinkedIn
</a>
</nav>
</div>
<div className="portrait">
<img src={avatar} alt="Dennis Thiessen" />
</div>
</section>
data.append('token', 'gdfgdfashdf');
data.append('receiver', 'dennis@thiessen.io');
data.append('subject', 'Contact from www.thiessen.io');
this.setState({
res: stringifyFormData(data),
invalid: false,
displayErrors: false,
});
fetch('https://www.riskahead.de/api/v1/web/sendcustommail', {
method: 'POST',
body: data,
});
}
openLightbox (index, event) {
event.preventDefault();
this.setState({
currentImage: index,
lightboxIsOpen: true,
});
}
closeLightbox () {
this.setState({
currentImage: 0,
lightboxIsOpen: false,
});
}
gotoPrevious () {
this.setState({
currentImage: this.state.currentImage - 1,
});
}
gotoNext () {
this.setState({
currentImage: this.state.currentImage + 1,
});
}
handleClickImage () {
if (this.state.currentImage === this.props.images.length - 1) return;
this.gotoNext();
}
render() {
const siteTitle = "Dennis Thiessen - IT Software Engineering"
const siteDescription = "Portfolio of Dennis Thiessen - IT Software Engineering"
const { res, invalid, displayErrors } = this.state;
return (
<Layout>
<Helmet>
<title>{siteTitle}</title>
<meta name="description" content={siteDescription}/>
<html lang="en" />
<meta charset="utf-8"/>
</Helmet>
<div id="main">
<section id="one">
<header className="major">
<h3>Are you looking for a freelance Full-Stack Developer / Consultant? <br />Look no more!</h3>
</header>
<p>I&apos;m a <b>Software, Data & AI Engineer</b> and <b>Consultant</b> who has work experience in <b>Java</b>, <b>C#</b> and <b>Python</b>. I did multiple projects for various companies and below you can find a short resume and project portfolio.
If you are looking for a passionate <b>IT Freelancer</b>, don&apos;t hesitate to contact me. I mostly work in the D-A-CH area but I am also available for international remote work.</p>
<p>Here is a short enumeration of my skills & tools. Further below you can find the project & companies I have worked with. For a full CV, or if you need further information, just contact me.</p>
<p><strong>Programming &amp; Script &amp; Markup Languages: </strong></p>
<ul>
<li>Python</li>
<li>Java, J2EE</li>
<li>JavaScript</li>
<li>C# &amp; .NET</li>
<li>PHP</li>
<li>HTML &amp; CSS</li>
<li>XML, JSON &amp; YAML</li>
</ul>
<p><strong>Databases & DWH:</strong></p>
<ul>
<li>MySQL</li>
<li>PostgreSQL</li>
<li>Oracle</li>
<li>Teradata DWH</li>
</ul>
<p><strong>Tools &amp; Frameworks:</strong></p>
<ul>
<li>IDEs: IntelliJ, Android Studio, VS Code</li>
<li>Versioning: git (Gitlab, Gitea, Github)</li>
<li>Project Management: JIRA, Confluence</li>
<li>RPA: UIPath</li>
<li>Test Automation: JUnit, Selenium, Serenity BDD</li>
<li>Frameworks: Pandas, Numpy, Torch, .NET, Entity, Spring, PyMath</li>
<li>Build &amp; Virtualization: Jenkins, Docker, Kubernetes, Gitlab CI/CD</li>
<li>Web: Nginx, RESTless Web-Services</li>
<li>OS: Win, MacOs, Linux (Debian, RedHat, Ubuntu)</li>
</ul>
<p><strong>Certifications:</strong></p>
<ul>
<li>AWS Certified Solutions Architect Associate (AWS)</li>
<li>Certified Professional for Software Architecture, Foundation Level (ISAQB)</li>
<li>Robotic Process Automation / UIPath Developer Training (UIPath)</li>
<li>Camunda BPM Process Engine Basic & Advanced Training (Camunda Services GmbH)</li>
<li>ITIL v3 Foundation Certificate in IT Service Management (Serview GmbH)</li>
<li>IT Project Management (Integrata AG)</li>
</ul>
<p><strong>Spoken Languages:</strong></p>
<ul>
<li>German (mother tongue)</li>
<li>English (Fluent)</li>
<li>Russian (Basic)</li>
<li>Norwegian (Basic)</li>
</ul>
</section>
<section id="two">
<h2>Recent Work</h2>
<Gallery images={DEFAULT_IMAGES.map(({ id, src, thumbnail, caption, description }) => ({
src,
thumbnail,
caption,
description
}))} />
</section>
<section id="three">
<h2>Send request</h2>
<p>You can contact me via this contact form. <br />I will get in touch with you <b>ASAP</b>.</p>
<div className="row">
<div className="8u 12u$(small)">
<form onSubmit={this.handleSubmit} noValidate className={displayErrors ? 'displayErrors' : ''}>
<div className="row uniform 50%">
<div className="6u 12u$(xsmall)"><input type="text" name="name" id="name" placeholder="Name" required/></div>
<div className="6u 12u$(xsmall)"><input type="email" name="email" id="email" placeholder="Email" required/></div>
<div className="12u"><textarea name="text" id="text" placeholder="Message" rows="4" required></textarea></div>
</div>
<ul className="actions">
<br />
<li><input type="submit" value="Send Message" /></li>
</ul>
</form>
<div className="res-block">
{invalid && (
<ShakingError text="Form is not valid, please check your input." />
)}
{!invalid && res && (
<ShakingError text="Message sent successfully." />
)}
</div>
</div>
<div className="4u 12u$(small)">
<ul className="labeled-icons">
<li>
<h3 className="icon fa-home"><span className="label">Address</span></h3>
Region Bern / Zurich<br />
Switzerland
</li>
<li>
<h3 className="icon fa-mobile"><span className="label">Phone</span></h3>
(on request)
</li>
<li>
<h3 className="icon fa-envelope-o"><span className="label">Email</span></h3>
<a href="mailto:dennis@thiessen.io">dennis@thiessen.io</a>
</li>
</ul>
</div>
</div>
</section>
<section className="section">
<h2 className="section__label">Profile</h2>
<div className="prose">
<p>
I work where <strong>software engineering meets operations</strong> designing ETL/ELT
and streaming pipelines, containerising ML inference, and turning monolithic data
processes into event-driven, serverless systems.
</p>
<p>
Right now I&apos;m at Swisscom in Bern, moving a legacy Teradata and Oracle stack onto
AWS. What I care about most is <strong>automation and reliability</strong>: systems that
run themselves, recover on their own, and don&apos;t page anyone at 3&nbsp;a.m.
</p>
</div>
</section>
<section className="section">
<h2 className="section__label">Experience</h2>
<ol className="roles">
{ROLES.map((role) => (
<li className={`role${role.live ? ' role--live' : ''}`} key={role.company}>
<div className="role__rail" aria-hidden="true">
<span className="role__node" />
</div>
<div className="role__body">
<p className="role__year">{role.year}</p>
<div className="role__head">
<h3 className="role__co">{role.company}</h3>
<span className="role__loc">{role.location}</span>
</div>
</Layout>
)
}
}
<p className="role__title">{role.title}</p>
<p className="role__desc">{role.description}</p>
</div>
</li>
))}
</ol>
</section>
const inputParsers = {
uppercase(input) {
return input.toUpperCase();
},
number(input) {
return parseFloat(input);
},
};
<section className="section">
<h2 className="section__label">Stack</h2>
<div className="stack">
{STACK.map((group) => (
<div className="group" key={group.label}>
<p className="group__label">{group.label}</p>
<p className="group__tags">
<Tags items={group.items} />
</p>
</div>
))}
</div>
</section>
class ShakingError extends React.Component {
constructor() { super(); this.state = { key: 0 }; }
<section className="section">
<h2 className="section__label">Contact</h2>
<div>
<p className="contact__intro">
Open to interesting problems in data, ML and platform engineering in Switzerland or
remote. Email is the fastest way to reach me.
</p>
<a className="contact__email" href={`mailto:${EMAIL}`}>
{EMAIL}
</a>
<div className="contact__links">
<a href={LINKEDIN} target="_blank" rel="noopener noreferrer">
LinkedIn
</a>
{/* Add a GitHub profile URL here when you have one you want public. */}
</div>
</div>
</section>
componentWillReceiveProps() {
this.setState({ key: ++this.state.key });
}
render() {
return <div key={this.state.key} className="bounce">{this.props.text}</div>;
}
}
<footer className="colophon">
<span>© {new Date().getFullYear()} Dennis Thiessen</span>
<span>Bern, Switzerland</span>
</footer>
</div>
</Layout>
)
function stringifyFormData(fd) {
const data = {};
for (let key of fd.keys()) {
data[key] = fd.get(key);
}
return JSON.stringify(data, null, 2);
}
export default HomeIndex
export default IndexPage