Add webpack support

This commit is contained in:
Schmelczer András 2019-12-19 22:14:45 +01:00
parent fb3ef7475c
commit eb2075aec5
33 changed files with 988 additions and 765 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
# Created by .ignore support plugin (hsz.mobi) node_modules
dist
package-lock.json

45
.idea/watcherTasks.xml generated Normal file
View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS">
<TaskOptions isEnabled="true">
<option name="arguments" value="--write $FilePathRelativeToProjectRoot$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="js" />
<option name="immediateSync" value="false" />
<option name="name" value="Prettier" />
<option name="output" value="$FilePathRelativeToProjectRoot$" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$ProjectFileDir$/node_modules/.bin/prettier" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs />
</TaskOptions>
<TaskOptions isEnabled="true">
<option name="arguments" value="--write $FilePathRelativeToProjectRoot$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="xhtml" />
<option name="immediateSync" value="false" />
<option name="name" value="Prettier" />
<option name="output" value="$FilePathRelativeToProjectRoot$" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$ProjectFileDir$/node_modules/.bin/prettier" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs />
</TaskOptions>
</component>
</project>

45
.idea/workspace.xml generated
View file

@ -2,16 +2,30 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="8edc47ab-1265-4111-9771-536b24cc9310" name="Default Changelist" comment=""> <list default="true" id="8edc47ab-1265-4111-9771-536b24cc9310" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/og-image.jpg" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/og.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/content-en.json" beforeDir="false" afterPath="$PROJECT_DIR$/content-en.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/content-en.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/css/elements.css" beforeDir="false" afterPath="$PROJECT_DIR$/css/elements.css" afterDir="false" /> <change beforePath="$PROJECT_DIR$/content-hu.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/css/page.css" beforeDir="false" afterPath="$PROJECT_DIR$/css/page.css" afterDir="false" /> <change beforePath="$PROJECT_DIR$/css/elements.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/index.html" afterDir="false" /> <change beforePath="$PROJECT_DIR$/css/main.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/my-notes.png" beforeDir="false" /> <change beforePath="$PROJECT_DIR$/css/page.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/js/content.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/js/main.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/avoid.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/color.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/forex.gif" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/led.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/led720.mp4" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/me.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/my-notes.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/og-image.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/og.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/photos.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/platform.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/process-simulator-input.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/process-simulator.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/simulation.jpg" beforeDir="false" />
</list> </list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@ -23,6 +37,7 @@
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="JavaScript File" /> <option value="JavaScript File" />
<option value="HTML File" />
</list> </list>
</option> </option>
</component> </component>
@ -35,13 +50,22 @@
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">
<property name="ASKED_ADD_EXTERNAL_FILES" value="true" /> <property name="ASKED_ADD_EXTERNAL_FILES" value="true" />
<property name="DefaultHtmlFileTemplate" value="HTML File" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" /> <property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" /> <property name="WebServerToolWindowFactoryState" value="false" />
<property name="ignore_missing_gitignore" value="true" /> <property name="ignore_missing_gitignore" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> <property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="prettierjs.PrettierConfiguration.Package" value="/usr/local/lib/node_modules/prettier" /> <property name="prettierjs.PrettierConfiguration.Package" value="/usr/local/lib/node_modules/prettier" />
<property name="settings.editor.selected.configurable" value="watcher.settings" />
<property name="ts.external.directory.path" value="C:\Projects\portfolio\CompiledCV\node_modules\typescript\lib" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Projects\portfolio\CompiledCV\src\content" />
<recent name="C:\Projects\portfolio\CompiledCV\dist" />
<recent name="C:\Projects\portfolio\CompiledCV\src" />
</key>
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$" /> <recent name="$PROJECT_DIR$" />
</key> </key>
@ -72,7 +96,8 @@
<workItem from="1576164066512" duration="3000" /> <workItem from="1576164066512" duration="3000" />
<workItem from="1576250286627" duration="24922000" /> <workItem from="1576250286627" duration="24922000" />
<workItem from="1576342852221" duration="24000" /> <workItem from="1576342852221" duration="24000" />
<workItem from="1576352253939" duration="106000" /> <workItem from="1576352253939" duration="353000" />
<workItem from="1576748546157" duration="8755000" />
</task> </task>
<servers /> <servers />
</component> </component>

24
custom.d.ts vendored Normal file
View file

@ -0,0 +1,24 @@
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
declare module "*.jpg" {
const content: string;
export default content;
}
declare module "*.jpeg" {
const content: string;
export default content;
}
declare module "*.gif" {
const content: string;
export default content;
}

View file

@ -1,42 +0,0 @@
<!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&aacute;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>
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/elements.css" />
<link rel="stylesheet" href="css/page.css" />
</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>
<script src="js/content.js"></script>
<script src="js/main.js"></script>
</html>

View file

@ -1,157 +0,0 @@
const createPageFactory = ({
nameId,
pictureId,
aboutId,
timelineId,
emailId,
photoId,
photoViewerId,
}) => {
const createPage = async (src) => {
const {config, header, timeline, footer} = await getData(src);
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.toggleLongDescription = toggleLongDescriptionFactory(config);
window.showPhoto = showPhoto;
window.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 getData = async (src) => await (await fetch(src, {
method: 'GET',
mode: 'cors',
cache: 'no-cache'
})).json();
const createIdFactory = () => {
let id = 0;
return () => id++;
};
const createId = createIdFactory();
const idToButtonId = (id) => `button_${id}`;
const idToActivityId = (id) => `activity_${id}`;
return createPage;
};

View file

@ -1,15 +0,0 @@
(async () => {
const src = 'content-en.json';
const ids = {
pictureId: 'header-pic',
nameId: 'name',
aboutId: 'about',
timelineId: 'timeline',
emailId: 'email',
photoViewerId: 'photo-viewer',
photoId: 'photo'
};
await createPageFactory(ids)(src);
document.body.style.visibility = 'visible';
})();

35
package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "portfolio",
"version": "1.0.0",
"description": "An easily configurable portfolio.",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"repository": {
"type": "git",
"url": "git+https://github.com/schmelczerandras/timeline.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/schmelczerandras/timeline/issues"
},
"homepage": "https://github.com/schmelczerandras/timeline#readme",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.13.0",
"prettier": "^1.19.1",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.2",
"ts-loader": "^6.2.1",
"typescript": "^3.7.3",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10"
}
}

View file

