Refactor portfolio and fix grammar
This commit is contained in:
parent
86ddc3d0d8
commit
59a0afbac7
37 changed files with 546 additions and 451 deletions
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
|
|
@ -1,12 +1,24 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"EEPROM",
|
||||
"Glsl",
|
||||
"andras",
|
||||
"decla",
|
||||
"favicons",
|
||||
"forex",
|
||||
"froms",
|
||||
"leds",
|
||||
"mesmerising",
|
||||
"optimisations",
|
||||
"optimised",
|
||||
"organiser",
|
||||
"realised",
|
||||
"schmelczer",
|
||||
"serialisation",
|
||||
"synchronised",
|
||||
"tabindex",
|
||||
"utilised",
|
||||
"utilising",
|
||||
"webm",
|
||||
"webp"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
"sass": "^1.26.10",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sharp": "^0.23.4",
|
||||
"string-replace-loader": "^2.3.0",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^2.3.8",
|
||||
"ts-loader": "^6.2.2",
|
||||
|
|
|
|||
41
src/data/ad-astra.ts
Normal file
41
src/data/ad-astra.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import adAstraPoster from '../static/media/ad_astra.jpg?format=jpg';
|
||||
import adAstraMp4 from '../static/media/ad_astra_720.mp4';
|
||||
import adAstraWebM from '../static/media/ad_astra_720.webm';
|
||||
|
||||
import { GitHub } from './shared';
|
||||
import { last } from '../helper/last';
|
||||
import { Video } from '../page/basics/video/video';
|
||||
|
||||
export const adAstraTimelineElement = {
|
||||
title: `Gaming on an ATtiny85`,
|
||||
date: `2020 Spring`,
|
||||
figure: new Video({
|
||||
poster: last(adAstraPoster.images)!.path,
|
||||
mp4: adAstraMp4,
|
||||
webm: adAstraWebM,
|
||||
}),
|
||||
description: `
|
||||
A simple game engine with a sample game set in space. The greatest challenge was to overcome
|
||||
the very limited resources of the hardware, this was also the most rewarding part.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
For reducing complexity while maintaining performance, a balance had to be found between object-oriented
|
||||
and structural programming. For example, a simple prototype-based inheritance is used for the game objects;
|
||||
meanwhile, an optimised SIMD utilising low-level driver is used for rendering to the display.
|
||||
I think, the codebase is quite readable and at the same time also fast, with the maximum frame times
|
||||
being between 15 and 20 milliseconds at a clock speed of 8 MHz. That means, it runs quite stably at 50-60 FPS.
|
||||
`,
|
||||
`
|
||||
As for the hardware, it is quite simple. Aside from the ATtiny85V, a D096-12864-SPI7 display is used for
|
||||
output and a TSOP4838 for input. The circuit runs on 3.3V, so a regulator is also needed. It uses a current
|
||||
of 8mA to 11mA on full brightness and around 1.5mA on standby mode.
|
||||
`,
|
||||
`
|
||||
There is also fault-tolerant persistent data storage utilising the built-in EEPROM.
|
||||
For creating sprites (which are also stored in EEPROM), I made a tool to convert PNG-s into C array definitions.
|
||||
This can also be found on GitHub along with the entire project.
|
||||
`,
|
||||
],
|
||||
links: [new GitHub('https://github.com/schmelczerandras/ad_astra')],
|
||||
};
|
||||
38
src/data/city-simulation.ts
Normal file
38
src/data/city-simulation.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import citySimulationPoster from '../static/media/simulation.jpg?format=jpg';
|
||||
import citySimulationWebM from '../static/media/simulation.webm';
|
||||
import citySimulationMp4 from '../static/media/simulation.mp4';
|
||||
|
||||
import { Video } from '../page/basics/video/video';
|
||||
import { last } from '../helper/last';
|
||||
|
||||
export const citySimulationTimelineElement = {
|
||||
date: `2018 July - August`,
|
||||
title: `City simulation`,
|
||||
figure: new Video({
|
||||
poster: last(citySimulationPoster.images)!.path,
|
||||
mp4: citySimulationMp4,
|
||||
webm: citySimulationWebM,
|
||||
}),
|
||||
description: `Simulating a city where car crashes are more frequent than usual.`,
|
||||
more: [
|
||||
`
|
||||
The state of the traffic lights can be changed through a REST API.
|
||||
Drivers follow the instructions of the traffic lights, so if a mistake is made,
|
||||
there will be collisions. There is also support for displaying tweets on a HUD.
|
||||
`,
|
||||
`
|
||||
This was created for a cybersecurity challenge. With the help of this program
|
||||
the contestants could instantly see the effect of their work.
|
||||
`,
|
||||
`
|
||||
The most interesting aspect of this project was building it in a server-client architecture.
|
||||
Every decision of the agents is calculated server-side. The real challenge was broadcasting
|
||||
these decisions in a fault-tolerant way using minimal bandwidth.
|
||||
`,
|
||||
`
|
||||
It is made with Unity using C# as the scripting language.
|
||||
The models and animations were also made by me using Blender.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
29
src/data/colors.ts
Normal file
29
src/data/colors.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import colourJpeg from '../static/media/color.jpg?format=jpg';
|
||||
import colourWebP from '../static/media/color.jpg?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
import { Open } from './shared';
|
||||
|
||||
export const colorsTimelineElement = {
|
||||
date: `2018 June`,
|
||||
title: `Photo colour grader`,
|
||||
figure: new Image(colourWebP, colourJpeg, `a picture of the app`),
|
||||
description: `An innovative (at least I thought so) colour grader web application.`,
|
||||
more: [
|
||||
`
|
||||
The most noteworthy feature of this application is the colour selector UI.
|
||||
This program is only intended as a proof-of-concept, I would have liked to
|
||||
experiment with some ideas and this was the outcome.
|
||||
`,
|
||||
`
|
||||
You can select some colours and then apply transformations to the other colours as a
|
||||
function of their distance to the selected colour.
|
||||
`,
|
||||
`
|
||||
By clicking on a coloured circle you can change its settings.
|
||||
New circles can be created by clicking in the large circle
|
||||
(and they can also be moved by drag & drop).
|
||||
`,
|
||||
],
|
||||
links: [new Open('color.schmelczer.dev')],
|
||||
};
|
||||
40
src/data/declared.ts
Normal file
40
src/data/declared.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import declaredJpeg from '../static/media/decla-red.png?format=jpg';
|
||||
import declaredWebP from '../static/media/decla-red.png?format=webp';
|
||||
import thesis from '../static/media/andras_schmelczer_thesis.pdf';
|
||||
|
||||
import { Preview } from '../page/basics/preview/preview';
|
||||
import { GitHub, Thesis, Open } from './shared';
|
||||
|
||||
export const declaredTimelineElement = {
|
||||
title: `Multiplayer game`,
|
||||
date: `2020 Autumn`,
|
||||
figure: new Preview(
|
||||
declaredWebP,
|
||||
declaredJpeg,
|
||||
'https://decla.red',
|
||||
'The website of the video game'
|
||||
),
|
||||
description: `
|
||||
Using SDF-2D (my ray tracing graphics library), I created a conquest-style multiplayer browser game.
|
||||
It even runs on mobiles.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
The scene is set in space, two teams have to conquer small planets, while they can also shoot at the other team.
|
||||
Points are given based on the number of planets controlled, and the first team which reaches a predefined score wins.
|
||||
`,
|
||||
`
|
||||
As for the communication, a server-client architecture is used. Messaging is provided by Socket.IO and
|
||||
a custom serialisation solution.
|
||||
`,
|
||||
`
|
||||
This (along with SDF-2D) was my BSc thesis project, so more in-depth information about them
|
||||
can be found in my thesis linked below.
|
||||
`,
|
||||
],
|
||||
links: [
|
||||
new GitHub('https://github.com/schmelczerandras/decla.red'),
|
||||
new Thesis(thesis),
|
||||
new Open('https://decla.red'),
|
||||
],
|
||||
};
|
||||
30
src/data/forex.ts
Normal file
30
src/data/forex.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import forexMp4 from '../static/media/forex.mp4';
|
||||
import forexWebM from '../static/media/forex.webm';
|
||||
import { Video } from '../page/basics/video/video';
|
||||
|
||||
export const forexTimelineElement = {
|
||||
title: `Predicting foreign exchange rates`,
|
||||
date: `2019 Autumn`,
|
||||
figure: new Video({
|
||||
mp4: forexMp4,
|
||||
webm: forexWebM,
|
||||
shouldActLikeGif: true,
|
||||
}),
|
||||
description: `
|
||||
From the animation, we can see that my implementation does a somewhat acceptable job at
|
||||
predicting (blue graph) the EUR/USD rates (green graph).
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
In a nutshell, the algorithm (written in Python using NumPy, SciPy, and Flask)
|
||||
predicts in the frequency domain. The steps are the following: smoothing the input values,
|
||||
differentiating, applying a short-time Fourier-transformation with overlapped (and Hanning-windowed) windows,
|
||||
extrapolating and then applying the inverse of these transformations to the resulting values.
|
||||
`,
|
||||
`
|
||||
Of course, there is still plenty of room for improvement, but even with this simple algorithm
|
||||
a mostly profitable trading strategy is viable. In my free time I may put more work into it.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
33
src/data/leds.ts
Normal file
33
src/data/leds.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import ledPoster from '../static/media/led.jpg?format=jpg';
|
||||
import ledMp4 from '../static/media/led.mp4';
|
||||
import ledWebM from '../static/media/led.webm';
|
||||
|
||||
import { last } from '../helper/last';
|
||||
import { Video } from '../page/basics/video/video';
|
||||
|
||||
export const ledsTimelineElement = {
|
||||
date: `2016 spring`,
|
||||
title: `Lights synchronised to music`,
|
||||
figure: new Video({
|
||||
poster: last(ledPoster.images)!.path,
|
||||
mp4: ledMp4,
|
||||
webm: ledWebM,
|
||||
}),
|
||||
description: `
|
||||
A full stack application with a built-in music player
|
||||
the output of which controls the colour of a couple of RGB LED strips.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
This was my first non-trivial project which got finished. Obviously,
|
||||
it is rather far from perfect, but I am still proud that I was able
|
||||
to build it on my own.
|
||||
`,
|
||||
`
|
||||
The backend logic is written in Python, the FFT implementation is provided by NumPy.
|
||||
A quite simple frontend for accessing the music player and changing
|
||||
the settings also got built using vanilla web development technologies.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
24
src/data/my-notes.ts
Normal file
24
src/data/my-notes.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import myNotesJpeg from '../static/media/my-notes.png?format=jpg';
|
||||
import myNotesWebP from '../static/media/my-notes.png?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
import { GitHub } from './shared';
|
||||
|
||||
export const myNotesTimelineElement = {
|
||||
date: `2019 November`,
|
||||
title: `My Notes`,
|
||||
figure: new Image(myNotesWebP, myNotesJpeg, `two screenshots of the application`),
|
||||
description: `A minimalist note organiser and editor powered by Markwon.`,
|
||||
more: [
|
||||
`
|
||||
This is a basic android app for creating and filtering markdown notes
|
||||
(based on #hashtags). It was my first exposure to Android development.
|
||||
`,
|
||||
`
|
||||
All in all, it is not a tremendous engineering feat, but at least it's usable.
|
||||
The knowledge gained while working on it was the more significant outcome of this
|
||||
adventure.
|
||||
`,
|
||||
],
|
||||
links: [new GitHub('https://github.com/schmelczerandras/my-notes')],
|
||||
};
|
||||
28
src/data/nuclear-editor.ts
Normal file
28
src/data/nuclear-editor.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import processSimulatorInputJpeg from '../static/media/process-simulator-input.jpg?format=jpg';
|
||||
import processSimulatorInputWebP from '../static/media/process-simulator-input.jpg?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
|
||||
export const nuclearEditorTimelineElement = {
|
||||
date: `2018 October - November`,
|
||||
title: `Graph editing application`,
|
||||
figure: new Image(
|
||||
processSimulatorInputWebP,
|
||||
processSimulatorInputJpeg,
|
||||
`a picture of the simulator's UI`
|
||||
),
|
||||
description: `
|
||||
An intuitive editor to create and edit input for the nuclear facility simulator.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
Nodes can be moved with drag & drop gestures.
|
||||
Editing the parameters of elements can be done on the right panel.
|
||||
`,
|
||||
`
|
||||
The UI is built with JavaFX. The output can be exported as JSON or
|
||||
directly uploaded to the simulation backend.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
39
src/data/nuclear.ts
Normal file
39
src/data/nuclear.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import processSimulatorJpeg from '../static/media/process-simulator.jpg?format=jpg';
|
||||
import processSimulatorWebP from '../static/media/process-simulator.jpg?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
|
||||
export const nuclearTimelineElement = {
|
||||
date: `2018 October - November`,
|
||||
title: `Simulating the cooling system of a nuclear facility`,
|
||||
figure: new Image(
|
||||
processSimulatorWebP,
|
||||
processSimulatorJpeg,
|
||||
`a screenshot of the simulator`
|
||||
),
|
||||
description: `
|
||||
The temperatures and flow velocities are dynamically calculated in a fluid-based
|
||||
cooling system based on a simple model.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
A simulated system can contain reactors (heaters), coolers, pumps, heat exchangers,
|
||||
drains, sources, and of course, pipes. With this, simple yet believable configurations
|
||||
can be defined. The aim of the project was to create a cheaply calculated and
|
||||
(for layman) a convincingly looking simulation.
|
||||
`,
|
||||
`
|
||||
The algorithm takes advantages of graphs and matrices to get to a next time frame. First,
|
||||
water flows are distributed by traversing the graph of pipes. Then a matrix is populated
|
||||
with the relations of the nodes (based on the water flow between them).
|
||||
After considering the base temperatures and heaters, the matrix is solved resulting in the
|
||||
current temperature of each node. This can be iteratively continued.
|
||||
`,
|
||||
`
|
||||
Python is used for the backend along with Flask and NumPy. A REST API facilitates
|
||||
the communication between the layers. For rendering on the frontend, a HTML5 canvas
|
||||
is utilised.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
13
src/data/photos.ts
Normal file
13
src/data/photos.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import photosJpeg from '../static/media/photos.jpg?format=jpg';
|
||||
import photosWebP from '../static/media/photos.jpg?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
import { Open } from './shared';
|
||||
|
||||
export const photosTimelineElement = {
|
||||
date: `2016 summer`,
|
||||
title: `Photos`,
|
||||
figure: new Image(photosWebP, photosJpeg, `a picture of the website`),
|
||||
description: `A simple web page where you can view my photos.`,
|
||||
links: [new Open('https://photo.schmelczer.dev')],
|
||||
};
|
||||
21
src/data/platform-game.ts
Normal file
21
src/data/platform-game.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import platformJpeg from '../static/media/platform.png?format=jpg';
|
||||
import platformWebP from '../static/media/platform.png?format=webp';
|
||||
|
||||
import { Image } from '../page/basics/image/image';
|
||||
|
||||
export const platformGameTimelineElement = {
|
||||
date: `2017 autumn`,
|
||||
title: `Platform game`,
|
||||
figure: new Image(platformWebP, platformJpeg, `a picture of the app`),
|
||||
description: `A 3D game written in C with the help of SDL 1.2 (I haven't heard of GPU programming at the time).`,
|
||||
more: [
|
||||
`
|
||||
The maps are randomly generated and fully destroyable.
|
||||
The player is getting chased by flying enemies. Overall, I find it a really enjoyable game.
|
||||
`,
|
||||
`
|
||||
I did this as a homework for my Basics of Programming course.
|
||||
`,
|
||||
],
|
||||
links: [],
|
||||
};
|
||||
41
src/data/sdf2d.ts
Normal file
41
src/data/sdf2d.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import sdf2dJpeg from '../static/media/sdf2d.png?format=jpg';
|
||||
import sdf2dWebP from '../static/media/sdf2d.png?format=webp';
|
||||
|
||||
import { Preview } from '../page/basics/preview/preview';
|
||||
import { GitHub, Open, NPM } from './shared';
|
||||
|
||||
export const sdf2dTimelineElement = {
|
||||
title: `2D ray tracing`,
|
||||
date: `2020 Autumn`,
|
||||
figure: new Preview(
|
||||
sdf2dWebP,
|
||||
sdf2dJpeg,
|
||||
'https://sdf2d.schmelczer.dev',
|
||||
'A webpage showcasing the SDF-2D project.'
|
||||
),
|
||||
description: `
|
||||
I created the SDF-2D library for efficiently rendering 2D scenes using ray tracing.
|
||||
My solution relies on signed distance fields (SDF-s), it supports both WebGL and WebGL2,
|
||||
and is an easily reusable and extendible NPM package.
|
||||
`,
|
||||
more: [
|
||||
`
|
||||
A multitude of optimisations were needed to achieve real-time performance even on low-end mobile devices.
|
||||
These include deferred shading, tile-based rendering, and dynamic shader generation to eliminate unnecessary
|
||||
instructions.
|
||||
`,
|
||||
`
|
||||
The result is a reusable library written in TypeScript with a — subjectively — simple and elegant API.
|
||||
For more information please check out the GitHub repository or the NPM package itself. Or simply enjoy the
|
||||
mesmerising demo scenes.
|
||||
`,
|
||||
`
|
||||
Creating this library package is also covered in my thesis (available above).
|
||||
`,
|
||||
],
|
||||
links: [
|
||||
new GitHub('https://github.com/schmelczerandras/sdf-2d'),
|
||||
new NPM('https://www.npmjs.com/package/sdf-2d'),
|
||||
new Open('https://sdf2d.schmelczer.dev'),
|
||||
],
|
||||
};
|
||||
11
src/data/shared.ts
Normal file
11
src/data/shared.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { ImageAnchorFactory } from '../page/basics/image-anchor/image-anchor';
|
||||
|
||||
import githubIcon from '../static/icons/github.svg';
|
||||
import openIcon from '../static/icons/open.svg';
|
||||
import cvIcon from '../static/icons/cv.svg';
|
||||
import packageIcon from '../static/icons/package.svg';
|
||||
|
||||
export const GitHub = ImageAnchorFactory(githubIcon, 'Open on GitHub');
|
||||
export const NPM = ImageAnchorFactory(packageIcon, 'Open on npm');
|
||||
export const Open = ImageAnchorFactory(openIcon, 'Open in new tab');
|
||||
export const Thesis = ImageAnchorFactory(cvIcon, 'Download thesis');
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import './text.scss';
|
||||
import { html } from '../../../types/html';
|
||||
|
||||
export const generate = (text: string): html => `
|
||||
<p class="text">${text}</p>
|
||||
`;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
@use '../../../style/mixins' as *;
|
||||
|
||||
.text {
|
||||
@include main-font();
|
||||
text-align: left;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { PageElement } from '../../page-element';
|
||||
import { createElement } from '../../../helper/create-element';
|
||||
import { generate } from './text.html';
|
||||
|
||||
export class Text extends PageElement {
|
||||
public constructor(text: string) {
|
||||
super(createElement(generate(text)));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,22 @@ import { url } from '../../../types/url';
|
|||
import { html } from '../../../types/html';
|
||||
|
||||
export const generate = ({
|
||||
poster,
|
||||
options,
|
||||
webm,
|
||||
mp4,
|
||||
poster,
|
||||
shouldActLikeGif,
|
||||
container,
|
||||
}: {
|
||||
poster?: url;
|
||||
options?: string;
|
||||
webm: url;
|
||||
mp4: url;
|
||||
poster?: url;
|
||||
shouldActLikeGif?: boolean;
|
||||
container?: boolean;
|
||||
}): html => `
|
||||
${container === undefined || container ? `<div class="figure-container">` : ''}
|
||||
<video loading="lazy" ${options} ${poster ? `poster="${poster}` : ''}" >
|
||||
<video loading="lazy" ${
|
||||
shouldActLikeGif ? 'autoplay loop muted' : ''
|
||||
} controls playsinline preload="metadata" ${poster ? `poster="${poster}` : ''}" >
|
||||
<source src="${webm}" type="video/webm"/>
|
||||
<source src="${mp4}" type="video/mp4"/>
|
||||
</video>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export class Video extends PageElement {
|
|||
poster?: url;
|
||||
mp4: url;
|
||||
webm: url;
|
||||
options?: string;
|
||||
shouldActLikeGif?: boolean;
|
||||
container?: boolean;
|
||||
}) {
|
||||
super(createElement(generate(options)));
|
||||
|
|
|
|||
|
|
@ -1 +1,5 @@
|
|||
@use '../../style/mixins' as *;
|
||||
|
||||
.content {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { createElement } from '../../helper/create-element';
|
||||
import { Content } from '../../types/portfolio';
|
||||
|
||||
import { generate } from './content.html';
|
||||
import { PageElement } from '../page-element';
|
||||
|
||||
export class PageContent extends PageElement {
|
||||
public constructor(content: Content) {
|
||||
public constructor(content: Array<string>) {
|
||||
super(createElement(generate()));
|
||||
content.forEach(c => this.attachElement(c));
|
||||
content.map(t => {
|
||||
const p = createElement(`<p>${t}</p>`);
|
||||
this.htmlRoot.appendChild(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Footer } from '../../types/portfolio';
|
||||
import './footer.scss';
|
||||
import cvIcon from '../../static/icons/cv.svg';
|
||||
import emailIcon from '../../static/icons/email.svg';
|
||||
import { html } from '../../types/html';
|
||||
import { FooterParameters } from './footer';
|
||||
|
||||
export const generate = ({
|
||||
title,
|
||||
|
|
@ -10,7 +10,7 @@ export const generate = ({
|
|||
curriculaVitae,
|
||||
lastEditText,
|
||||
lastEdit,
|
||||
}: Footer): html => `
|
||||
}: FooterParameters): html => `
|
||||
<footer id="footer">
|
||||
<h2>${title}</h2>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import { Footer } from '../../types/portfolio';
|
||||
import { PageElement } from '../page-element';
|
||||
|
||||
import { generate } from './footer.html';
|
||||
import { createElement } from '../../helper/create-element';
|
||||
import { url } from '../../types/url';
|
||||
|
||||
export interface FooterParameters {
|
||||
title: string;
|
||||
email: string;
|
||||
curriculaVitae: Array<{
|
||||
name: string;
|
||||
url: url;
|
||||
}>;
|
||||
lastEditText: string;
|
||||
lastEdit: Date;
|
||||
}
|
||||
|
||||
export class PageFooter extends PageElement {
|
||||
constructor(footer: Footer) {
|
||||
constructor(footer: FooterParameters) {
|
||||
super(createElement(generate(footer)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import './header.scss';
|
||||
import { Header } from '../../types/portfolio';
|
||||
|
||||
import { html } from '../../types/html';
|
||||
|
||||
export const generate = ({ name }: Header): html => `
|
||||
export const generate = (name: string): html => `
|
||||
<section id="about">
|
||||
<div class="picture"></div>
|
||||
<div class="placeholder"></div>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { PageContent } from '../content/content';
|
||||
import { Header } from '../../types/portfolio';
|
||||
|
||||
import { generate } from './header.html';
|
||||
import { createElement } from '../../helper/create-element';
|
||||
import { PageThemeSwitcher } from '../theme-switcher/theme-switcher';
|
||||
import { PageElement } from '../page-element';
|
||||
import { Image } from '../basics/image/image';
|
||||
|
||||
export class PageHeader extends PageElement {
|
||||
public constructor(header: Header) {
|
||||
super(createElement(generate(header)));
|
||||
this.attachElementByReplacing('.picture', header.picture);
|
||||
public constructor(header: { name: string; photo: Image; about: Array<string> }) {
|
||||
super(createElement(generate(header.name)));
|
||||
|
||||
this.attachElementByReplacing('.picture', header.photo);
|
||||
this.attachElement(new PageContent(header.about));
|
||||
this.attachElement(new PageThemeSwitcher());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { TimelineElement } from '../../../types/portfolio';
|
||||
import info from '../../../static/icons/info.svg';
|
||||
import './timeline-element.scss';
|
||||
import { html } from '../../../types/html';
|
||||
import { TimelineElementParameters } from './timeline-element';
|
||||
|
||||
export const generate = (
|
||||
{ date, title, more }: TimelineElement,
|
||||
{ date, title, more }: TimelineElementParameters,
|
||||
showMore: string
|
||||
): html => {
|
||||
const id = titleToFragment(title);
|
||||
|
|
@ -18,7 +18,7 @@ export const generate = (
|
|||
<div class="figure"></div>
|
||||
<div class="lower">
|
||||
<h2><a href="#${id}">${title}</a></h2>
|
||||
<div class="description"></div>
|
||||
<p class="description"></p>
|
||||
${more ? '<div class="more"></div>' : ''}
|
||||
<div class="buttons">
|
||||
${
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@
|
|||
padding-bottom: var(--small-margin);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
padding: 0 8px 8px 8px;
|
||||
padding: 0 8px var(--small-margin) 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import { TimelineElement } from '../../../types/portfolio';
|
||||
import { PageContent } from '../../content/content';
|
||||
import { PageElement } from '../../page-element';
|
||||
import { generate } from './timeline-element.html';
|
||||
import { createElement } from '../../../helper/create-element';
|
||||
import { Video } from '../../basics/video/video';
|
||||
import { Image } from '../../basics/image/image';
|
||||
import { Preview } from '../../basics/preview/preview';
|
||||
|
||||
export interface TimelineElementParameters {
|
||||
date: string;
|
||||
figure: Image | Video | Preview;
|
||||
title: string;
|
||||
description: string;
|
||||
more?: Array<string>;
|
||||
links: Array<PageElement>;
|
||||
}
|
||||
|
||||
export class PageTimelineElement extends PageElement {
|
||||
private isOpen: boolean;
|
||||
|
|
@ -11,7 +22,7 @@ export class PageTimelineElement extends PageElement {
|
|||
private showLess: string;
|
||||
|
||||
public constructor(
|
||||
timelineElement: TimelineElement,
|
||||
timelineElement: TimelineElementParameters,
|
||||
showMore: string,
|
||||
showLess: string
|
||||
) {
|
||||
|
|
@ -30,7 +41,7 @@ export class PageTimelineElement extends PageElement {
|
|||
} else super(root);
|
||||
|
||||
this.attachElementByReplacing('.figure', timelineElement.figure);
|
||||
this.attachElementByReplacing('.description', timelineElement.description);
|
||||
this.query('.description').innerText = timelineElement.description;
|
||||
timelineElement.links.forEach(l => this.attachElementAsChildOf('.buttons', l));
|
||||
|
||||
this.showMore = showMore;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import { Timeline } from '../../types/portfolio';
|
||||
import { PageTimelineElement } from './timeline-element/timeline-element';
|
||||
import {
|
||||
PageTimelineElement,
|
||||
TimelineElementParameters,
|
||||
} from './timeline-element/timeline-element';
|
||||
import { generate } from './timeline.html';
|
||||
import { createElement } from '../../helper/create-element';
|
||||
import { PageElement } from '../page-element';
|
||||
|
||||
export class PageTimeline extends PageElement {
|
||||
public constructor({ elements, showMoreText, showLessText }: Timeline) {
|
||||
public constructor({
|
||||
elements,
|
||||
showMoreText,
|
||||
showLessText,
|
||||
}: {
|
||||
showMoreText: string;
|
||||
showLessText: string;
|
||||
elements: Array<TimelineElementParameters>;
|
||||
}) {
|
||||
super(createElement(generate()));
|
||||
elements.forEach(e =>
|
||||
this.attachElement(new PageTimelineElement(e, showMoreText, showLessText))
|
||||
|
|
|
|||
393
src/portfolio.ts
393
src/portfolio.ts
|
|
@ -1,375 +1,65 @@
|
|||
import meJpeg from './static/media/me.jpg?format=jpg';
|
||||
import meWebP from './static/media/me.jpg?format=webp';
|
||||
import cvEnglish from './static/media/cv_andras_schmelczer.pdf';
|
||||
|
||||
import { PageFooter } from './page/footer/footer';
|
||||
import { Video } from './page/basics/video/video';
|
||||
import { Text } from './page/basics/text/text';
|
||||
import { Image } from './page/basics/image/image';
|
||||
import { PageHeader } from './page/header/header';
|
||||
import { PageTimeline } from './page/timeline/timeline';
|
||||
import { PageImageViewer } from './page/image-viewer/image-viewer';
|
||||
import { last } from './helper/last';
|
||||
import { PageBackground } from './page/background/background';
|
||||
|
||||
import { Main } from './page/main/main';
|
||||
import { ImageAnchorFactory } from './page/basics/image-anchor/image-anchor';
|
||||
import { Preview } from './page/basics/preview/preview';
|
||||
|
||||
import meJpeg from './static/media/me.jpg?format=jpg';
|
||||
import meWebP from './static/media/me.jpg?format=webp';
|
||||
|
||||
import declaredJpeg from './static/media/decla-red.png?format=jpg';
|
||||
import declaredWebP from './static/media/decla-red.png?format=webp';
|
||||
|
||||
import sdf2dJpeg from './static/media/sdf2d.png?format=jpg';
|
||||
import sdf2dWebP from './static/media/sdf2d.png?format=webp';
|
||||
|
||||
import myNotesJpeg from './static/media/my-notes.png?format=jpg';
|
||||
import myNotesWebP from './static/media/my-notes.png?format=webp';
|
||||
|
||||
import processSimulatorJpeg from './static/media/process-simulator.jpg?format=jpg';
|
||||
import processSimulatorWebP from './static/media/process-simulator.jpg?format=webp';
|
||||
|
||||
import processSimulatorInputJpeg from './static/media/process-simulator-input.jpg?format=jpg';
|
||||
import processSimulatorInputWebP from './static/media/process-simulator-input.jpg?format=webp';
|
||||
|
||||
import citySimulationJpeg from './static/media/simulation.jpg?format=jpg';
|
||||
import citySimulationWebP from './static/media/simulation.jpg?format=webp';
|
||||
|
||||
import colourJpeg from './static/media/color.jpg?format=jpg';
|
||||
import colourWebP from './static/media/color.jpg?format=webp';
|
||||
|
||||
import platformJpeg from './static/media/platform.png?format=jpg';
|
||||
import platformWebP from './static/media/platform.png?format=webp';
|
||||
|
||||
import photosJpeg from './static/media/photos.jpg?format=jpg';
|
||||
import photosWebP from './static/media/photos.jpg?format=webp';
|
||||
|
||||
import cvEnglish from './static/media/cv_andras_schmelczer.pdf';
|
||||
import thesis from './static/media/andras_schmelczer_thesis.pdf';
|
||||
|
||||
import forexMp4 from './static/media/forex.mp4';
|
||||
import forexWebM from './static/media/forex.webm';
|
||||
|
||||
import adAstraPoster from './static/media/ad_astra.jpg?format=jpg';
|
||||
import adAstraMp4 from './static/media/ad_astra_720.mp4';
|
||||
import adAstraWebM from './static/media/ad_astra_720.webm';
|
||||
|
||||
import ledPoster from './static/media/led.jpg?format=jpg';
|
||||
import ledMp4 from './static/media/led.mp4';
|
||||
import ledWebM from './static/media/led.webm';
|
||||
|
||||
import githubIcon from './static/icons/github.svg';
|
||||
import openIcon from './static/icons/open.svg';
|
||||
import cvIcon from './static/icons/cv.svg';
|
||||
import { Body } from './page/body/body';
|
||||
import { declaredTimelineElement } from './data/declared';
|
||||
import { sdf2dTimelineElement } from './data/sdf2d';
|
||||
import { adAstraTimelineElement } from './data/ad-astra';
|
||||
import { forexTimelineElement } from './data/forex';
|
||||
import { myNotesTimelineElement } from './data/my-notes';
|
||||
import { nuclearTimelineElement } from './data/nuclear';
|
||||
import { nuclearEditorTimelineElement } from './data/nuclear-editor';
|
||||
import { citySimulationTimelineElement } from './data/city-simulation';
|
||||
import { colorsTimelineElement } from './data/colors';
|
||||
import { platformGameTimelineElement } from './data/platform-game';
|
||||
import { photosTimelineElement } from './data/photos';
|
||||
import { ledsTimelineElement } from './data/leds';
|
||||
|
||||
export const create = () => {
|
||||
const GitHub = ImageAnchorFactory(githubIcon, 'Open on GitHub');
|
||||
const Open = ImageAnchorFactory(openIcon, 'Open in new tab');
|
||||
const Thesis = ImageAnchorFactory(cvIcon, 'Download thesis');
|
||||
|
||||
new Body(
|
||||
new Main(
|
||||
new PageBackground(1, 1),
|
||||
new PageHeader({
|
||||
name: `András Schmelczer`,
|
||||
picture: new Image(meWebP, meJpeg, `a picture of me`, false),
|
||||
photo: new Image(meWebP, meJpeg, `a picture of me`, false),
|
||||
about: [
|
||||
new Text(`I have always been fascinated by the engineering feats that surround us and pervade every aspect
|
||||
of our lives. When I realised I might someday be able to contribute to this field, I knew that
|
||||
this would become my life’s ambition. As I am finishing my last semester at the
|
||||
Budapest University of Technology and Economics, I feel I am getting closer to it every day.`),
|
||||
new Text(`Look at some of the more interesting projects I have worked on. They are all listed below.
|
||||
Further information about me can be found at the bottom of the page.`),
|
||||
`
|
||||
I have always been fascinated by the engineering feats that surround us and pervade every aspect
|
||||
of our lives. When I realised I might someday be able to contribute to this field, I knew that
|
||||
this would become my life’s ambition.
|
||||
As I am finishing my last semester at the Budapest University of Technology and Economics,
|
||||
I feel I am getting closer to my ambition every day.
|
||||
`,
|
||||
`
|
||||
Discover some of my more interesting projects. They are all listed below.
|
||||
Further information about me can be found at the bottom of the page.
|
||||
`,
|
||||
],
|
||||
}),
|
||||
new PageTimeline({
|
||||
showMoreText: `Show details`,
|
||||
showLessText: `Show less`,
|
||||
elements: [
|
||||
{
|
||||
title: `Multiplayer game`,
|
||||
date: `2020 Autumn`,
|
||||
figure: new Preview(
|
||||
declaredWebP,
|
||||
declaredJpeg,
|
||||
'https://decla.red',
|
||||
'The website of the video game'
|
||||
),
|
||||
description: new Text(
|
||||
`Using SDF-2D, I developed a conquest-style multiplayer browser game. It even runs on mobiles.`
|
||||
),
|
||||
more: [
|
||||
new Text(`The scene is set in space, two teams have to conquer small planets, while they can also shoot at the other team.
|
||||
Points are given based on the number of planets controlled,
|
||||
and the first team which reaches a predefined score wins.`),
|
||||
new Text(`As for the communication, a server-client architecture is used. Messaging is provided by Socket.IO and a custom
|
||||
serialisation solution.`),
|
||||
new Text(`This (along with SDF-2D) was my BSc thesis project, so more in-depth information about them
|
||||
can be found in my thesis linked below.`),
|
||||
],
|
||||
links: [
|
||||
new GitHub('https://github.com/schmelczerandras/decla.red'),
|
||||
new Thesis(thesis),
|
||||
new Open('https://decla.red'),
|
||||
],
|
||||
},
|
||||
{
|
||||
title: `2D ray tracing`,
|
||||
date: `2020 Autumn`,
|
||||
figure: new Preview(
|
||||
sdf2dWebP,
|
||||
sdf2dJpeg,
|
||||
'https://sdf2d.schmelczer.dev',
|
||||
'A webpage showcasing the SDF-2D project.'
|
||||
),
|
||||
description: new Text(`I created the SDF-2D library for efficiently rendering 2D scenes using ray tracing.
|
||||
My solution relies on signed distance fields (SDF-s), it supports both WebGL and WebGL2,
|
||||
and is an easily reusable and extendible NPM package.`),
|
||||
more: [
|
||||
new Text(`A multitude of optimisations were needed to achieve real-time performance even on low-end mobile devices.
|
||||
These include deferred shading, tile-based rendering, and dynamic shader generation to eliminate unnecessary
|
||||
instructions. Additionally, there were some interesting quirks of specific hardware that also needed to be overcome.`),
|
||||
new Text(`The end result is a reusable library written in TypeScript with a — subjectively — simple and elegant API.
|
||||
For more information please check out the GitHub repository or the NPM package itself. Or simply enjoy the
|
||||
mesmerizing demo scenes.`),
|
||||
],
|
||||
links: [
|
||||
new GitHub('https://github.com/schmelczerandras/sdf-2d'),
|
||||
new Open('https://sdf2d.schmelczer.dev'),
|
||||
],
|
||||
},
|
||||
{
|
||||
title: `Video game on an ATtiny85`,
|
||||
date: `2020 Spring`,
|
||||
figure: new Video({
|
||||
poster: last(adAstraPoster.images)!.path,
|
||||
mp4: adAstraMp4,
|
||||
webm: adAstraWebM,
|
||||
options: `controls playsinline preload="none"`,
|
||||
}),
|
||||
description: new Text(`A simple game engine with a sample game set in space. The greatest challenge was to overcome
|
||||
the very limited resources of the hardware, this was also the most rewarding part.`),
|
||||
more: [
|
||||
new Text(`For reducing complexity while maintaining performance, a balance had to be found between object-oriented
|
||||
and structural programming. For example, a simple prototype-based inheritance is used for the game objects.
|
||||
Meanwhile, an optimized SIMD utilizing low-level driver is used for drawing on the display.
|
||||
I think the codebase is quite readable and at the same time the
|
||||
maximum frame times are between 15 ms and 20 ms at 8 MHz clock speed, which I find quite impressive.`),
|
||||
new Text(`As for the hardware, it is rather simple. Aside from the ATtiny85V, a D096-12864-SPI7 display is used for
|
||||
output and a TSOP4838 for input. The circuit runs on 3.3V, so a regulator is also needed. It uses a current
|
||||
of 8mA to 11mA on full brightness and around 1.5mA on standby mode.`),
|
||||
new Text(
|
||||
`There is also fault-tolerant persistent data storage using the built-in EEPROM.
|
||||
For creating sprites (which are also stored in EEPROM) I made a tool to convert PNG-s into C code.
|
||||
This can also be found on GitHub as well as the entire project.`
|
||||
),
|
||||
],
|
||||
links: [new GitHub('https://github.com/schmelczerandras/ad_astra')],
|
||||
},
|
||||
|
||||
{
|
||||
title: `Predicting foreign exchange rates`,
|
||||
date: `2019 Autumn`,
|
||||
figure: new Video({
|
||||
mp4: forexMp4,
|
||||
webm: forexWebM,
|
||||
options: `autoplay loop muted playsinline controls`,
|
||||
}),
|
||||
description: new Text(
|
||||
`From the animation we can see that my algorithm does a somewhat acceptable job at
|
||||
predicting (blue graph) the EUR/USD rates (green graph).`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`In a nutshell, the algorithm (written with Python - NumPy, SciPy, Flask),
|
||||
extrapolates in the frequency domain. The steps are the following: smoothing the input values,
|
||||
differentiating, applying a short-time Fourier-transformation with overlapped (and Hanning-windowed) windows,
|
||||
extrapolating and then applying the inverse of these transformations to the extrapolated values.`
|
||||
),
|
||||
new Text(
|
||||
`Of course, there is still plenty of room for improvement, but even with this simple algorithm
|
||||
a mostly profitable trading strategy is viable. In my free time I may put more work into it.`
|
||||
),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
date: `2019 November`,
|
||||
title: `My Notes`,
|
||||
figure: new Image(
|
||||
myNotesWebP,
|
||||
myNotesJpeg,
|
||||
`two screenshots of the application`
|
||||
),
|
||||
description: new Text(
|
||||
`A minimalist note organizer and editor powered by Markwon.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`A basic android app for creating and filtering notes written in markdown.`
|
||||
),
|
||||
new Text(
|
||||
`It was my homework for BME's Android and web development course.
|
||||
It was also my first experience with Android development.`
|
||||
),
|
||||
],
|
||||
links: [new GitHub('https://github.com/schmelczerandras/my-notes')],
|
||||
},
|
||||
{
|
||||
date: `2018 October - November`,
|
||||
title: `Simulating the cooling system of a nuclear facility`,
|
||||
figure: new Image(
|
||||
processSimulatorWebP,
|
||||
processSimulatorJpeg,
|
||||
`a screenshot of the simulator`
|
||||
),
|
||||
description: new Text(
|
||||
`Dynamically calculating the temperatures and flow velocities
|
||||
in a fluid-based cooling system based on a simple model.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`A simulated system can contain reactors (heaters / coolers), pumps, heat exchangers,
|
||||
drains sources, and of course, pipes.`
|
||||
),
|
||||
new Text(
|
||||
`The algorithm takes advantages of graphs and matrices to get to a next time frame.`
|
||||
),
|
||||
new Text(
|
||||
`Python is used for the backend along with Flask and NumPy. A REST API facilitates
|
||||
the communication between the layers. For drawing the frontend HTML5 canvas is utilized.`
|
||||
),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
date: `2018 October - November`,
|
||||
title: `Graph editing application`,
|
||||
figure: new Image(
|
||||
processSimulatorInputWebP,
|
||||
processSimulatorInputJpeg,
|
||||
`a picture of the simulator's UI`
|
||||
),
|
||||
description: new Text(
|
||||
`An intuitive editor to create and edit input files for the nuclear facility simulator.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`Nodes can be moved with drag&drop gestures. Editing the parameters of elements
|
||||
can be done on the right panel.`
|
||||
),
|
||||
new Text(
|
||||
`The UI is built with JavaFX. The output can be exported as JSON or
|
||||
directly uploaded to the simulation backend.`
|
||||
),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
date: `2018 July - August`,
|
||||
title: `City simulation`,
|
||||
figure: new Image(
|
||||
citySimulationWebP,
|
||||
citySimulationJpeg,
|
||||
`a picture of a low-poly city`
|
||||
),
|
||||
description: new Text(
|
||||
`Simulating a city where car crashes are more frequent than usual.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`Through a REST API the state of the traffic lights can be changed.
|
||||
The drivers follow the instructions of the traffic lights, so if a mistake is made,
|
||||
there will be collisions. There is also support for displaying tweets on a HUD.`
|
||||
),
|
||||
new Text(
|
||||
`This was created for a Cybersecurity challenge. With the help of this program
|
||||
the contestants could instantly see the effect of their work.`
|
||||
),
|
||||
new Text(
|
||||
`The most interesting aspect of this project was building it in a server-client architecture.
|
||||
The decisions of the agents is calculated server-side. The real challenge was broadcasting
|
||||
these decisions in a fault-tolerant way using minimal bandwidth.`
|
||||
),
|
||||
new Text(
|
||||
`The program is made with Unity using C# as the scripting language. The models and animations
|
||||
were also made by me using Blender.`
|
||||
),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
date: `2018 June`,
|
||||
title: `Photo colour grader`,
|
||||
figure: new Image(colourWebP, colourJpeg, `a picture of the app`),
|
||||
description: new Text(
|
||||
`An innovative (at least I thought so) colour grader web application.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`The most noteworthy feature of this application is the colour selector UI.
|
||||
This program is only intended as a proof-of-concept, I wanted to experiment with
|
||||
some ideas and this was the outcome.`
|
||||
),
|
||||
new Text(
|
||||
`You can select some colours and then apply transformations to the other colours as a
|
||||
function of their distance to the selected colour.`
|
||||
),
|
||||
new Text(
|
||||
`By clicking on a coloured circle you can change its settings.
|
||||
New circles can be created by clicking in the large circle (and they can also be moved by drag & drop).`
|
||||
),
|
||||
],
|
||||
links: [new Open('color.schmelczer.dev')],
|
||||
},
|
||||
{
|
||||
date: `2017 autumn`,
|
||||
title: `Platform game`,
|
||||
figure: new Image(platformWebP, platformJpeg, `a picture of the app`),
|
||||
description: new Text(
|
||||
`A 3D game written in C with the help of SDL 1.2 (I haven't heard of GPU programming at the time).`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`The maps are randomly generated and fully destroyable.
|
||||
The player is getting chased by flying enemies. Overall, I find it a really enjoyable game.`
|
||||
),
|
||||
new Text(`I did this as a homework for my Basics of Programming course.`),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
{
|
||||
date: `2016 summer`,
|
||||
title: `Photos`,
|
||||
figure: new Image(photosWebP, photosJpeg, `a picture of the website`),
|
||||
description: new Text(`A simple web page where you can view my photos.`),
|
||||
links: [new Open('https://photo.schmelczer.dev')],
|
||||
},
|
||||
{
|
||||
date: `2016 spring`,
|
||||
title: `Lights synchronised to music`,
|
||||
figure: new Video({
|
||||
poster: last(ledPoster.images)!.path,
|
||||
mp4: ledMp4,
|
||||
webm: ledWebM,
|
||||
options: `controls playsinline preload="none"`,
|
||||
}),
|
||||
description: new Text(
|
||||
`A full stack application with a built-in
|
||||
music player which music controls the colour of some RGB LED strips.`
|
||||
),
|
||||
more: [
|
||||
new Text(
|
||||
`This was my first non-trivial project which got finished. Obviously,
|
||||
it is rather far from perfect, but I am still proud that I was able to build it on my own.`
|
||||
),
|
||||
new Text(
|
||||
`The backend logic is written in Python, the FFT implementation is provided by NumPy.
|
||||
A quite simple frontend for accessing the music player and changing
|
||||
the settings also got built using vanilla web development technologies.`
|
||||
),
|
||||
],
|
||||
links: [],
|
||||
},
|
||||
declaredTimelineElement,
|
||||
sdf2dTimelineElement,
|
||||
adAstraTimelineElement,
|
||||
forexTimelineElement,
|
||||
myNotesTimelineElement,
|
||||
nuclearTimelineElement,
|
||||
nuclearEditorTimelineElement,
|
||||
citySimulationTimelineElement,
|
||||
colorsTimelineElement,
|
||||
platformGameTimelineElement,
|
||||
photosTimelineElement,
|
||||
ledsTimelineElement,
|
||||
],
|
||||
}),
|
||||
new PageFooter({
|
||||
|
|
@ -377,7 +67,8 @@ export const create = () => {
|
|||
curriculaVitae: [{ name: `Curriculum vitae`, url: cvEnglish }],
|
||||
email: `andras@schmelczer.dev`,
|
||||
lastEditText: `Last modified on `,
|
||||
lastEdit: new Date(2020, 11 - 1, 17), // months are 0 indexed
|
||||
// @ts-ignore: injected by webpack
|
||||
lastEdit: new Date(__CURRENT_DATE__),
|
||||
})
|
||||
),
|
||||
new PageImageViewer()
|
||||
|
|
|
|||
8
src/static/icons/package.svg
Normal file
8
src/static/icons/package.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<polyline points="12 3 20 7.5 20 16.5 12 21 4 16.5 4 7.5 12 3" />
|
||||
<line x1="12" y1="12" x2="20" y2="7.5" />
|
||||
<line x1="12" y1="12" x2="12" y2="21" />
|
||||
<line x1="12" y1="12" x2="4" y2="7.5" />
|
||||
<line x1="16" y1="5.25" x2="8" y2="9.75" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
BIN
src/static/media/simulation.mp4
Normal file
BIN
src/static/media/simulation.mp4
Normal file
Binary file not shown.
BIN
src/static/media/simulation.webm
Normal file
BIN
src/static/media/simulation.webm
Normal file
Binary file not shown.
|
|
@ -42,6 +42,10 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
p {
|
||||
@include main-font();
|
||||
}
|
||||
|
||||
noscript {
|
||||
@include square(100%);
|
||||
@include center-children();
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import { Video } from '../page/basics/video/video';
|
||||
import { Text } from '../page/basics/text/text';
|
||||
import { Image } from '../page/basics/image/image';
|
||||
|
||||
import { PageElement } from '../page/page-element';
|
||||
import { url } from './url';
|
||||
import { Preview } from '../page/basics/preview/preview';
|
||||
|
||||
export interface Header {
|
||||
name: string;
|
||||
picture: Image;
|
||||
about: Content;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
showMoreText: string;
|
||||
showLessText: string;
|
||||
elements: Array<TimelineElement>;
|
||||
}
|
||||
|
||||
export interface TimelineElement {
|
||||
date: string;
|
||||
figure: Image | Video | Preview;
|
||||
title: string;
|
||||
description: Text;
|
||||
more?: Content;
|
||||
links: Array<PageElement>;
|
||||
}
|
||||
|
||||
export interface Footer {
|
||||
title: string;
|
||||
email: string;
|
||||
curriculaVitae: Array<CV>;
|
||||
lastEditText: string;
|
||||
lastEdit: Date;
|
||||
}
|
||||
|
||||
export interface CV {
|
||||
name: string;
|
||||
url: url;
|
||||
}
|
||||
|
||||
export type Content = Array<PageElement>;
|
||||
|
|
@ -6,11 +6,9 @@ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
|||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||
const Sharp = require('responsive-loader/sharp');
|
||||
const webpack = require('webpack');
|
||||
const Sass = require('sass');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isDevelopment = !isProduction;
|
||||
|
||||
module.exports = (env, argv) => ({
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
|
|
@ -23,7 +21,7 @@ module.exports = (env, argv) => ({
|
|||
optimization: {
|
||||
minimizer: [
|
||||
new TerserJSPlugin({
|
||||
sourceMap: isDevelopment,
|
||||
sourceMap: argv.mode === 'development',
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({}),
|
||||
],
|
||||
|
|
@ -48,6 +46,9 @@ module.exports = (env, argv) => ({
|
|||
filename: '[name].css',
|
||||
chunkFilename: '[id].css',
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
__CURRENT_DATE__: Date.now(),
|
||||
}),
|
||||
],
|
||||
entry: {
|
||||
index: './src/index.ts',
|
||||
|
|
@ -78,7 +79,7 @@ module.exports = (env, argv) => ({
|
|||
{
|
||||
loader: 'image-webpack-loader',
|
||||
options: {
|
||||
disable: !isProduction,
|
||||
disable: argv.mode === 'development',
|
||||
mozjpeg: {
|
||||
progressive: true,
|
||||
quality: 65,
|
||||
|
|
@ -147,7 +148,19 @@ module.exports = (env, argv) => ({
|
|||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: 'ts-loader',
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
},
|
||||
{
|
||||
// for removing whitespace from template strings
|
||||
loader: 'string-replace-loader',
|
||||
options: {
|
||||
search: /`.*?`/gs,
|
||||
replace: match => match.replace(/\s\s+/g, ' ').trim(),
|
||||
},
|
||||
},
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue