diff --git a/src/helper/random.ts b/src/helper/random.ts index 7b3b068..9509744 100644 --- a/src/helper/random.ts +++ b/src/helper/random.ts @@ -1,9 +1,5 @@ -import { polyfillImul } from './polyfill-imul'; - export class Random { - public constructor(private seed: number) { - polyfillImul(); - } + public constructor(private seed: number) {} public get next(): number { // result is in [0, 1) diff --git a/src/page/about/about.html.ts b/src/page/about/about.html.ts index 02ad206..e62031c 100644 --- a/src/page/about/about.html.ts +++ b/src/page/about/about.html.ts @@ -1,12 +1,11 @@ -import { Header } from '../../types/portfolio'; - import './about.scss'; +import { Header } from '../../types/portfolio'; import { html } from '../../types/html'; export const generate = ({ name }: Header): html => ` -
-
-
-

${name}

-
+
+
+
+

${name}

+
`; diff --git a/src/page/about/about.scss b/src/page/about/about.scss index da7d681..30523fe 100644 --- a/src/page/about/about.scss +++ b/src/page/about/about.scss @@ -2,6 +2,7 @@ section#about { @include card-base(); + padding: var(--normal-margin); background-color: var(--accent-color); font-size: 0; @@ -31,7 +32,7 @@ section#about { p, h1 { color: var(--very-light-text-color); - margin-top: var(--small-margin); + margin-top: var(--line-height); } @include on-large-screen { diff --git a/src/page/about/about.ts b/src/page/about/about.ts index 7990763..89aa779 100644 --- a/src/page/about/about.ts +++ b/src/page/about/about.ts @@ -1,6 +1,5 @@ import { PageContent } from '../content/content'; import { Header } from '../../types/portfolio'; - import { generate } from './about.html'; import { createElement } from '../../helper/create-element'; import { PageThemeSwitcher } from '../theme-switcher/theme-switcher'; diff --git a/src/page/background/background.html.ts b/src/page/background/background.html.ts index 4a6d521..bdf0517 100644 --- a/src/page/background/background.html.ts +++ b/src/page/background/background.html.ts @@ -2,5 +2,5 @@ import './background.scss'; import { html } from '../../types/html'; export const generate = (): html => ` - + `; diff --git a/src/page/basics/anchor/anchor.html.ts b/src/page/basics/anchor/anchor.html.ts index d38f780..23848e0 100644 --- a/src/page/basics/anchor/anchor.html.ts +++ b/src/page/basics/anchor/anchor.html.ts @@ -1,7 +1,8 @@ import './anchor.scss'; import { html } from '../../../types/html'; +import { url } from '../../../types/url'; -export const generate = ({ href, text }: { href: string; text: string }): html => ` +export const generate = ({ href, text }: { href: url; text: string }): html => ` ` + +
+ ${svg} +
+

${title}

+
+`; diff --git a/src/page/basics/image-anchor/image-anchor.scss b/src/page/basics/image-anchor/image-anchor.scss new file mode 100644 index 0000000..45e4e6b --- /dev/null +++ b/src/page/basics/image-anchor/image-anchor.scss @@ -0,0 +1,21 @@ +@use '../../../style/mixins' as *; + +.image-anchor { + @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; + } +} diff --git a/src/page/basics/image-anchor/image-anchor.ts b/src/page/basics/image-anchor/image-anchor.ts new file mode 100644 index 0000000..84a9499 --- /dev/null +++ b/src/page/basics/image-anchor/image-anchor.ts @@ -0,0 +1,11 @@ +import { PageElement } from '../../page-element'; +import { createElement } from '../../../helper/create-element'; +import { url } from '../../../types/url'; +import { generate } from './image-anchor.html'; + +export const ImageAnchorFactory = (svg: string, title: string) => + class ImageAnchor extends PageElement { + public constructor(href: url) { + super(createElement(generate({ href, svg, title }))); + } + }; diff --git a/src/page/basics/image/image.html.ts b/src/page/basics/image/image.html.ts index 6b33b43..bb7965f 100644 --- a/src/page/basics/image/image.html.ts +++ b/src/page/basics/image/image.html.ts @@ -16,10 +16,10 @@ export const generate = ({ }): html => ` ${container ? `
` : ''} ${alt} ${container ? `
` : ''} `; diff --git a/src/page/basics/preview/preview.html.ts b/src/page/basics/preview/preview.html.ts new file mode 100644 index 0000000..d4bfc8b --- /dev/null +++ b/src/page/basics/preview/preview.html.ts @@ -0,0 +1,15 @@ +import './preview.scss'; +import play from '../../../static/icons/play-button.svg'; +import loading from '../../../static/icons/loading.svg'; +import { html } from '../../../types/html'; + +export const generate = ({ alt }: { alt: string }): html => ` +
+ +
+ +
${loading}
+
${play}
+
+
+`; diff --git a/src/page/basics/preview/preview.scss b/src/page/basics/preview/preview.scss new file mode 100644 index 0000000..0241f86 --- /dev/null +++ b/src/page/basics/preview/preview.scss @@ -0,0 +1,65 @@ +@use '../../../style/mixins' as *; + +.preview { + position: relative; + + .overlay { + @include square(100%); + position: absolute; + left: 0; + top: 0; + + * { + position: absolute; + left: 0; + right: 0; + } + + .load-button { + @include image-button(var(--large-icon-size)); + @include absolute-center; + @include square(calc(var(--large-icon-size) + var(--normal-margin) * 2)); + + &:hover svg { + box-shadow: var(--shadow); + } + + svg { + border-radius: 10000px; + backdrop-filter: blur(16px); + transition: transform var(--transition-time), box-shadow var(--transition-time); + stroke: var(--normal-text-color); + } + } + + .loading { + visibility: hidden; + &, + & > svg { + @include square(var(--large-icon-size)); + @include absolute-center; + } + } + + iframe { + @include square(100%); + border: none; + &:fullscreen { + border-radius: 0; + } + } + } + + &.loaded { + .figure-container, + .load-button { + visibility: hidden; + } + } + + &.waiting { + .loading { + visibility: visible; + } + } +} diff --git a/src/page/basics/preview/preview.ts b/src/page/basics/preview/preview.ts new file mode 100644 index 0000000..cf7d6e7 --- /dev/null +++ b/src/page/basics/preview/preview.ts @@ -0,0 +1,39 @@ +import { PageElement } from '../../page-element'; +import { createElement } from '../../../helper/create-element'; +import { generate } from './preview.html'; +import { Image } from '../image/image'; +import { ResponsiveImage } from '../../../types/responsive-image'; +import { OnLoadEvent } from '../../../events/concrete-events/on-load-event'; + +export class Preview extends PageElement { + public constructor(poster: ResponsiveImage, private readonly url: string, alt: string) { + super(createElement(generate({ alt }))); + this.url += '?portfolioView'; + this.attachElementByReplacing('.poster', new Image(poster, alt)); + this.query('.load-button').addEventListener('click', this.loadContent.bind(this)); + this.query('iframe').addEventListener('load', () => { + this.htmlRoot.classList.remove('waiting'); + }); + } + + public handleOnLoadEvent(event: OnLoadEvent): OnLoadEvent { + new IntersectionObserver(e => { + if (!e[0].isIntersecting) { + this.unloadContent(); + } + }).observe(this.htmlRoot.parentElement); + + return event; + } + + public loadContent() { + this.htmlRoot.classList.add('waiting'); + this.htmlRoot.classList.add('loaded'); + (this.query('iframe') as HTMLIFrameElement).src = this.url; + } + + public unloadContent() { + this.htmlRoot.classList.remove('loaded'); + (this.query('iframe') as HTMLIFrameElement).src = ''; + } +} diff --git a/src/page/basics/text/text.scss b/src/page/basics/text/text.scss index 851b58e..50f9ae0 100644 --- a/src/page/basics/text/text.scss +++ b/src/page/basics/text/text.scss @@ -1,4 +1,3 @@ .primitive-text { text-align: left; - margin-top: var(--line-height); } diff --git a/src/page/footer/footer.scss b/src/page/footer/footer.scss index bb1c22f..bbe8edc 100644 --- a/src/page/footer/footer.scss +++ b/src/page/footer/footer.scss @@ -6,6 +6,10 @@ footer#page-footer { margin-top: var(--large-margin); width: 100%; + a { + @include link; + } + h2 { @include title-font(); } @@ -27,11 +31,8 @@ footer#page-footer { img, svg { @include max-square(var(--icon-size)); - margin-right: var(--small-margin); - - * { - fill: var(--normal-text-color); - } + margin-right: calc(var(--small-margin) / 2); + stroke: var(--normal-text-color); } a { diff --git a/src/page/image-viewer/image-viewer.html.ts b/src/page/image-viewer/image-viewer.html.ts index feab342..c3a2065 100644 --- a/src/page/image-viewer/image-viewer.html.ts +++ b/src/page/image-viewer/image-viewer.html.ts @@ -4,8 +4,8 @@ import './image-viewer.scss'; import { html } from '../../types/html'; export const generate = (): html => ` -
-
- cancel -
+
+
+
${cancel}
+
`; diff --git a/src/page/image-viewer/image-viewer.scss b/src/page/image-viewer/image-viewer.scss index b3ea7fe..7b4bd07 100644 --- a/src/page/image-viewer/image-viewer.scss +++ b/src/page/image-viewer/image-viewer.scss @@ -28,12 +28,11 @@ section#image-viewer { } #cancel { - @include square(var(--icon-size)); + @include square(calc(var(--large-icon-size) + var(--normal-margin) * 2)); position: absolute; - box-sizing: content-box; - padding: var(--normal-margin); right: 0; top: 0; - cursor: pointer; + + @include image-button(var(--large-icon-size)); } } diff --git a/src/page/image-viewer/image-viewer.ts b/src/page/image-viewer/image-viewer.ts index d5fbec8..9fb9e04 100644 --- a/src/page/image-viewer/image-viewer.ts +++ b/src/page/image-viewer/image-viewer.ts @@ -17,8 +17,9 @@ export class PageImageViewer extends PageElement { const media = Array.prototype.slice.call(document.querySelectorAll('img')); media - .filter((e: HTMLElement) => e.parentElement !== this.htmlRoot) + .filter((e: HTMLElement) => !e.attributes['image-viewer-ignore']) .forEach((e: HTMLImageElement) => (e.onclick = this.handleClick.bind(this))); + return super.handleOnLoadEvent(event); } diff --git a/src/page/page-element.ts b/src/page/page-element.ts index 9eebaf8..001c11d 100644 --- a/src/page/page-element.ts +++ b/src/page/page-element.ts @@ -44,6 +44,11 @@ export abstract class PageElement extends EventHandler implements EventBroadcast this.children.push(element); } + protected attachElementAsChildOf(query: string, element: PageElement) { + this.query(query).appendChild(element.htmlRoot); + this.children.push(element); + } + protected attachElement(element: PageElement) { this.htmlRoot.appendChild(element.htmlRoot); this.children.push(element); diff --git a/src/page/theme-switcher/theme-switcher.html.ts b/src/page/theme-switcher/theme-switcher.html.ts index eca7cd8..292ea9e 100644 --- a/src/page/theme-switcher/theme-switcher.html.ts +++ b/src/page/theme-switcher/theme-switcher.html.ts @@ -2,5 +2,5 @@ import './theme-switcher.scss'; import { html } from '../../types/html'; export const generate = (): html => ` - + `; diff --git a/src/page/timeline/timeline-element/timeline-element.html.ts b/src/page/timeline/timeline-element/timeline-element.html.ts index de0cbb8..7881dd9 100644 --- a/src/page/timeline/timeline-element/timeline-element.html.ts +++ b/src/page/timeline/timeline-element/timeline-element.html.ts @@ -1,34 +1,35 @@ import { TimelineElement } from '../../../types/portfolio'; - +import info from '../../../static/icons/info.svg'; import './timeline-element.scss'; import { html } from '../../../types/html'; export const generate = ( { date, title, more }: TimelineElement, - showMore: string, - showLess: string + showMore: string ): html => ` -
-
-
-

${date}

+
+
+
+

${date}

+
+
+
+
+

${title}

+
+ ${more ? '
' : ''} +
+ ${ + more + ? ` +
+
${info}
+

${showMore}

+
` + : '' + }
-
-

${title}

-
-
- ${ - more - ? ` -
- - ` - : '' - } - -
-
+
+ +
`; 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; }