@ -1,116 +1,138 @@
{ import me from "../static/me.jpg";
"config": { import forex from "../static/forex.gif";
"showMore": "Show details", import myNotes from "../static/my-notes.jpg";
"showLess": "Show less" import processSimulator from "../static/process-simulator.jpg";
}, import processSimulatorInput from "../static/process-simulator-input.jpg";
"header": { import citySimulation from "../static/simulation.jpg";
"name": "András Schmelczer", import color from "../static/color.jpg";
"picture": "/static/me.jpg", import platform from "../static/platform.png";
"about": [ import photos from "../static/photos.jpg";
"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.", import led from "../static/led.jpg";
"You can see some of the more interesting projects I have worked on below."
] export const content = {
}, config: {
"timeline": [ showMore: "Show details",
{ showLess: "Show less"
"date": "2019 Autumn", },
"title": "Predicting foreign exchange rates", header: {
"picture": "/static/forex.gif", name: "András Schmelczer",
"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).", picture: me,
"more": [ about: [
"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.", "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.",
"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." "You can see some of the more interesting projects I have worked on below."
] ]
}, },
{ timeline: [
"date": "2019 November", {
"title": "My Notes", date: "2019 Autumn",
"picture": "/static/my-notes.jpg", title: "Predicting foreign exchange rates",
"description": "A minimalist note organizer and editor powered by Markwon.", picture: forex,
"more": [ description:
{ "type": "a", "href": "https://github.com/schmelczerandras/my-notes", "text": "MyNotes on GitHub" }, "From the animation we can see that my algorithm does a somewhat acceptable job at predicting (blue graph) the EUR/USD rates (green graph).",
"A basic android app for creating and filtering notes written in markdown.", more: [
"It was my homework for BME's Android and web development course. It was also my first experience with Android development." "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": "2018 October - November", {
"title": "Simulating the cooling system of a nuclear facility", date: "2019 November",
"picture": "/static/process-simulator.jpg", title: "My Notes",
"description": "Dynamically calculating the temperatures and flow velocities in a fluid based cooling system based on a simple model.", picture: myNotes,
"more": [ description: "A minimalist note organizer and editor powered by Markwon.",
"A simulated system can contain reactors (heaters / coolers), pumps, heat exchangers, drains sources, and of course, pipes.", more: [
"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." type: "a",
] href: "https://github.com/schmelczerandras/my-notes",
}, text: "MyNotes on GitHub"
{ },
"date": "2018 October - November", "A basic android app for creating and filtering notes written in markdown.",
"title": "Graph editing application", "It was my homework for BME's Android and web development course. It was also my first experience with Android development."
"picture": "/static/process-simulator-input.jpg", ]
"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.", date: "2018 October - November",
"The UI is built with JavaFX. The output can be exported as JSON or directly uploaded to the simulation backend." 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.",
"date": "2018 July-August", more: [
"title": "City simulation", "A simulated system can contain reactors (heaters / coolers), pumps, heat exchangers, drains sources, and of course, pipes.",
"picture": "/static/simulation.jpg", "The algorithm takes advantages of graphs and matrices to get to a next time frame.",
"description": "Simulating a city where car crashes are more frequent than usual.", "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."
"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.", date: "2018 October - November",
"The program is made with Unity using C# as the scripting language. The models and animations were also made by me using Blender." title: "Graph editing application",
] picture: processSimulatorInput,
}, description:
{ "An intuitive editor to create and edit input files for the nuclear facility simulator.",
"date": "2018 June", more: [
"title": "Photo color grader", "Nodes can be moved with drag&drop gestures. Editing the parameters of elements can be done on the right panel.",
"picture": "/static/color.jpg", "The UI is built with JavaFX. The output can be exported as JSON or directly uploaded to the simulation backend."
"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.", date: "2018 July-August",
"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).", title: "City simulation",
{ "type": "a", "href": "color", "text": "schmelczer.dev/color" } picture: citySimulation,
] description:
}, "Simulating a city where car crashes are more frequent than usual.",
{ more: [
"date": "2017 autumn", "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.",
"title": "Platform game", "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.",
"picture": "/static/platform.png", "The program is made with Unity using C# as the scripting language. The models and animations were also made by me using Blender."
"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: "2018 June",
] title: "Photo color grader",
}, picture: color,
{ description:
"date": "2016 summer", "An innovative (at least I thought so) color grader web application.",
"title": "Photos", more: [
"picture": "/static/photos.jpg", "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. ",
"description": "A simple web page where you can view my photos.", "You can select some colors and then apply transformations to the other colors as a function of their distance to the selected color.",
"link": "schmelczer.dev/photos" "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": "2016 spring", },
"title": "Lights synchronised to music", {
"picture": "/static/led.jpg", date: "2017 autumn",
"description": "A full stack application with a built-in music player which music controls the color of some RGB LED strips.",
"more": [ title: "Platform game",
"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.", picture: platform,
"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.", description:
"Below is a video showing the system in work.", "A 3D game written in C with the help of SDL 1.2 (I haven't heard of GPU programming at the time).",
{ "type": "video", "src": "static/led720.mp4" } 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."
], ]
"footer": { },
"email": "andras.schmelczer@schdesign.hu", {
"cv": "/static/andras_schmelczer_cv.pdf" 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"
}
};

View file

@ -1,93 +1,94 @@
{ /*{
"header": { "header": {
"name": "Schmelczer András", "name": "Schmelczer András",
"picture": "/static/me.jpg", "picture": "/static/me.jpg",
"about": [ "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.", "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.", "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." "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": [ "timeline": [
{ {
"date": "2018 október - november", "date": "2018 október - november",
"title": "Atomreaktor hűtőrendszerének szimulációja", "title": "Atomreaktor hűtőrendszerének szimulációja",
"picture": "/static/process-simulator.jpg", "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.", "description": "Egy csőrendszerben lévő víz hőmérsékletének és áramlásának dinamikus számítása.",
"more": [ "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.", "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.", "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." "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", "date": "2018 október - november",
"title": "Gráf szerkesztő alkalmazás", "title": "Gráf szerkesztő alkalmazás",
"picture": "/static/process-simulator-input.jpg", "picture": "/static/process-simulator-input.jpg",
"description": "A fentebb látható szoftverhez tartozó csőrendszert lehet vele létrehozni.", "description": "A fentebb látható szoftverhez tartozó csőrendszert lehet vele létrehozni.",
"more": [ "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.", "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.", "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." "Java-ban lett írva, a megjelenítést a JavaFX biztosítja."
] ]
}, },
{ {
"date": "2018 július - augusztus", "date": "2018 július - augusztus",
"title": "Közlekedés szimuláció", "title": "Közlekedés szimuláció",
"picture": "/static/sim.jpg", "picture": "/static/sim.jpg",
"description": "A modellek Blenderben, a szimuláció Unityben készült.", "description": "A modellek Blenderben, a szimuláció Unityben készült.",
"more": [ "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.", "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 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." "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", "date": "2018 június",
"title": "Színszerkesztő", "title": "Színszerkesztő",
"picture": "/static/szinezo.jpg", "picture": "/static/szinezo.jpg",
"description": "Egy innovatív color grader képekhez.", "description": "Egy innovatív color grader képekhez.",
"more": [ "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.", "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.", "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).", "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" } { "type": "a", "href": "/szinezo", "text": "schmelczer.hu/szinezo" }
] ]
}, },
{ {
"date": "2017 ősz", "date": "2017 ősz",
"title": "Platform játék", "title": "Platform játék",
"picture": "/static/platform.png", "picture": "/static/platform.png",
"description": "Írtam egy 3D-s játékot C-ben az SDL 1.2 segítségével.", "description": "Írtam egy 3D-s játékot C-ben az SDL 1.2 segítségével.",
"more": [ "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.", "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.", "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" } { "type": "a", "href": "/platform", "text": "schmelczer.hu/platform" }
] ]
}, },
{ {
"date": "2016 nyár", "date": "2016 nyár",
"title": "Fényképek", "title": "Fényképek",
"picture": "/static/kepek.jpg", "picture": "/static/kepek.jpg",
"description": "Csináltam egy oldalt, ahol a fényképeimet lehet megnézni.", "description": "Csináltam egy oldalt, ahol a fényképeimet lehet megnézni.",
"link": "schmelczer.hu/kepek" "link": "schmelczer.hu/kepek"
}, },
{ {
"date": "2016 tavasz", "date": "2016 tavasz",
"title": "Zenére világító ledsorok", "title": "Zenére világító ledsorok",
"picture": "/static/LED.jpg", "picture": "/static/LED.jpg",
"description": "Egy alkalmazást készítettem, amivel RGB ledsorok színét lehet a zene ritmusára változtatni.", "description": "Egy alkalmazást készítettem, amivel RGB ledsorok színét lehet a zene ritmusára változtatni.",
"more": [ "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.", "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.", "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.", "A működő rendszerről készítettem egy videót, ami alább tekinthető meg.",
{ "type": "video", "src": "static/led720.mp4" } { "type": "video", "src": "static/led720.mp4" }
] ]
} }
], ],
"footer": { "footer": {
"email": "andras.schmelczer@schdesign.hu" "email": "andras.schmelczer@schdesign.hu"
} }
} }
*/

35
src/index.html Normal file
View 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&aacute;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>

View file

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 570 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 475 KiB

After

Width:  |  Height:  |  Size: 475 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Before After
Before After

View file

@ -1,173 +1,173 @@
/* X sign visible in the photo viewer mode. */ /* X sign visible in the photo viewer mode. */
#exit { #exit {
position: absolute; position: absolute;
width: var(--exit-size); width: var(--exit-size);
height: var(--exit-size); height: var(--exit-size);
top: var(--exit-size); top: var(--exit-size);
right: var(--exit-size); right: var(--exit-size);
cursor: pointer; cursor: pointer;
} }
#exit:before, #exit:before,
#exit:after { #exit:after {
content: ""; content: "";
position: absolute; position: absolute;
width: var(--line-width); width: var(--line-width);
height: calc(var(--exit-size) * 1.4142); height: calc(var(--exit-size) * 1.4142);
background: white; background: white;
border-radius: var(--border-radius); border-radius: var(--border-radius);
top: calc(var(--exit-size) * -0.4142 / 2); top: calc(var(--exit-size) * -0.4142 / 2);
left: 50%; left: 50%;
} }
#exit:before { #exit:before {
transform: rotate(45deg); transform: rotate(45deg);
} }
#exit:after { #exit:after {
transform: rotate(-45deg); transform: rotate(-45deg);
} }
/**/ /**/
/* Links with interactive underline. */ /* Links with interactive underline. */
a { a {
text-decoration: none; text-decoration: none;
position: relative; position: relative;
border-bottom: solid 3px var(--light-accent-color); border-bottom: solid 3px var(--light-accent-color);
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
} }
a:after { a:after {
content: ""; content: "";
height: var(--line-width); height: var(--line-width);
background-color: var(--accent-color); background-color: var(--accent-color);
position: absolute; position: absolute;
left: 0; left: 0;
bottom: calc(-1 * var(--line-width)); bottom: calc(-1 * var(--line-width));
width: 0; width: 0;
transition: width var(--transition-time); transition: width var(--transition-time);
} }
a:hover:after { a:hover:after {
width: 100%; width: 100%;
} }
/**/ /**/
/* Line with circle for the timeline sections. */ /* Line with circle for the timeline sections. */
.line { .line {
margin-left: calc(var(--dot-size) / 2); margin-left: calc(var(--dot-size) / 2);
margin-right: var(--line-height); margin-right: var(--line-height);
border-left: var(--line-width) solid var(--text-color); border-left: var(--line-width) solid var(--text-color);
position: relative; position: relative;
} }
.line:before { .line:before {
content: ""; content: "";
position: absolute; position: absolute;
left: calc(-1 / 2 * var(--dot-size) - 1.5 * var(--line-width)); left: calc(-1 / 2 * var(--dot-size) - 1.5 * var(--line-width));
background: var(--bg-color); background: var(--bg-color);
top: 33%; top: 33%;
width: var(--dot-size); width: var(--dot-size);
height: var(--dot-size); height: var(--dot-size);
border-radius: 100%; border-radius: 100%;
border: var(--line-width) var(--text-color) solid; border: var(--line-width) var(--text-color) solid;
} }
/**/ /**/
/* Activity cards. */ /* Activity cards. */
.card { .card {
border-radius: var(--border-radius); border-radius: var(--border-radius);
text-align: center; text-align: center;
padding: var(--margin); padding: var(--margin);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.125); box-shadow: 0 0 5px rgba(0, 0, 0, 0.125);
transition: box-shadow; transition: box-shadow;
transition-duration: var(--transition-time); transition-duration: var(--transition-time);
background: var(--card-color); background: var(--card-color);
} }
.card:hover { .card:hover {
box-shadow: 0 0 3px rgba(0, 0, 0, 0.05); box-shadow: 0 0 3px rgba(0, 0, 0, 0.05);
} }
/**/ /**/
/* Dates related to the lines and cards. */ /* Dates related to the lines and cards. */
.date-narrow-screen, .date-narrow-screen,
.date-wide-screen { .date-wide-screen {
font: 400 1em "Open sans", sans-serif; font: 400 1em "Open sans", sans-serif;
} }
.date-narrow-screen { .date-narrow-screen {
display: none; display: none;
margin: 0; margin: 0;
margin-top: calc(var(--line-height) / 2.25) !important; margin-top: calc(var(--line-height) / 2.25) !important;
color: var(--light-text-color); color: var(--light-text-color);
} }
.date-wide-screen { .date-wide-screen {
position: relative; position: relative;
top: calc(33% + var(--dot-size) + 1ch); top: calc(33% + var(--dot-size) + 1ch);
margin: 0 var(--margin) 0 calc(var(--line-width) + 1ex); margin: 0 var(--margin) 0 calc(var(--line-width) + 1ex);
width: 100px; width: 100px;
} }
/**/ /**/
/* The photo viewer */ /* The photo viewer */
#photo-viewer { #photo-viewer {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
background: var(--photo-viewer-color); background: var(--photo-viewer-color);
z-index: -3; z-index: -3;
opacity: 0; opacity: 0;
transition: opacity var(--transition-time); transition: opacity var(--transition-time);
} }
/* #photo */ /* #photo */
#photo-viewer > img { #photo-viewer > img {
max-width: 80vw; max-width: 80vw;
max-height: 80vh; max-height: 80vh;
} }
/**/ /**/
/* Scrollbar. */ /* Scrollbar. */
body::-webkit-scrollbar-track, body::-webkit-scrollbar-track,
body::-webkit-scrollbar { body::-webkit-scrollbar {
background-color: var(--scroll-color); background-color: var(--scroll-color);
width: 12px; width: 12px;
} }
body::-webkit-scrollbar-thumb { body::-webkit-scrollbar-thumb {
background-color: var(--accent-color); background-color: var(--accent-color);
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
/**/ /**/
/* Selections. */ /* Selections. */
::-moz-selection { ::-moz-selection {
background: var(--accent-color); background: var(--accent-color);
color: white; color: white;
} }
::selection { ::selection {
background: var(--accent-color); background: var(--accent-color);
color: white; color: white;
} }
/**/ /**/
/* Absolute centering parent. */ /* Absolute centering parent. */
.center { .center {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
/**/ /**/
@media (max-width: 900px) { @media (max-width: 900px) {
.line { .line {
display: none; display: none;
} }
/* Disable animation. */ /* Disable animation. */
.card:hover { .card:hover {
box-shadow: 0 0 5px rgba(0, 0, 0, 0.125); box-shadow: 0 0 5px rgba(0, 0, 0, 0.125);
} }
.date-narrow-screen { .date-narrow-screen {
display: block; display: block;
} }
.date-wide-screen { .date-wide-screen {
display: none; display: none;
} }
} }

3
src/styles/index.scss Normal file
View file

@ -0,0 +1,3 @@
@import "main";
@import "elements";
@import "page";

View file

@ -1,28 +1,28 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i|Raleway&subset=latin-ext'); @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i|Raleway&subset=latin-ext');
:root { :root {
--photo-viewer-color: rgba(0, 0, 0, 0.75); --photo-viewer-color: rgba(0, 0, 0, 0.75);
--accent-color: #5264bf; --accent-color: #5264bf;
--light-accent-color: #e5e5ff; --light-accent-color: #e5e5ff;
--scroll-color: #ffd6d6; --scroll-color: #ffd6d6;
--bg-color:linear-gradient(90deg, #fff9e0 0, #ffd6d6 100%); --bg-color:linear-gradient(90deg, #fff9e0 0, #ffd6d6 100%);
--card-color: white; --card-color: white;
--text-color: #31343f; --text-color: #31343f;
--light-text-color: #7a7d8e; --light-text-color: #7a7d8e;
--dot-size: 25px; --dot-size: 25px;
--line-width: 3px; --line-width: 3px;
--exit-size: 25px; --exit-size: 25px;
--line-height: 15px; --line-height: 15px;
--smaller-margin: 25px; --smaller-margin: 25px;
--margin: 35px; --margin: 35px;
--border-radius: 5px; --border-radius: 5px;
--transition-time: 200ms; --transition-time: 200ms;
--width: 765px; --width: 765px;
} }
@media (max-width: 900px) { @media (max-width: 900px) {
:root { :root {
--exit-size: 20px; --exit-size: 20px;
--margin: 25px; --margin: 25px;
--smaller-margin: 20px; --smaller-margin: 20px;
} }
} }

View file

@ -1,130 +1,130 @@
* { * {
margin: 0; margin: 0;
box-sizing: content-box; box-sizing: content-box;
color: var(--text-color); color: var(--text-color);
} }
html { html {
background-color: #31343f; background-color: #31343f;
background: var(--bg-color); background: var(--bg-color);
} }
body { body {
width: var(--width); width: var(--width);
margin: auto; margin: auto;
visibility: hidden; visibility: hidden;
} }
header, header,
footer, footer,
#timeline, #timeline,
#about > p:first-of-type, #about > p:first-of-type,
#timeline > section:not(:first-of-type) > .card { #timeline > section:not(:first-of-type) > .card {
margin-top: var(--margin); margin-top: var(--margin);
} }
#header-pic, #header-pic,
h1 { h1 {
font: 400 3.33em "Raleway", sans-serif; font: 400 3.33em "Raleway", sans-serif;
text-align: center; text-align: center;
} }
#header-pic { #header-pic {
height: 4ch; height: 4ch;
border-radius: 100%; border-radius: 100%;
margin-right: 1.5ex; margin-right: 1.5ex;
} }
p, p,
a { a {
font: 400 1.125em "Open sans", sans-serif; font: 400 1.125em "Open sans", sans-serif;
} }
#about > p { #about > p {
text-align: justify; text-align: justify;
hyphens: auto; hyphens: auto;
} }
#timeline > section { #timeline > section {
display: flex; display: flex;
} }
.card { .card {
flex: 1; flex: 1;
} }
#about > p, #about > p,
.card > *:not(:first-child):not(:last-child), .card > *:not(:first-child):not(:last-child),
.collapsed > *:not(:first-child) { .collapsed > *:not(:first-child) {
margin-top: var(--line-height); margin-top: var(--line-height);
} }
h2 { h2 {
font: 400 2em "Raleway", sans-serif; font: 400 2em "Raleway", sans-serif;
} }
.card img, .card img,
video { video {
width: 100%; width: 100%;
} }
img { img {
user-select: none; user-select: none;
border-radius: var(--border-radius); border-radius: var(--border-radius);
cursor: pointer; cursor: pointer;
} }
.description { .description {
font-style: italic; font-style: italic;
margin-bottom: var(--smaller-margin); margin-bottom: var(--smaller-margin);
} }
.collapsed > p { .collapsed > p {
text-align: left; text-align: left;
} }
.collapsed { .collapsed {
height: 0; height: 0;
overflow: hidden; overflow: hidden;
transition: height var(--transition-time); transition: height var(--transition-time);
} }
.collapsed > *:last-child { .collapsed > *:last-child {
margin-bottom: var(--smaller-margin); margin-bottom: var(--smaller-margin);
} }
footer { footer {
margin-bottom: var(--margin); margin-bottom: var(--margin);
} }
@media (max-width: 900px) { @media (max-width: 900px) {
body { body {
width: 85%; width: 85%;
font-size: 0.85em; font-size: 0.85em;
} }
#photo { #photo {
max-width: 94vw; max-width: 94vw;
} }
.card { .card {
font-size: 0.9em; font-size: 0.9em;
} }
header { header {
flex-direction: column; flex-direction: column;
} }
h1, h1,
#header-pic { #header-pic {
font-size: 3em; font-size: 3em;
} }
#header-pic { #header-pic {
height: 5.5ch; height: 5.5ch;
margin: 0.75ex 0 0.5ex 0; margin: 0.75ex 0 0.5ex 0;
} }
#about > p { #about > p {
text-align: left; text-align: left;
} }
} }

19
src/ts/index.ts Normal file
View 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
View 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;
};

10
tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": false,
"module": "es6",
"target": "es5",
"sourceMap": true,
"allowJs": true
}
}

48
webpack.config.js Normal file
View file

@ -0,0 +1,48 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
devtool: "inline-source-map",
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
hash: true,
xhtml: true,
template: "./src/index.html"
})
],
entry: {
index: "./src/ts/index.ts"
},
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: {
loader: "file-loader",
query: {
outputPath: "images"
}
}
}
]
},
resolve: {
extensions: [".ts"]
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
}
};