Redesign site and modernize tooling
My Gatsby Deployment pipeline / build-and-deploy (push) Successful in 4m4s
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:
+183
-276
@@ -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 & 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'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'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 & Script & Markup Languages: </strong></p>
|
||||
<ul>
|
||||
<li>Python</li>
|
||||
<li>Java, J2EE</li>
|
||||
<li>JavaScript</li>
|
||||
<li>C# & .NET</li>
|
||||
<li>PHP</li>
|
||||
<li>HTML & CSS</li>
|
||||
<li>XML, JSON & 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 & 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 & 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'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't page anyone at 3 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
|
||||
|
||||
Reference in New Issue
Block a user