Update features

This commit is contained in:
schmelczerandras 2020-11-18 23:31:26 +01:00
parent f66f052d7e
commit 4d7d15c3c7
34 changed files with 482 additions and 238 deletions

View file

@ -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)

View file

@ -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 => `
<section id="about">
<div class="picture"></div>
<div class="placeholder"></div>
<h1>${name}</h1>
</section>
<section id="about">
<div class="picture"></div>
<div class="placeholder"></div>
<h1>${name}</h1>
</section>
`;

View file

@ -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 {

View file

@ -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';

View file

@ -2,5 +2,5 @@ import './background.scss';
import { html } from '../../types/html';
export const generate = (): html => `
<canvas id="background"></canvas>
<canvas id="background"></canvas>
`;

View file

@ -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 => `
<a class="primitive-anchor"
href="${href}"
target="_blank"

View file

@ -0,0 +1,23 @@
import './image-anchor.scss';
import { html } from '../../../types/html';
import { url } from '../../../types/url';
export const generate = ({
href,
svg,
title,
}: {
href: url;
svg: url;
title: string;
}): html => `
<a class="image-anchor"
href="${href}"
target="_blank"
>
<div class="svgContainer">
${svg}
</div>
<p>${title}</p>
</a>
`;

View file

@ -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;
}
}

View file

@ -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 })));
}
};

View file

@ -16,10 +16,10 @@ export const generate = ({
}): html => `
${container ? `<div class="figure-container">` : ''}
<img tabindex="0"
srcset="${image.srcSet}"
sizes="${sizes}"
src="${last(image.images)?.path}"
alt="${alt}"
srcset="${image.srcSet}"
sizes="${sizes}"
src="${last(image.images)?.path}"
alt="${alt}"
/>
${container ? `</div>` : ''}
`;

View file

@ -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 => `
<div class="preview">
<img image-viewer-ignore class="poster" />
<div class="overlay">
<iframe title="${alt}" height=300 allowfullscreen loading="lazy"></iframe>
<div class="loading">${loading}</div>
<div class="load-button">${play}</div>
</div>
</div>
`;

View file

@ -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;
}
}
}

View file

@ -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 = '';
}
}

View file

@ -1,4 +1,3 @@
.primitive-text {
text-align: left;
margin-top: var(--line-height);
}

View file

@ -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 {

View file

@ -4,8 +4,8 @@ import './image-viewer.scss';
import { html } from '../../types/html';
export const generate = (): html => `
<section id="image-viewer">
<div id="container"></div>
<img tabindex="0" id="cancel" src="${cancel}" alt="cancel"/>
</section>
<section id="image-viewer">
<div id="container"></div>
<div tabindex="0" id="cancel">${cancel}</div>
</section>
`;

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -2,5 +2,5 @@ import './theme-switcher.scss';
import { html } from '../../types/html';
export const generate = (): html => `
<input id="theme-switcher" aria-label="color-theme-switch" type="checkbox" name="switch-theme"/>
<input id="theme-switcher" aria-label="color-theme-switch" type="checkbox" name="switch-theme"/>
`;

View file

@ -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 => `
<section class="timeline-element">
<div class="line-container">
<div class="line"></div>
<p class="date">${date}</p>
<section class="timeline-element">
<div class="line-container">
<div class="line"></div>
<p class="date">${date}</p>
</div>
<div class="card">
<div class="figure"></div>
<div class="lower">
<h2>${title}</h2>
<div class="description"></div>
${more ? '<div class="more"></div>' : ''}
<div class="buttons">
${
more
? `
<div class="info-button">
<div class="svgContainer">${info}</div>
<p>${showMore}</p>
</div>`
: ''
}
</div>
<div class="card">
<h2>${title}</h2>
<div class="figure"></div>
<div class="description"></div>
${
more
? `
<div class="more"></div>
<div class="buttons">
<a tabindex="0" class="show-more">${showMore}</a>
<a tabindex="0" class="show-less">${showLess}</a>
</div>
`
: ''
}
<div class="link"></div>
</div>
</section>
</div>
</div>
</section>
`;

View file

@ -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);
}
}
}
}
}

View file

@ -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();
}

View file

@ -2,5 +2,5 @@ import './timeline.scss';
import { html } from '../../types/html';
export const generate = (): html => `
<div id="timeline"></div>
<div id="timeline"></div>
`;

View file

@ -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);
}
}

View file

@ -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 lifes 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

View file

@ -1,4 +1,5 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 241.171 241.171" style="enable-background:new 0 0 241.171 241.171;" xml:space="preserve" width="512px" height="512px">
<path d="M138.138,120.754l99.118-98.576c4.752-4.704,4.752-12.319,0-17.011c-4.74-4.704-12.439-4.704-17.179,0 l-99.033,98.492L21.095,3.699c-4.74-4.752-12.439-4.752-17.179,0c-4.74,4.764-4.74,12.475,0,17.227l99.876,99.888L3.555,220.497 c-4.74,4.704-4.74,12.319,0,17.011c4.74,4.704,12.439,4.704,17.179,0l100.152-99.599l99.551,99.563 c4.74,4.752,12.439,4.752,17.179,0c4.74-4.764,4.74-12.475,0-17.227L138.138,120.754z" class="active-path" fill="#fff9e0"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 24 24" stroke-width="1.5" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>

Before

Width:  |  Height:  |  Size: 730 B

After

Width:  |  Height:  |  Size: 321 B

Before After
Before After

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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),

View file

@ -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;

View file

@ -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<PageElement>;
}
export interface Footer {
title: string;
email: string;
curiumVitaes: Array<CV>;
curriculaVitae: Array<CV>;
lastEditText: string;
lastEdit: Date;
}