Add webpack support
138
src/content/en.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import me from "../static/me.jpg";
|
||||
import forex from "../static/forex.gif";
|
||||
import myNotes from "../static/my-notes.jpg";
|
||||
import processSimulator from "../static/process-simulator.jpg";
|
||||
import processSimulatorInput from "../static/process-simulator-input.jpg";
|
||||
import citySimulation from "../static/simulation.jpg";
|
||||
import color from "../static/color.jpg";
|
||||
import platform from "../static/platform.png";
|
||||
import photos from "../static/photos.jpg";
|
||||
import led from "../static/led.jpg";
|
||||
|
||||
export const content = {
|
||||
config: {
|
||||
showMore: "Show details",
|
||||
showLess: "Show less"
|
||||
},
|
||||
header: {
|
||||
name: "András Schmelczer",
|
||||
picture: me,
|
||||
about: [
|
||||
"I have always been fascinated by the engineering feats that surround us. When I realized that someday I might be able to contribute to these achievements, I knew that is what I need to aim for. As I am finishing my fifth semester at the Budapest University of Technology and Economics, I feel I am getting closer to it every day.",
|
||||
"You can see some of the more interesting projects I have worked on below."
|
||||
]
|
||||
},
|
||||
timeline: [
|
||||
{
|
||||
date: "2019 Autumn",
|
||||
title: "Predicting foreign exchange rates",
|
||||
picture: forex,
|
||||
description:
|
||||
"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: [
|
||||
"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.",
|
||||
"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."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2019 November",
|
||||
title: "My Notes",
|
||||
picture: myNotes,
|
||||
description: "A minimalist note organizer and editor powered by Markwon.",
|
||||
more: [
|
||||
{
|
||||
type: "a",
|
||||
href: "https://github.com/schmelczerandras/my-notes",
|
||||
text: "MyNotes on GitHub"
|
||||
},
|
||||
"A basic android app for creating and filtering notes written in markdown.",
|
||||
"It was my homework for BME's Android and web development course. It was also my first experience with Android development."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2018 October - November",
|
||||
title: "Simulating the cooling system of a nuclear facility",
|
||||
picture: processSimulator,
|
||||
description:
|
||||
"Dynamically calculating the temperatures and flow velocities 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.",
|
||||
"The algorithm takes advantages of graphs and matrices to get to a next time frame.",
|
||||
"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."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2018 October - November",
|
||||
title: "Graph editing application",
|
||||
picture: processSimulatorInput,
|
||||
description:
|
||||
"An intuitive editor to create and edit input files 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."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2018 July-August",
|
||||
title: "City simulation",
|
||||
picture: citySimulation,
|
||||
description:
|
||||
"Simulating a city where car crashes are more frequent than usual.",
|
||||
more: [
|
||||
"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.",
|
||||
"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. The decisions of the agents is calculated server-side. The real challenge was broadcasting these decisions in a fault-tolerant way using minimal bandwidth.",
|
||||
"The program is made with Unity using C# as the scripting language. The models and animations were also made by me using Blender."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2018 June",
|
||||
title: "Photo color grader",
|
||||
picture: color,
|
||||
description:
|
||||
"An innovative (at least I thought so) color grader web application.",
|
||||
more: [
|
||||
"The most noteworthy feature of this application is the color selector UI. This program is only intended as a proof-of-concept, I wanted to experiment with some ideas and this was the outcome. ",
|
||||
"You can select some colors and then apply transformations to the other colors as a function of their distance to the selected color.",
|
||||
"By clicking on a colored 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).",
|
||||
{ type: "a", href: "color", text: "schmelczer.dev/color" }
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2017 autumn",
|
||||
|
||||
title: "Platform game",
|
||||
picture: platform,
|
||||
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."
|
||||
]
|
||||
},
|
||||
{
|
||||
date: "2016 summer",
|
||||
title: "Photos",
|
||||
picture: photos,
|
||||
description: "A simple web page where you can view my photos.",
|
||||
link: "schmelczer.dev/photos"
|
||||
},
|
||||
{
|
||||
date: "2016 spring",
|
||||
title: "Lights synchronised to music",
|
||||
picture: led,
|
||||
description:
|
||||
"A full stack application with a built-in music player which music controls the color of some 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 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.",
|
||||
"Below is a video showing the system in work.",
|
||||
{ type: "video", src: "static/led720.mp4" }
|
||||
]
|
||||
}
|
||||
],
|
||||
footer: {
|
||||
email: "andras.schmelczer@schdesign.hu",
|
||||
cv: "/static/andras_schmelczer_cv.pdf"
|
||||
}
|
||||
};
|
||||
94
src/content/hu.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*{
|
||||
"header": {
|
||||
"name": "Schmelczer András",
|
||||
"picture": "/static/me.jpg",
|
||||
"about": [
|
||||
"Mind a szoftverfejlesztés, mind pedig a design fontos számomra. Élvezem a problémák megoldását. Motivál, hogy hasznos és érdekes projektekben vegyek részt. Szeretek tanulni.",
|
||||
|
||||
"2017-ben kezdtem tanulmányaimat a Budapesti Műszaki és Gazdaságtudományi Egyetem mérnökinformatikus szakán. Azóta is minden félévemet kiváló eredménnyel zártam. Tavaly csatlakoztam a Simonyi Károly Szakkollégium schdesign köréhez. Korábban a Pécsi Tudományegyetem Gyakorló Gimnáziumába jártam, mialatt angol komplex C1-es nyelvvizsgát is szereztem.",
|
||||
|
||||
"Az alábbiakban összeszedtem pár izgalmasabb projektemet. A képekből természetesen csak a megoldások megjelenítés részét lehet látni. Emellett azonban igyekeztem a háttérben zajló folyamatokról is írni, hiszen az igazi kihívások általában ott rejlenek."
|
||||
]
|
||||
},
|
||||
"timeline": [
|
||||
{
|
||||
"date": "2018 október - november",
|
||||
"title": "Atomreaktor hűtőrendszerének szimulációja",
|
||||
"picture": "/static/process-simulator.jpg",
|
||||
"description": "Egy csőrendszerben lévő víz hőmérsékletének és áramlásának dinamikus számítása.",
|
||||
"more": [
|
||||
"A reaktorok (vízmelegítők), szivattyúk, hőcserélők adataiból és a csőrendszer felépítéséből kiszámolja az alkalmazás, hogy melyik időpillanatban hol, mennyi víz folyik, és az milyen meleg.",
|
||||
"Ezt egy érdekes gráfelméleti algoritmussal, illetve egy mátrix ügyes manipulálásával éri el.",
|
||||
"A szimuláció backendje python Flask-ben lett írva. Ezzel kommunikálni egy REST API-n keresztül lehet. A megjelenítés HTML5 canvas segítségével történik."
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2018 október - november",
|
||||
"title": "Gráf szerkesztő alkalmazás",
|
||||
"picture": "/static/process-simulator-input.jpg",
|
||||
"description": "A fentebb látható szoftverhez tartozó csőrendszert lehet vele létrehozni.",
|
||||
"more": [
|
||||
"A grafikus és felhasználóbarát szerkesztőprogram a végeredményt megfelelő JSON formátumba alakítja, amit a szimulátor már könnyedén fel tud dolgozni.",
|
||||
"Szerkeszteni klikkeléssel, illetve drag & droppal lehetséges. Az alkalmazásban továbbá lehet a vízmelegítők, szivattyúk stb. paramétereit is beállítani.",
|
||||
"Java-ban lett írva, a megjelenítést a JavaFX biztosítja."
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2018 július - augusztus",
|
||||
"title": "Közlekedés szimuláció",
|
||||
"picture": "/static/sim.jpg",
|
||||
"description": "A modellek Blenderben, a szimuláció Unityben készült.",
|
||||
"more": [
|
||||
"Egy versenyhez készült program. REST API-kon keresztül lehet a lámpák színét változtatni és a szimulációt befolyásolni, (akár még tweet-et is lehet beküldeni), az autók pedig ettől függően közlekednek, esetlegesen karamboloznak és felrobbannak.",
|
||||
"Az egész érdekessége, hogy egy szerver-kliens architektúrát valósít meg, a szervezés egyszerűbbé tétele végett. Izgalmas kihívás volt a netes kommunikációból fakadó laggot kompenzálni.",
|
||||
"Az összes képen látható modellt és animációt én készítettem. A scriptelés C# segítségével történt."
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2018 június",
|
||||
"title": "Színszerkesztő",
|
||||
"picture": "/static/szinezo.jpg",
|
||||
"description": "Egy innovatív color grader képekhez.",
|
||||
"more": [
|
||||
"Ki lehet választani bizonyos színeket, és a többi színt az előbbiektől lévő távolságának függvényében lehet módosítani, telitettséget, színezettségét változtatni.",
|
||||
"Egyelőre proof of concept stádiumban van, viszont tervezem befejezni.",
|
||||
"A színes gombokra való kattintással lehet az opciók közt váltani. Színes gombot a nagy körbe való kattintással lehet létrehozni (mozgatni pedig drag & droppal).",
|
||||
{ "type": "a", "href": "/szinezo", "text": "schmelczer.hu/szinezo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2017 ősz",
|
||||
"title": "Platform játék",
|
||||
"picture": "/static/platform.png",
|
||||
"description": "Írtam egy 3D-s játékot C-ben az SDL 1.2 segítségével.",
|
||||
"more": [
|
||||
"A pályák véletlenszerűen generálódnak, menthetők és rombolhatók is. A játékost repülő ellenségek üldözik.",
|
||||
"Ez volt a Programozás alapjai I. tárgyhoz készített házifeladatom. Összességében egy élvezhető játék lett.",
|
||||
{ "type": "a", "href": "/platform", "text": "schmelczer.hu/platform" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"date": "2016 nyár",
|
||||
"title": "Fényképek",
|
||||
"picture": "/static/kepek.jpg",
|
||||
"description": "Csináltam egy oldalt, ahol a fényképeimet lehet megnézni.",
|
||||
"link": "schmelczer.hu/kepek"
|
||||
},
|
||||
{
|
||||
"date": "2016 tavasz",
|
||||
"title": "Zenére világító ledsorok",
|
||||
"picture": "/static/LED.jpg",
|
||||
"description": "Egy alkalmazást készítettem, amivel RGB ledsorok színét lehet a zene ritmusára változtatni.",
|
||||
"more": [
|
||||
"Ez volt az első nagyobb projektem, ez természetesen a megvalósítás minőségén is érezhető. Ettől független büszke vagyok a végeredményre.",
|
||||
"Pythonban lett írva, amivel egy webes frontenden keresztül lehet kommunikálni. Továbbá beépítésre került egy zenelejátszó is a programba.",
|
||||
"A működő rendszerről készítettem egy videót, ami alább tekinthető meg.",
|
||||
{ "type": "video", "src": "static/led720.mp4" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"email": "andras.schmelczer@schdesign.hu"
|
||||
}
|
||||
}
|
||||
*/
|
||||
35
src/index.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<meta property="og:image:width" content="1500">
|
||||
<meta property="og:image:height" content="785">
|
||||
<meta property="og:title" content="Portfolio - András Schmelczer">
|
||||
<meta property="og:description" content="You can view my projects on a timeline.">
|
||||
<meta property="og:url" content="https://schmelczer.dev">
|
||||
<meta property="og:image" content="https://schmelczer.dev/og-image.jpg">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="theme-color" content="#31343F" />
|
||||
|
||||
<title>Portfolio</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="center" id="photo-viewer">
|
||||
<img id="photo" alt="currently opened photo" src=""/>
|
||||
<div id="exit"></div>
|
||||
</section>
|
||||
|
||||
<header class="center">
|
||||
<img id="header-pic" alt="a picture of me" src=""/>
|
||||
<h1 id="name"></h1>
|
||||
</header>
|
||||
|
||||
<section id="about"></section>
|
||||
|
||||
<main id="timeline"></main>
|
||||
|
||||
<footer class="card center"><a id="email"></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/static/avoid.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
src/static/color.jpg
Normal file
|
After Width: | Height: | Size: 570 KiB |
BIN
src/static/forex.gif
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
src/static/led.jpg
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
src/static/led720.mp4
Normal file
BIN
src/static/me.jpg
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
src/static/my-notes.jpg
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
src/static/og-image.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
src/static/og.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/static/photos.jpg
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
src/static/platform.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
src/static/process-simulator-input.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
src/static/process-simulator.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
src/static/simulation.jpg
Normal file
|
After Width: | Height: | Size: 457 KiB |
173
src/styles/elements.scss
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/* X sign visible in the photo viewer mode. */
|
||||
#exit {
|
||||
position: absolute;
|
||||
width: var(--exit-size);
|
||||
height: var(--exit-size);
|
||||
top: var(--exit-size);
|
||||
right: var(--exit-size);
|
||||
cursor: pointer;
|
||||
}
|
||||
#exit:before,
|
||||
#exit:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: var(--line-width);
|
||||
height: calc(var(--exit-size) * 1.4142);
|
||||
background: white;
|
||||
border-radius: var(--border-radius);
|
||||
top: calc(var(--exit-size) * -0.4142 / 2);
|
||||
left: 50%;
|
||||
}
|
||||
#exit:before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
#exit:after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Links with interactive underline. */
|
||||
a {
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
border-bottom: solid 3px var(--light-accent-color);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
a:after {
|
||||
content: "";
|
||||
height: var(--line-width);
|
||||
background-color: var(--accent-color);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(-1 * var(--line-width));
|
||||
width: 0;
|
||||
transition: width var(--transition-time);
|
||||
}
|
||||
a:hover:after {
|
||||
width: 100%;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Line with circle for the timeline sections. */
|
||||
.line {
|
||||
margin-left: calc(var(--dot-size) / 2);
|
||||
margin-right: var(--line-height);
|
||||
border-left: var(--line-width) solid var(--text-color);
|
||||
position: relative;
|
||||
}
|
||||
.line:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: calc(-1 / 2 * var(--dot-size) - 1.5 * var(--line-width));
|
||||
background: var(--bg-color);
|
||||
top: 33%;
|
||||
width: var(--dot-size);
|
||||
height: var(--dot-size);
|
||||
border-radius: 100%;
|
||||
border: var(--line-width) var(--text-color) solid;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Activity cards. */
|
||||
.card {
|
||||
border-radius: var(--border-radius);
|
||||
text-align: center;
|
||||
padding: var(--margin);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.125);
|
||||
transition: box-shadow;
|
||||
transition-duration: var(--transition-time);
|
||||
background: var(--card-color);
|
||||
}
|
||||
.card:hover {
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Dates related to the lines and cards. */
|
||||
.date-narrow-screen,
|
||||
.date-wide-screen {
|
||||
font: 400 1em "Open sans", sans-serif;
|
||||
}
|
||||
.date-narrow-screen {
|
||||
display: none;
|
||||
margin: 0;
|
||||
margin-top: calc(var(--line-height) / 2.25) !important;
|
||||
color: var(--light-text-color);
|
||||
}
|
||||
.date-wide-screen {
|
||||
position: relative;
|
||||
top: calc(33% + var(--dot-size) + 1ch);
|
||||
margin: 0 var(--margin) 0 calc(var(--line-width) + 1ex);
|
||||
width: 100px;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* The photo viewer */
|
||||
#photo-viewer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: var(--photo-viewer-color);
|
||||
z-index: -3;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-time);
|
||||
}
|
||||
|
||||
/* #photo */
|
||||
#photo-viewer > img {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Scrollbar. */
|
||||
body::-webkit-scrollbar-track,
|
||||
body::-webkit-scrollbar {
|
||||
background-color: var(--scroll-color);
|
||||
width: 12px;
|
||||
}
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background-color: var(--accent-color);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Selections. */
|
||||
::-moz-selection {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
::selection {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* Absolute centering parent. */
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/**/
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Disable animation. */
|
||||
.card:hover {
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.date-narrow-screen {
|
||||
display: block;
|
||||
}
|
||||
.date-wide-screen {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
3
src/styles/index.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@import "main";
|
||||
@import "elements";
|
||||
@import "page";
|
||||
28
src/styles/main.scss
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i|Raleway&subset=latin-ext');
|
||||
|
||||
:root {
|
||||
--photo-viewer-color: rgba(0, 0, 0, 0.75);
|
||||
--accent-color: #5264bf;
|
||||
--light-accent-color: #e5e5ff;
|
||||
--scroll-color: #ffd6d6;
|
||||
--bg-color:linear-gradient(90deg, #fff9e0 0, #ffd6d6 100%);
|
||||
--card-color: white;
|
||||
--text-color: #31343f;
|
||||
--light-text-color: #7a7d8e;
|
||||
--dot-size: 25px;
|
||||
--line-width: 3px;
|
||||
--exit-size: 25px;
|
||||
--line-height: 15px;
|
||||
--smaller-margin: 25px;
|
||||
--margin: 35px;
|
||||
--border-radius: 5px;
|
||||
--transition-time: 200ms;
|
||||
--width: 765px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
:root {
|
||||
--exit-size: 20px;
|
||||
--margin: 25px;
|
||||
--smaller-margin: 20px;
|
||||
}
|
||||
}
|
||||
130
src/styles/page.scss
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
* {
|
||||
margin: 0;
|
||||
box-sizing: content-box;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #31343f;
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
body {
|
||||
width: var(--width);
|
||||
margin: auto;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
header,
|
||||
footer,
|
||||
#timeline,
|
||||
#about > p:first-of-type,
|
||||
#timeline > section:not(:first-of-type) > .card {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
|
||||
#header-pic,
|
||||
h1 {
|
||||
font: 400 3.33em "Raleway", sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#header-pic {
|
||||
height: 4ch;
|
||||
border-radius: 100%;
|
||||
margin-right: 1.5ex;
|
||||
}
|
||||
|
||||
p,
|
||||
a {
|
||||
font: 400 1.125em "Open sans", sans-serif;
|
||||
}
|
||||
|
||||
#about > p {
|
||||
text-align: justify;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
#timeline > section {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#about > p,
|
||||
.card > *:not(:first-child):not(:last-child),
|
||||
.collapsed > *:not(:first-child) {
|
||||
margin-top: var(--line-height);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: 400 2em "Raleway", sans-serif;
|
||||
}
|
||||
|
||||
.card img,
|
||||
video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
user-select: none;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-style: italic;
|
||||
margin-bottom: var(--smaller-margin);
|
||||
}
|
||||
|
||||
.collapsed > p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: height var(--transition-time);
|
||||
}
|
||||
.collapsed > *:last-child {
|
||||
margin-bottom: var(--smaller-margin);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
body {
|
||||
width: 85%;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
#photo {
|
||||
max-width: 94vw;
|
||||
}
|
||||
|
||||
.card {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1,
|
||||
#header-pic {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
#header-pic {
|
||||
height: 5.5ch;
|
||||
margin: 0.75ex 0 0.5ex 0;
|
||||
}
|
||||
|
||||
#about > p {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
19
src/ts/index.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { createPageFactory } from "./parser";
|
||||
import { content } from "../content/en";
|
||||
|
||||
import "../styles/index.scss";
|
||||
|
||||
(async () => {
|
||||
const ids = {
|
||||
pictureId: "header-pic",
|
||||
nameId: "name",
|
||||
aboutId: "about",
|
||||
timelineId: "timeline",
|
||||
emailId: "email",
|
||||
photoViewerId: "photo-viewer",
|
||||
photoId: "photo"
|
||||
};
|
||||
|
||||
await createPageFactory(ids)(content);
|
||||
document.body.style.visibility = "visible";
|
||||
})();
|
||||
168
src/ts/parser.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
export const createPageFactory = ({
|
||||
nameId,
|
||||
pictureId,
|
||||
aboutId,
|
||||
timelineId,
|
||||
emailId,
|
||||
photoId,
|
||||
photoViewerId
|
||||
}) => {
|
||||
const createPage = content => {
|
||||
const { config, header, timeline, footer } = content;
|
||||
processHeader(header);
|
||||
processTimeline(timeline, config);
|
||||
processFooter(footer);
|
||||
setupGlobals(config);
|
||||
};
|
||||
|
||||
const processHeader = ({ name, picture, about }) => {
|
||||
document.title = name;
|
||||
getElement(nameId).textContent = name;
|
||||
getElement(pictureId).src = picture;
|
||||
getElement(pictureId).onclick = () => showPhoto(picture);
|
||||
getElement(aboutId).innerHTML = listToHtml(about);
|
||||
};
|
||||
|
||||
const listToHtml = list =>
|
||||
list
|
||||
.map(element => {
|
||||
if (!element.type || element.type === "p") {
|
||||
return `<p>${element}</p>`;
|
||||
} else if (element.type === "a") {
|
||||
return `<a href="${element.href}" target="_blank"> ${element.text} </a>`;
|
||||
} else if (element.type === "video") {
|
||||
return `<video controls><source src="${element.src}" /></video>`;
|
||||
} else return "";
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const processTimeline = (timeline, { showMore }) => {
|
||||
getElement(timelineId).innerHTML = timeline
|
||||
.map(element => timelineElementToHTML(element, createId(), showMore))
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const timelineElementToHTML = (
|
||||
{ date, title, picture, description, more, link },
|
||||
id,
|
||||
showMore
|
||||
) => `
|
||||
<section>
|
||||
<div class="line">
|
||||
${date ? `<p class="date-wide-screen">${date}</p>` : ""}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>${title}</h2>
|
||||
${date ? `<p class="date-narrow-screen">${date}</p>` : ""}
|
||||
${
|
||||
picture
|
||||
? `<img src="${picture}" onclick="showPhoto('${picture}');" alt="${picture}"/>`
|
||||
: ""
|
||||
}
|
||||
${description ? `<p class="description">${description}</p>` : ""}
|
||||
${
|
||||
more
|
||||
? `
|
||||
<div class="collapsed" id="${idToActivityId(id)}">
|
||||
${listToHtml(more)}
|
||||
</div>
|
||||
<a id="${idToButtonId(id)}" onclick="toggleLongDescription(${id})">
|
||||
${showMore}
|
||||
</a>
|
||||
`
|
||||
: link
|
||||
? `<a href="http://${link}" target="_blank">${link}</a>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const processFooter = ({ email }) => {
|
||||
getElement(emailId).href = `mailto:${email}`;
|
||||
getElement(emailId).textContent = email;
|
||||
};
|
||||
|
||||
const hideFrame = () => {
|
||||
getElement(photoViewerId).style["z-index"] = -1;
|
||||
getElement(photoViewerId).style.opacity = "0";
|
||||
};
|
||||
|
||||
const showPhoto = src => {
|
||||
getElement(photoId).src = src;
|
||||
getElement(photoViewerId).style["z-index"] = 1000;
|
||||
getElement(photoViewerId).style.opacity = "1";
|
||||
};
|
||||
|
||||
const setupGlobals = config => {
|
||||
(window as any).toggleLongDescription = toggleLongDescriptionFactory(config);
|
||||
(window as any).showPhoto = showPhoto;
|
||||
|
||||
(window as any).hideFrame = hideFrame;
|
||||
getElement(photoViewerId).addEventListener("click", hideFrame);
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
document.body.addEventListener("keydown", handleEscape);
|
||||
};
|
||||
|
||||
const toggleLongDescriptionFactory = ({ showMore, showLess }) => id => {
|
||||
const button = getElement(idToButtonId(id));
|
||||
const element = getElement(idToActivityId(id));
|
||||
|
||||
if (isClosed(element)) {
|
||||
open(element);
|
||||
button.textContent = showLess;
|
||||
} else {
|
||||
close(element);
|
||||
button.textContent = showMore;
|
||||
}
|
||||
};
|
||||
|
||||
const onResize = () => {
|
||||
const elements = document.getElementsByClassName("collapsed");
|
||||
Array.prototype.forEach.call(elements, element => {
|
||||
if (isOpen(element)) {
|
||||
element.style.height = "auto";
|
||||
setTimeout(() => open(element), 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isClosed = element =>
|
||||
["0px", "0", 0, ""].includes(element.style.height);
|
||||
|
||||
const isOpen = element => !isClosed(element);
|
||||
|
||||
const close = element => (element.style.height = "0");
|
||||
|
||||
const open = element => (element.style.height = `${element.scrollHeight}px`);
|
||||
|
||||
const handleEscape = event => {
|
||||
if (event.key === "Escape") {
|
||||
hideFrame();
|
||||
}
|
||||
};
|
||||
|
||||
const getElementFactory = () => {
|
||||
const foundElements = {};
|
||||
return id => {
|
||||
if (!(id in foundElements)) {
|
||||
foundElements[id] = document.getElementById(id);
|
||||
}
|
||||
return foundElements[id];
|
||||
};
|
||||
};
|
||||
const getElement = getElementFactory();
|
||||
|
||||
const createIdFactory = () => {
|
||||
let id = 0;
|
||||
return () => id++;
|
||||
};
|
||||
const createId = createIdFactory();
|
||||
|
||||
const idToButtonId = id => `button_${id}`;
|
||||
|
||||
const idToActivityId = id => `activity_${id}`;
|
||||
|
||||
return createPage;
|
||||
};
|
||||