+
`;
diff --git a/src/page/timeline/timeline-element/timeline-element.scss b/src/page/timeline/timeline-element/timeline-element.scss
index 47129fa..fdd5908 100644
--- a/src/page/timeline/timeline-element/timeline-element.scss
+++ b/src/page/timeline/timeline-element/timeline-element.scss
@@ -91,46 +91,75 @@ section.timeline-element {
background-color: var(--card-color);
}
- & > *:not(:first-child) {
- margin-top: var(--line-height);
+ img,
+ video,
+ iframe {
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
}
- .content {
- margin-top: 0;
- }
+ $border-width: 1px;
- h2 {
- @include sub-title-font();
- }
-
- & > p {
- font-style: italic;
- text-align: center;
- }
-
- .more {
- overflow: hidden;
- height: 0;
- transition: height var(--transition-time);
- }
-
- .buttons {
- position: relative;
- margin-top: var(--line-height);
-
- .show-more,
- .show-less {
- transition: opacity var(--transition-time);
+ .lower {
+ & > * {
+ padding: 0 var(--normal-margin);
+ margin-top: var(--small-margin);
}
- .show-more {
- opacity: 1;
+ h2 {
+ @include sub-title-font();
}
- .show-less {
- @include absolute-center();
- opacity: 0;
- visibility: hidden;
+ & > p {
+ text-align: center;
+ }
+
+ .more {
+ overflow: hidden;
+ margin: 0;
+ height: 0;
+ transition: height var(--transition-time);
+
+ .content p {
+ margin-top: var(--line-height);
+ }
+ }
+
+ .buttons {
+ display: flex;
+ justify-content: center;
+ border-top: $border-width solid var(--normal-text-color);
+ margin: 0;
+ padding: 0;
+ margin-top: var(--small-margin);
+
+ .info-button {
+ @include image-button(var(--icon-size));
+
+ .svgContainer {
+ position: relative;
+ margin: auto;
+ @include square(var(--icon-size));
+
+ svg {
+ stroke: var(--normal-text-color);
+ }
+ }
+
+ p {
+ padding-bottom: var(--small-margin);
+ font-size: 0.9rem;
+ font-style: italic;
+ }
+ }
+
+ & > * {
+ flex: 1;
+
+ padding-top: var(--small-margin);
+ &:not(:last-child) {
+ border-right: $border-width solid var(--normal-text-color);
+ }
+ }
}
}
}
diff --git a/src/page/timeline/timeline-element/timeline-element.ts b/src/page/timeline/timeline-element/timeline-element.ts
index 896fb39..a837208 100644
--- a/src/page/timeline/timeline-element/timeline-element.ts
+++ b/src/page/timeline/timeline-element/timeline-element.ts
@@ -8,13 +8,15 @@ import { OnBodyDimensionsChangedEvent } from '../../../events/concrete-events/on
export class PageTimelineElement extends PageElement {
private isOpen: boolean;
private more: HTMLElement;
+ private showMore: string;
+ private showLess: string;
public constructor(
timelineElement: TimelineElement,
showMore: string,
showLess: string
) {
- const root = createElement(generate(timelineElement, showMore, showLess));
+ const root = createElement(generate(timelineElement, showMore));
if (timelineElement.more) {
const content = new PageContent(timelineElement.more);
@@ -23,30 +25,25 @@ export class PageTimelineElement extends PageElement {
this.isOpen = false;
this.more = root.querySelector('.more');
this.more.appendChild(content.htmlRoot);
- window.addEventListener('resize', this.handleResize.bind(this));
- root
- .querySelector('.buttons')
- .addEventListener('click', this.toggleOpen.bind(this));
+ addEventListener('resize', this.handleResize.bind(this));
+
+ this.query('.info-button').addEventListener('click', this.toggleOpen.bind(this));
} else super(root);
this.attachElementByReplacing('.figure', timelineElement.figure);
this.attachElementByReplacing('.description', timelineElement.description);
+ timelineElement.links.forEach(l => this.attachElementAsChildOf('.buttons', l));
- if (timelineElement.link) {
- this.attachElementByReplacing('.link', timelineElement.link);
- }
+ this.showMore = showMore;
+ this.showLess = showLess;
}
private toggleOpen() {
- const showMore = this.query('.show-more') as HTMLElement;
- const showLess = this.query('.show-less') as HTMLElement;
if (this.isOpen) {
- PageTimelineElement.show(showMore);
- PageTimelineElement.hide(showLess);
+ this.query('.info-button p').innerText = this.showMore;
this.closeMore();
} else {
- PageTimelineElement.show(showLess);
- PageTimelineElement.hide(showMore);
+ this.query('.info-button p').innerText = this.showLess;
this.openMore();
}
diff --git a/src/page/timeline/timeline.html.ts b/src/page/timeline/timeline.html.ts
index 6b5d567..2ca3371 100644
--- a/src/page/timeline/timeline.html.ts
+++ b/src/page/timeline/timeline.html.ts
@@ -2,5 +2,5 @@ import './timeline.scss';
import { html } from '../../types/html';
export const generate = (): html => `
-
+
`;
diff --git a/src/page/timeline/timeline.scss b/src/page/timeline/timeline.scss
index 299da19..9e72dfa 100644
--- a/src/page/timeline/timeline.scss
+++ b/src/page/timeline/timeline.scss
@@ -1,10 +1,7 @@
@use '../../style/mixins' as *;
-div#timeline {
- @include on-large-screen {
- // workaround for IE
- & > :first-child {
- margin-top: var(--large-margin);
- }
+@include on-large-screen {
+ div#timeline > :first-child {
+ margin-top: var(--large-margin);
}
}
diff --git a/src/portfolio.ts b/src/portfolio.ts
index 8ff32d9..a5a7027 100644
--- a/src/portfolio.ts
+++ b/src/portfolio.ts
@@ -6,10 +6,18 @@ import { PageHeader } from './page/about/about';
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 { Anchor } from './page/basics/anchor/anchor';
+import { Body } from './page/body/body';
+import { ImageAnchorFactory } from './page/basics/image-anchor/image-anchor';
+import { Preview } from './page/basics/preview/preview';
import me from './static/media/me.jpg';
+import declared from './static/media/decla-red.png';
import forexMP4 from './static/media/forex.mp4';
import forexWEBM from './static/media/forex.webm';
+import thesis from './static/media/andras-schmelczer-thesis.pdf';
import adAstraMP4 from './static/media/ad_astra_720.mp4';
+import cvIcon from './static/icons/cv.svg';
import adAstraWEBM from './static/media/ad_astra_720.webm';
import ad_astra_index from './static/media/ad_astra.jpg';
import myNotes from './static/media/my-notes.png';
@@ -24,27 +32,26 @@ import led from './static/media/led.jpg';
import cvEnglish from './static/cv/cv_andras_schmelczer.pdf';
import ledMP4 from './static/media/led.mp4';
import ledWEBM from './static/media/led.webm';
-import { PageBackground } from './page/background/background';
-import { Anchor } from './page/basics/anchor/anchor';
-
-import { Body } from './page/body/body';
+import githubIcon from './static/icons/github.svg';
+import openIcon from './static/icons/open.svg';
export const create = () => {
+ const GitHub = ImageAnchorFactory(githubIcon, 'Open on GitHub');
+ const Open = ImageAnchorFactory(openIcon, 'Open in new tab');
+ const Thesis = ImageAnchorFactory(cvIcon, 'Download thesis');
+
const page = {
imageViewer: new PageImageViewer(),
header: new PageHeader({
name: `András Schmelczer`,
picture: new Image(me, `a picture of me`, false),
about: [
- new Text(
- `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 starting my last semester at the
- Budapest University of Technology and Economics, I feel I am getting closer to it every day.`
- ),
- new Text(
- `You can see some of the more interesting projects I have worked on below.`
- ),
+ 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.`),
],
}),
timeline: new PageTimeline({
@@ -52,29 +59,53 @@ export const create = () => {
showLessText: `Show less`,
elements: [
{
- title: `SDF-2D library`,
+ title: `Multiplayer game`,
date: `2020 Autumn`,
- figure: new Image(sdf2d, `a screenshot of a demo scene`),
+ figure: new Preview(
+ declared,
+ 'https://decla.red',
+ 'The website of the video game'
+ ),
description: new Text(
- `I created an NPM package for efficiently rendering and shading 2D scenes described
- by signed distance fields (SDF-s). It supports both WebGL and WebGL2 and is easily extendible.`
+ `Using SDF-2D, I developed a conquest-style multiplayer browser game. It even runs on mobiles.`
),
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 minimize unnecessary
- instructions. Additionally, there were some interesting quirks of specific hardware that also needed to be overcame.`
- ),
- new Text(
- `The end result is a reusable library written in TypeScript with a simple and elegant API.
+ 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(
+ sdf2d,
+ '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.`
- ),
- new Anchor(`https://sdf2d.schmelczer.dev`, `View it in action`),
- new Anchor(
- `https://github.com/schmelczerandras/sdf-2d`,
- `Check it out on GitHub`
- ),
+ mesmerizing demo scenes.`),
+ ],
+ links: [
+ new GitHub('https://github.com/schmelczerandras/sdf-2d'),
+ new Open('https://sdf2d.schmelczer.dev'),
],
},
{
@@ -86,33 +117,24 @@ export const create = () => {
adAstraWEBM,
`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.`
- ),
+ 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
+ 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.
- An optimized SIMD utilizing low level driver is used for drawing on the display.
- I think the code base is quite readable and at the same time the
- maximum frame times are between 15ms and 20ms at 8MHz.`
- ),
- 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.`
- ),
+ 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.`
- ),
- new Anchor(
- `https://github.com/schmelczerandras/ad_astra`,
- `View it on GitHub`
+ 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')],
},
{
@@ -140,6 +162,7 @@ export const create = () => {
a mostly profitable trading strategy is viable. In my free time I may put more work into it.`
),
],
+ links: [],
},
{
date: `2019 November`,
@@ -161,6 +184,7 @@ export const create = () => {
It was also my first experience with Android development.`
),
],
+ links: [],
},
{
date: `2018 October - November`,
@@ -183,6 +207,7 @@ export const create = () => {
the communication between the layers. For drawing the frontend HTML5 canvas is utilized.`
),
],
+ links: [],
},
{
date: `2018 October - November`,
@@ -201,6 +226,7 @@ export const create = () => {
directly uploaded to the simulation backend.`
),
],
+ links: [],
},
{
date: `2018 July - August`,
@@ -229,6 +255,7 @@ export const create = () => {
were also made by me using Blender.`
),
],
+ links: [],
},
{
date: `2018 June`,
@@ -251,8 +278,8 @@ export const create = () => {
`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).`
),
- new Anchor('https://color.schmelczer.dev', `color.schmelczer.dev`),
],
+ links: [new Open('color.schmelczer.dev')],
},
{
date: `2017 autumn`,
@@ -268,13 +295,14 @@ export const create = () => {
),
new Text(`I did this as a homework for my Basics of Programming course.`),
],
+ links: [],
},
{
date: `2016 summer`,
title: `Photos`,
figure: new Image(photos, `a picture of the website`),
description: new Text(`A simple web page where you can view my photos.`),
- link: new Anchor(`https://photo.schmelczer.dev`, `photo.schmelczer.dev`),
+ links: [new Open('https://photo.schmelczer.dev')],
},
{
date: `2016 spring`,
@@ -300,12 +328,13 @@ export const create = () => {
the settings also got built using vanilla web development technologies.`
),
],
+ links: [],
},
],
}),
footer: new PageFooter({
title: `Learn more`,
- curiumVitaes: [{ name: `Curriculum vitae`, url: cvEnglish }],
+ 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
diff --git a/src/static/icons/cancel.svg b/src/static/icons/cancel.svg
index 1d7600f..aa8be64 100644
--- a/src/static/icons/cancel.svg
+++ b/src/static/icons/cancel.svg
@@ -1,4 +1,5 @@
-
-
+
\ No newline at end of file
diff --git a/src/static/media/andras-schmelczer-thesis.pdf b/src/static/media/andras-schmelczer-thesis.pdf
new file mode 100644
index 0000000..258166f
Binary files /dev/null and b/src/static/media/andras-schmelczer-thesis.pdf differ
diff --git a/src/static/media/decla-red.png b/src/static/media/decla-red.png
new file mode 100644
index 0000000..7aab0a5
Binary files /dev/null and b/src/static/media/decla-red.png differ
diff --git a/src/style/a.scss b/src/style/a.scss
deleted file mode 100644
index 18da81c..0000000
--- a/src/style/a.scss
+++ /dev/null
@@ -1,48 +0,0 @@
-@use 'sass:color';
-@use 'style/mixins' as *;
-
-a {
- $border-shift: 10px;
- $line-width: 2px;
-
- @include special-text-font();
- text-decoration: none;
- cursor: pointer;
- position: relative;
- display: inline-block;
- overflow: hidden;
-
- padding: 0 3px $line-width 0;
-
- &:before,
- &:after {
- content: '';
- display: block;
- position: absolute;
- bottom: 0;
- }
-
- &:before {
- width: calc(100% + #{$border-shift});
- border-bottom: $line-width dashed var(--accent-color);
- transition: transform var(--transition-time);
- }
-
- &:after {
- width: 100%;
- height: $line-width;
- background: linear-gradient(
- 90deg,
- var(--card-color) 0,
- transparent 4px,
- transparent calc(100% - 4px),
- var(--card-color) 100%
- );
- }
-
- &:hover {
- &:before {
- transform: translateX(-$border-shift);
- }
- }
-}
diff --git a/src/style/mixins.scss b/src/style/mixins.scss
index be1150e..459578c 100644
--- a/src/style/mixins.scss
+++ b/src/style/mixins.scss
@@ -12,6 +12,24 @@ $breakpoint-width: 925px !default;
}
}
+@mixin image-button($icon-size) {
+ display: block;
+ box-sizing: content-box;
+ cursor: pointer;
+ text-decoration: none;
+
+ &:hover svg {
+ transform: translateX(-50%) translateY(-50%) scale(1.15);
+ }
+
+ svg {
+ @include absolute-center;
+ @include square($icon-size);
+ transition: transform var(--transition-time);
+ transform-origin: center center;
+ }
+}
+
@mixin center-children() {
display: flex;
align-items: center;
@@ -27,7 +45,6 @@ $breakpoint-width: 925px !default;
@mixin card-base() {
text-align: center;
- padding: var(--normal-margin);
box-shadow: var(--shadow);
z-index: 1;
width: 100%;
@@ -64,7 +81,7 @@ $breakpoint-width: 925px !default;
@mixin main-font() {
font: 400 1.1rem 'Open Sans', sans-serif;
color: var(--normal-text-color);
- line-height: 1.6;
+ line-height: 1.8;
}
@mixin special-text-font() {
@@ -72,3 +89,49 @@ $breakpoint-width: 925px !default;
color: var(--special-text-color);
font-style: italic;
}
+
+@mixin link {
+ $border-shift: 10px;
+ $line-width: 2px;
+
+ @include special-text-font();
+ text-decoration: none;
+ cursor: pointer;
+ position: relative;
+ display: inline-block;
+ overflow: hidden;
+
+ padding: 0 3px $line-width 0;
+
+ &:before,
+ &:after {
+ content: '';
+ display: block;
+ position: absolute;
+ bottom: 0;
+ }
+
+ &:before {
+ width: calc(100% + #{$border-shift});
+ border-bottom: $line-width dashed var(--accent-color);
+ transition: transform var(--transition-time);
+ }
+
+ &:after {
+ width: 100%;
+ height: $line-width;
+ background: linear-gradient(
+ 90deg,
+ var(--card-color) 0,
+ transparent 4px,
+ transparent calc(100% - 4px),
+ var(--card-color) 100%
+ );
+ }
+
+ &:hover {
+ &:before {
+ transform: translateX(-$border-shift);
+ }
+ }
+}
diff --git a/src/style/vars.scss b/src/style/vars.scss
index 5ee4edf..a1402e8 100644
--- a/src/style/vars.scss
+++ b/src/style/vars.scss
@@ -27,9 +27,9 @@
--normal-margin: 45px;
--small-margin: 25px;
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1), 0 0 1px rgba(0, 0, 0, 0.2);
- --inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.075),
- inset 0 0 1px rgba(0, 0, 0, 0.2);
- --icon-size: 35px;
+ --inset-shadow: inset 0 -9px 7px -7px rgb(0, 0, 0, 0.07);
+ --icon-size: 45px;
+ --large-icon-size: 80px;
--body-width: 765px;
}
}
@@ -41,9 +41,9 @@
--normal-margin: 30px;
--small-margin: 15px;
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.075), 0 0 1px rgba(0, 0, 0, 0.125);
- --inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.05),
- inset 0 0 1px rgba(0, 0, 0, 0.125);
- --icon-size: 25px;
+ --inset-shadow: inset 0 -9px 7px -7px rgb(0, 0, 0, 0.07);
+ --icon-size: 35px;
+ --large-icon-size: 55px;
--body-width: 90%;
}
}
@@ -52,7 +52,7 @@
--background: #242638;
--normal-text-color: #ffffff;
--card-color: #263551;
- --blurred-card-color: #26355166;
+ --blurred-card-color: #26355155;
--blur-radius: 24px;
--special-text-color: #ffffff;
--inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.175),
diff --git a/src/styles.scss b/src/styles.scss
index 2ef0c73..27864da 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,6 +1,5 @@
@use 'style/vars';
@use 'style/mixins' as *;
-@use 'style/a';
@use 'style/animations/animations';
@use 'style/dark-mode/dark-mode';
@@ -55,7 +54,7 @@ html {
.figure-container {
font-size: 0;
box-shadow: var(--inset-shadow);
- margin-top: var(--line-height);
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
pointer-events: none;
cursor: pointer;
diff --git a/src/types/portfolio.ts b/src/types/portfolio.ts
index f0c6297..02508c5 100644
--- a/src/types/portfolio.ts
+++ b/src/types/portfolio.ts
@@ -1,9 +1,10 @@
import { Video } from '../page/basics/video/video';
import { Text } from '../page/basics/text/text';
import { Image } from '../page/basics/image/image';
-import { Anchor } from '../page/basics/anchor/anchor';
+
import { PageElement } from '../page/page-element';
import { url } from './url';
+import { Preview } from '../page/basics/preview/preview';
export interface Portfolio {
header: Header;
@@ -24,18 +25,18 @@ export interface Timeline {
}
export interface TimelineElement {
- title: string;
date: string;
- figure: Image | Video;
+ figure: Image | Video | Preview;
+ title: string;
description: Text;
more?: Content;
- link?: Anchor;
+ links: Array;
}
export interface Footer {
title: string;
email: string;
- curiumVitaes: Array;
+ curriculaVitae: Array;
lastEditText: string;
lastEdit: Date;
}