Improve image handling & fix shadows
This commit is contained in:
parent
bc5074b28d
commit
2bb2117a59
47 changed files with 330 additions and 329 deletions
|
|
@ -24,11 +24,13 @@ import { sdf2d } from './projects/sdf2d';
|
||||||
import { towers } from './projects/towers';
|
import { towers } from './projects/towers';
|
||||||
import { CV, Email, GitHubLink, LinkedIn } from './shared';
|
import { CV, Email, GitHubLink, LinkedIn } from './shared';
|
||||||
|
|
||||||
|
const imageViewer = new ImageViewer();
|
||||||
const main = new Main(
|
const main = new Main(
|
||||||
new Header({
|
new Header({
|
||||||
name: 'András Schmelczer',
|
name: 'András Schmelczer',
|
||||||
image: me,
|
image: me,
|
||||||
imageAltText: 'a picture of me',
|
imageAltText: 'a picture of me',
|
||||||
|
imageViewer,
|
||||||
about: [
|
about: [
|
||||||
'With more than six years of professional software engineering experience and a degree in Computer Science, I can confidently undertake any challenge. My interests span diverse areas, allowing me to design complex — even multidisciplinary — systems with a clear understanding.',
|
'With more than six years of professional software engineering experience and a degree in Computer Science, I can confidently undertake any challenge. My interests span diverse areas, allowing me to design complex — even multidisciplinary — systems with a clear understanding.',
|
||||||
|
|
||||||
|
|
@ -56,7 +58,7 @@ const main = new Main(
|
||||||
platformGame,
|
platformGame,
|
||||||
photos,
|
photos,
|
||||||
leds,
|
leds,
|
||||||
].map((p) => new TimelineElement(p, 'Show details', 'Show less')),
|
].map((p) => new TimelineElement(p, 'Show details', 'Show less', imageViewer)),
|
||||||
|
|
||||||
Contact({
|
Contact({
|
||||||
title: 'Get in touch',
|
title: 'Get in touch',
|
||||||
|
|
@ -72,6 +74,6 @@ const main = new Main(
|
||||||
|
|
||||||
export const portfolio: Array<PageElement> = [
|
export const portfolio: Array<PageElement> = [
|
||||||
main,
|
main,
|
||||||
new ImageViewer(),
|
|
||||||
new UpArrowButton(main, 'go up'),
|
new UpArrowButton(main, 'go up'),
|
||||||
|
imageViewer,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Video } from '../../page/figure/video/video';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import { Video } from '../../page/video/video';
|
|
||||||
import adAstraPoster from '../media/ad_astra.jpg';
|
import adAstraPoster from '../media/ad_astra.jpg';
|
||||||
import adAstraMp4 from '../media/mp4/ad_astra.mp4';
|
import adAstraMp4 from '../media/mp4/ad_astra.mp4';
|
||||||
import adAstraWebM from '../media/webm/ad_astra.webm';
|
import adAstraWebM from '../media/webm/ad_astra.webm';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Preview } from '../../page/preview/preview';
|
import { Preview } from '../../page/figure/preview/preview';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import avoidPoster from '../media/avoid.png';
|
import avoidPoster from '../media/avoid.png';
|
||||||
import { Open } from '../shared';
|
import { Open } from '../shared';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Video } from '../../page/figure/video/video';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import { Video } from '../../page/video/video';
|
|
||||||
import citySimulationMp4 from '../media/mp4/simulation.mp4';
|
import citySimulationMp4 from '../media/mp4/simulation.mp4';
|
||||||
import citySimulationPoster from '../media/simulation.jpg';
|
import citySimulationPoster from '../media/simulation.jpg';
|
||||||
import citySimulationWebM from '../media/webm/simulation.webm';
|
import citySimulationWebM from '../media/webm/simulation.webm';
|
||||||
|
|
@ -16,7 +16,7 @@ export const citySimulation: TimelineElementParameters = {
|
||||||
}),
|
}),
|
||||||
description: 'I simulated a city where car crashes are more frequent than usual.',
|
description: 'I simulated a city where car crashes are more frequent than usual.',
|
||||||
more: [
|
more: [
|
||||||
'The state of the traffic lights can be changed through a REST API. 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 as the context for a cybersecurity challenge on PLCs. With the help of this program, the contestants could instantly see the effect of their work.',
|
'The state of the traffic lights can be changed through a REST API. 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 as the context for a cybersecurity challenge about PLCs. With the help of this program, the contestants could instantly see the effect of their work.',
|
||||||
|
|
||||||
'An exciting aspect of the project was building it in a server-client architecture. Every decision of the agents is calculated server-side. The real challenge was broadcasting these decisions in a fault-tolerant way using minimal bandwidth.',
|
'An exciting aspect of the project was building it in a server-client architecture. Every decision of the agents is calculated server-side. The real challenge was broadcasting these decisions in a fault-tolerant way using minimal bandwidth.',
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import colorsPoster from '../media/color.jpg';
|
import colorsPoster from '../media/color.jpg';
|
||||||
|
|
||||||
export const colors: TimelineElementParameters = {
|
export const colors: TimelineElementParameters = {
|
||||||
title: 'Photo colour grader',
|
title: 'Photo colour grader',
|
||||||
date: '2018 June',
|
date: '2018 June',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: colorsPoster,
|
image: colorsPoster,
|
||||||
alt: 'a picture of the app',
|
alt: 'a picture of the app',
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description: 'An innovative (at least I thought so) colour grader web application.',
|
description: 'An innovative (at least I thought so) colour grader web application.',
|
||||||
more: [
|
more: [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Preview } from '../../page/preview/preview';
|
import { Preview } from '../../page/figure/preview/preview';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import declaredPoster from '../media/decla-red.png';
|
import declaredPoster from '../media/decla-red.png';
|
||||||
import bscThesis from '../media/sdf2d-andras-schmelczer.pdf';
|
import bscThesis from '../media/sdf2d-andras-schmelczer.pdf';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Video } from '../../page/figure/video/video';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import { Video } from '../../page/video/video';
|
|
||||||
import forexPoster from '../media/forex.jpg';
|
import forexPoster from '../media/forex.jpg';
|
||||||
import forexMp4 from '../media/mp4/forex.mp4';
|
import forexMp4 from '../media/mp4/forex.mp4';
|
||||||
import forexWebM from '../media/webm/forex.webm';
|
import forexWebM from '../media/webm/forex.webm';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import mscThesis from '../media/great-ai-andras-schmelczer.pdf';
|
import mscThesis from '../media/great-ai-andras-schmelczer.pdf';
|
||||||
import greatAiPoster from '../media/great-ai.png';
|
import greatAiPoster from '../media/great-ai.png';
|
||||||
|
|
@ -7,10 +7,9 @@ import { Open, PyPi, Thesis } from '../shared';
|
||||||
export const greatAi: TimelineElementParameters = {
|
export const greatAi: TimelineElementParameters = {
|
||||||
title: 'GreatAI — AI deployment framework',
|
title: 'GreatAI — AI deployment framework',
|
||||||
date: '2022',
|
date: '2022',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: greatAiPoster,
|
image: greatAiPoster,
|
||||||
alt: 'some example code using GreatAI',
|
alt: 'some example code using GreatAI',
|
||||||
container: true,
|
|
||||||
isEagerLoaded: true,
|
isEagerLoaded: true,
|
||||||
}),
|
}),
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Video } from '../../page/figure/video/video';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import { Video } from '../../page/video/video';
|
|
||||||
import ledPoster from '../media/led.jpg';
|
import ledPoster from '../media/led.jpg';
|
||||||
import ledMp4 from '../media/mp4/led.mp4';
|
import ledMp4 from '../media/mp4/led.mp4';
|
||||||
import ledWebM from '../media/webm/led.webm';
|
import ledWebM from '../media/webm/led.webm';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import myNotesPoster from '../media/my-notes.png';
|
import myNotesPoster from '../media/my-notes.png';
|
||||||
import { GitHub } from '../shared';
|
import { GitHub } from '../shared';
|
||||||
|
|
@ -6,10 +6,9 @@ import { GitHub } from '../shared';
|
||||||
export const myNotes: TimelineElementParameters = {
|
export const myNotes: TimelineElementParameters = {
|
||||||
title: 'My Notes — Android app',
|
title: 'My Notes — Android app',
|
||||||
date: '2019 November',
|
date: '2019 November',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: myNotesPoster,
|
image: myNotesPoster,
|
||||||
alt: 'two screenshots of the application',
|
alt: 'two screenshots of the application',
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description: 'A minimalist Android note organiser and editor powered by Markwon.',
|
description: 'A minimalist Android note organiser and editor powered by Markwon.',
|
||||||
more: [
|
more: [
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import nuclearEditorPoster from '../media/process-simulator-input.jpg';
|
import nuclearEditorPoster from '../media/process-simulator-input.jpg';
|
||||||
|
|
||||||
export const nuclearEditor: TimelineElementParameters = {
|
export const nuclearEditor: TimelineElementParameters = {
|
||||||
title: 'Graph editor — JavaFX',
|
title: 'Graph editor — JavaFX',
|
||||||
date: '2018 October - November',
|
date: '2018 October - November',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: nuclearEditorPoster,
|
image: nuclearEditorPoster,
|
||||||
alt: "a picture of the simulator's UI",
|
alt: "a picture of the simulator's UI",
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description:
|
description:
|
||||||
'An intuitive editor to create and edit input for the nuclear facility simulator (see above).',
|
'An intuitive editor to create and edit input for the nuclear facility simulator (see above).',
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import processSimulatorPoster from '../media/process-simulator.jpg';
|
import processSimulatorPoster from '../media/process-simulator.jpg';
|
||||||
|
|
||||||
export const nuclear: TimelineElementParameters = {
|
export const nuclear: TimelineElementParameters = {
|
||||||
title: 'Simulating the cooling system of a nuclear facility',
|
title: 'Simulating the cooling system of a nuclear facility',
|
||||||
date: '2018 October - November',
|
date: '2018 October - November',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: processSimulatorPoster,
|
image: processSimulatorPoster,
|
||||||
alt: 'a screenshot of the simulator',
|
alt: 'a screenshot of the simulator',
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description:
|
description:
|
||||||
'The temperatures and flow volumes are dynamically calculated by two graph models on a remote server while multiple "monitoring" clients update in real-time.',
|
'The temperatures and flow volumes are dynamically calculated by two graph models on a remote server while multiple "monitoring" clients update in real-time.',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import photosPoster from '../media/photos.jpg';
|
import photosPoster from '../media/photos.jpg';
|
||||||
import { Open } from '../shared';
|
import { Open } from '../shared';
|
||||||
|
|
@ -6,10 +6,9 @@ import { Open } from '../shared';
|
||||||
export const photos: TimelineElementParameters = {
|
export const photos: TimelineElementParameters = {
|
||||||
title: 'Photos',
|
title: 'Photos',
|
||||||
date: '2016 summer',
|
date: '2016 summer',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: photosPoster,
|
image: photosPoster,
|
||||||
alt: 'a picture of the website',
|
alt: 'a picture of the website',
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description: 'A simple webpage where you can view my photos.',
|
description: 'A simple webpage where you can view my photos.',
|
||||||
more: [
|
more: [
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Video } from '../../page/figure/video/video';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import { Video } from '../../page/video/video';
|
|
||||||
import platformMp4 from '../media/mp4/platform.mp4';
|
import platformMp4 from '../media/mp4/platform.mp4';
|
||||||
import platformPoster from '../media/platform.png';
|
import platformPoster from '../media/platform.png';
|
||||||
import platformWebM from '../media/webm/platform.webm';
|
import platformWebM from '../media/webm/platform.webm';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Preview } from '../../page/preview/preview';
|
import { Preview } from '../../page/figure/preview/preview';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import sdf2dPoster from '../media/sdf2d.png';
|
import sdf2dPoster from '../media/sdf2d.png';
|
||||||
import { NPM, Open, Youtube } from '../shared';
|
import { NPM, Open, Youtube } from '../shared';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Image } from '../../page/image-viewer/image/image.html';
|
import { BorderedImage } from '../../page/figure/bordered-image/bordered-image';
|
||||||
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
import { TimelineElementParameters } from '../../page/timeline-element/timeline-element-parameters';
|
||||||
import towersPoster from '../media/towers.png';
|
import towersPoster from '../media/towers.png';
|
||||||
import { GitHub, Open } from '../shared';
|
import { GitHub, Open } from '../shared';
|
||||||
|
|
@ -6,10 +6,9 @@ import { GitHub, Open } from '../shared';
|
||||||
export const towers: TimelineElementParameters = {
|
export const towers: TimelineElementParameters = {
|
||||||
title: 'Multi-device life tracking',
|
title: 'Multi-device life tracking',
|
||||||
date: '2019 August - September',
|
date: '2019 August - September',
|
||||||
figure: Image({
|
figure: new BorderedImage({
|
||||||
image: towersPoster,
|
image: towersPoster,
|
||||||
alt: 'a picture of the website',
|
alt: 'a picture of the website',
|
||||||
container: true,
|
|
||||||
}),
|
}),
|
||||||
description: 'An aesthetic representation of your previous and current goals/tasks.',
|
description: 'An aesthetic representation of your previous and current goals/tasks.',
|
||||||
more: [
|
more: [
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,6 @@ html[animations='off'] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
|
@ -74,6 +65,23 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noscript {
|
||||||
|
@include square(100%);
|
||||||
|
@include center-children();
|
||||||
|
@include sub-title-font();
|
||||||
|
}
|
||||||
|
|
||||||
|
.image,
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
stroke: var(--normal-text-color);
|
stroke: var(--normal-text-color);
|
||||||
}
|
}
|
||||||
|
|
@ -82,68 +90,8 @@ p {
|
||||||
@include main-font();
|
@include main-font();
|
||||||
}
|
}
|
||||||
|
|
||||||
noscript {
|
a {
|
||||||
@include square(100%);
|
text-decoration: none;
|
||||||
@include center-children();
|
|
||||||
@include sub-title-font();
|
|
||||||
}
|
|
||||||
|
|
||||||
.start-button {
|
|
||||||
@include image-button(var(--large-icon-size));
|
|
||||||
@include absolute-center;
|
|
||||||
@include square(calc(var(--large-icon-size) + var(--normal-margin) * 2));
|
|
||||||
|
|
||||||
// as a result of the firefox fix, it is required for iOS devices
|
|
||||||
transform: translate3d(-50%, -50%, 0.00001px);
|
|
||||||
|
|
||||||
&:hover svg {
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
border-radius: 10000px;
|
|
||||||
@include blurred-background;
|
|
||||||
transition: transform var(--transition-time), box-shadow var(--transition-time);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inverted svg {
|
|
||||||
fill: var(--accent-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure-container {
|
|
||||||
font-size: 0;
|
|
||||||
box-shadow: var(--inset-shadow);
|
|
||||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
|
||||||
pointer-events: none;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
* {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image,
|
|
||||||
video,
|
|
||||||
iframe {
|
|
||||||
z-index: -1;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video,
|
|
||||||
iframe {
|
|
||||||
// the picture of videos is not always visible on firefox mobile without this
|
|
||||||
transform: translate3d(0, 0, 0.00001px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image,
|
|
||||||
video,
|
|
||||||
iframe {
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
|
|
|
||||||
0
src/page/figure/bordered-image/bordered-image.scss
Normal file
0
src/page/figure/bordered-image/bordered-image.scss
Normal file
23
src/page/figure/bordered-image/bordered-image.ts
Normal file
23
src/page/figure/bordered-image/bordered-image.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ResponsiveImage } from '../../../types/responsive-image';
|
||||||
|
import { ImageViewer } from '../../image-viewer/image-viewer';
|
||||||
|
import { Image } from '../../image/image.html';
|
||||||
|
import { Figure } from '../figure';
|
||||||
|
import './bordered-image.scss';
|
||||||
|
|
||||||
|
export class BorderedImage extends Figure {
|
||||||
|
public constructor(
|
||||||
|
options: {
|
||||||
|
image: ResponsiveImage;
|
||||||
|
alt: string;
|
||||||
|
sizes?: string | null;
|
||||||
|
isEagerLoaded?: boolean;
|
||||||
|
},
|
||||||
|
public imageViewer?: ImageViewer
|
||||||
|
) {
|
||||||
|
super(Image(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async onClick() {
|
||||||
|
this.imageViewer?.showImage(this.query('img') as HTMLImageElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/page/figure/figure.html.ts
Normal file
21
src/page/figure/figure.html.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import play from '../../../static/icons/play-button.svg';
|
||||||
|
import { html } from '../../types/html';
|
||||||
|
import './figure.scss';
|
||||||
|
|
||||||
|
export const generate = ({
|
||||||
|
children,
|
||||||
|
hasButton,
|
||||||
|
invertButton,
|
||||||
|
}: {
|
||||||
|
children: html;
|
||||||
|
hasButton: boolean;
|
||||||
|
invertButton: boolean;
|
||||||
|
}): html => `
|
||||||
|
<div class="figure-container" tabindex=0 >
|
||||||
|
${children}
|
||||||
|
${
|
||||||
|
hasButton
|
||||||
|
? `<div class="start-button ${invertButton ? 'inverted' : ''}" >${play}</div>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>`;
|
||||||
29
src/page/figure/figure.scss
Normal file
29
src/page/figure/figure.scss
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
@use '../../style/mixins' as *;
|
||||||
|
|
||||||
|
.figure-container {
|
||||||
|
box-shadow: var(--inset-shadow);
|
||||||
|
transition: box-shadow var(--transition-time);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> .start-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: 1000px;
|
||||||
|
@include blurred-background;
|
||||||
|
transition: transform var(--transition-time), box-shadow var(--transition-time);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inverted > svg {
|
||||||
|
fill: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/page/figure/figure.ts
Normal file
21
src/page/figure/figure.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { html } from '../../types/html';
|
||||||
|
import { PageElement } from '../page-element';
|
||||||
|
import { generate } from './figure.html';
|
||||||
|
|
||||||
|
export abstract class Figure extends PageElement {
|
||||||
|
public constructor(
|
||||||
|
children: html,
|
||||||
|
{
|
||||||
|
hasButton = false,
|
||||||
|
invertButton = false,
|
||||||
|
}: {
|
||||||
|
hasButton?: boolean;
|
||||||
|
invertButton?: boolean;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
super(generate({ children, hasButton, invertButton }));
|
||||||
|
this.htmlRoot.addEventListener('click', this.onClick.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract onClick(): unknown;
|
||||||
|
}
|
||||||
21
src/page/figure/preview/preview.html.ts
Normal file
21
src/page/figure/preview/preview.html.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import loading from '../../../../static/icons/loading.svg';
|
||||||
|
import { html } from '../../../types/html';
|
||||||
|
import { ResponsiveImage } from '../../../types/responsive-image';
|
||||||
|
import { Image } from '../../image/image.html';
|
||||||
|
import './preview.scss';
|
||||||
|
|
||||||
|
export const generate = ({
|
||||||
|
alt,
|
||||||
|
poster,
|
||||||
|
}: {
|
||||||
|
alt: string;
|
||||||
|
poster: ResponsiveImage;
|
||||||
|
}): html =>
|
||||||
|
`${Image({
|
||||||
|
image: poster,
|
||||||
|
alt,
|
||||||
|
})}
|
||||||
|
<div class="overlay">
|
||||||
|
<div class="loading">${loading}</div>
|
||||||
|
<iframe title="${alt}" allowfullscreen loading="lazy"></iframe>
|
||||||
|
</div>`;
|
||||||
|
|
@ -1,42 +1,38 @@
|
||||||
@use '../../style/mixins' as *;
|
@use '../../../style/mixins' as *;
|
||||||
|
|
||||||
.preview {
|
.figure-container {
|
||||||
position: relative;
|
> .overlay {
|
||||||
|
|
||||||
.overlay {
|
|
||||||
@include square(100%);
|
@include square(100%);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
iframe {
|
> .loading {
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
@include square(var(--large-icon-size));
|
@include square(var(--large-icon-size));
|
||||||
@include absolute-center;
|
@include absolute-center;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
> iframe {
|
||||||
@include square(100%);
|
@include square(100%);
|
||||||
border: none;
|
border: none;
|
||||||
&:fullscreen {
|
position: absolute;
|
||||||
border-radius: 0;
|
left: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loaded {
|
&.loaded {
|
||||||
.figure-container,
|
> .start-button {
|
||||||
.start-button {
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
> .overlay {
|
||||||
visibility: visible;
|
pointer-events: all;
|
||||||
|
|
||||||
|
> .loading {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
28
src/page/figure/preview/preview.ts
Normal file
28
src/page/figure/preview/preview.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { ResponsiveImage } from '../../../types/responsive-image';
|
||||||
|
import { Figure } from '../figure';
|
||||||
|
import { generate } from './preview.html';
|
||||||
|
|
||||||
|
export class Preview extends Figure {
|
||||||
|
public constructor(poster: ResponsiveImage, private readonly url: string, alt: string) {
|
||||||
|
super(generate({ poster, alt }), {
|
||||||
|
hasButton: true,
|
||||||
|
});
|
||||||
|
this.url += '?portfolioView';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onClick() {
|
||||||
|
this.htmlRoot.classList.add('loaded');
|
||||||
|
(this.query('iframe') as HTMLIFrameElement).src = this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initialize() {
|
||||||
|
new IntersectionObserver((e) => {
|
||||||
|
if (!e[0].isIntersecting) {
|
||||||
|
this.htmlRoot.classList.remove('loaded');
|
||||||
|
(this.query('iframe') as HTMLIFrameElement).src = '';
|
||||||
|
}
|
||||||
|
}).observe(this.htmlRoot.parentElement!);
|
||||||
|
|
||||||
|
super.initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ResponsiveImage } from '../../types/responsive-image';
|
import { ResponsiveImage } from '../../../types/responsive-image';
|
||||||
import { url } from '../../types/url';
|
import { url } from '../../../types/url';
|
||||||
|
|
||||||
export interface VideoParameters {
|
export interface VideoParameters {
|
||||||
mp4: url;
|
mp4: url;
|
||||||
14
src/page/figure/video/video.html.ts
Normal file
14
src/page/figure/video/video.html.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { html } from '../../../types/html';
|
||||||
|
import { Image } from '../../image/image.html';
|
||||||
|
import { VideoParameters } from './video-parameters';
|
||||||
|
import './video.scss';
|
||||||
|
|
||||||
|
export const generate = ({ webm, mp4, poster, altText }: VideoParameters): html => `
|
||||||
|
${Image({
|
||||||
|
image: poster,
|
||||||
|
alt: altText,
|
||||||
|
})}
|
||||||
|
<video playsinline controls preload="none">
|
||||||
|
<source src="${webm}" type="video/webm"/>
|
||||||
|
<source src="${mp4}" type="video/mp4"/>
|
||||||
|
</video>`;
|
||||||
16
src/page/figure/video/video.scss
Normal file
16
src/page/figure/video/video.scss
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
@use '../../../style/mixins' as *;
|
||||||
|
|
||||||
|
.figure-container {
|
||||||
|
&.loaded > .start-button,
|
||||||
|
&:not(.loaded) > video {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
> video {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { PageElement } from '../page-element';
|
import { Figure } from '../figure';
|
||||||
import { VideoParameters } from './video-parameters';
|
import { VideoParameters } from './video-parameters';
|
||||||
import { generate } from './video.html';
|
import { generate } from './video.html';
|
||||||
|
|
||||||
export class Video extends PageElement {
|
export class Video extends Figure {
|
||||||
public constructor(options: VideoParameters) {
|
public constructor(options: VideoParameters) {
|
||||||
super(generate(options));
|
super(generate(options), {
|
||||||
|
hasButton: true,
|
||||||
this.query('.start-button').addEventListener('click', this.startVideo.bind(this));
|
invertButton: options.invertButton,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startVideo() {
|
protected async onClick() {
|
||||||
this.query('.start-button').style.visibility = 'hidden';
|
this.query('.start-button').style.visibility = 'hidden';
|
||||||
this.htmlRoot.classList.add('loaded');
|
this.htmlRoot.classList.add('loaded');
|
||||||
|
|
||||||
|
|
@ -4,15 +4,13 @@ import './header.scss';
|
||||||
export const generate = ({
|
export const generate = ({
|
||||||
name,
|
name,
|
||||||
about,
|
about,
|
||||||
photo,
|
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
about: Array<string>;
|
about: Array<string>;
|
||||||
photo: html;
|
|
||||||
}): html => `
|
}): html => `
|
||||||
<header id="about">
|
<header id="about">
|
||||||
<div class="photo-container">
|
<div class="profile-picture">
|
||||||
${photo}
|
<img/>
|
||||||
<div class="placeholder"></div>
|
<div class="placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$img-size: 125px;
|
$img-size: 125px;
|
||||||
> .photo-container > .image {
|
> .profile-picture {
|
||||||
@include square($img-size);
|
@include square($img-size);
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> h1 {
|
> h1 {
|
||||||
|
|
@ -30,11 +31,10 @@
|
||||||
auto;
|
auto;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
> .photo-container {
|
> .profile-picture {
|
||||||
position: relative;
|
> .figure-container {
|
||||||
|
|
||||||
> .image {
|
|
||||||
@include square($img-size);
|
@include square($img-size);
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(#{math.div(-$img-size, 3)} - var(--normal-margin));
|
left: calc(#{math.div(-$img-size, 3)} - var(--normal-margin));
|
||||||
top: calc(#{math.div(-$img-size, 3)} - var(--normal-margin));
|
top: calc(#{math.div(-$img-size, 3)} - var(--normal-margin));
|
||||||
|
|
@ -55,15 +55,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
> h1,
|
> h1,
|
||||||
> .photo-container > .placeholder {
|
> .profile-picture > .placeholder {
|
||||||
@include title-font();
|
@include title-font();
|
||||||
}
|
}
|
||||||
|
|
||||||
> .photo-container {
|
> .profile-picture {
|
||||||
> .image {
|
position: relative;
|
||||||
border-radius: 100%;
|
z-index: 1;
|
||||||
box-shadow: var(--shadow);
|
|
||||||
margin: auto;
|
.figure-container {
|
||||||
|
&,
|
||||||
|
> .image {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ResponsiveImage } from '../../types/responsive-image';
|
import { ResponsiveImage } from '../../types/responsive-image';
|
||||||
import { Image } from '../image-viewer/image/image.html';
|
import { BorderedImage } from '../figure/bordered-image/bordered-image';
|
||||||
|
import { ImageViewer } from '../image-viewer/image-viewer';
|
||||||
import { PageElement } from '../page-element';
|
import { PageElement } from '../page-element';
|
||||||
import { generate } from './header.html';
|
import { generate } from './header.html';
|
||||||
import { ThemeSwitcher } from './theme-switcher/theme-switcher';
|
import { ThemeSwitcher } from './theme-switcher/theme-switcher';
|
||||||
|
|
@ -10,22 +11,31 @@ export class Header extends PageElement {
|
||||||
image,
|
image,
|
||||||
imageAltText,
|
imageAltText,
|
||||||
about,
|
about,
|
||||||
|
imageViewer,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
image: ResponsiveImage;
|
image: ResponsiveImage;
|
||||||
imageAltText: string;
|
imageAltText: string;
|
||||||
about: Array<string>;
|
about: Array<string>;
|
||||||
|
imageViewer?: ImageViewer;
|
||||||
}) {
|
}) {
|
||||||
super(
|
super(
|
||||||
generate({
|
generate({
|
||||||
name,
|
name,
|
||||||
about,
|
about,
|
||||||
photo: Image({
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.attachElementByReplacing(
|
||||||
|
'img',
|
||||||
|
new BorderedImage(
|
||||||
|
{
|
||||||
image,
|
image,
|
||||||
alt: imageAltText,
|
alt: imageAltText,
|
||||||
sizes: '(max-width: 924px) 125px, 190px',
|
sizes: '(max-width: 924px) 125px, 190px',
|
||||||
}),
|
},
|
||||||
})
|
imageViewer
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this.attachElement(new ThemeSwitcher());
|
this.attachElement(new ThemeSwitcher());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import './image-viewer.scss';
|
||||||
|
|
||||||
export const generate = (): html => `
|
export const generate = (): html => `
|
||||||
<div id="image-viewer">
|
<div id="image-viewer">
|
||||||
<img height="0" width="0" image-viewer-ignore />
|
<img height="0" width="0" />
|
||||||
<button id="cancel">${cancel}</button>
|
<button id="cancel">${cancel}</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,9 @@
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@include square(auto);
|
@include square(auto);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
@include on-large-screen {
|
max-width: 80%;
|
||||||
max-width: 80%;
|
max-height: 80%;
|
||||||
max-height: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include on-small-screen {
|
@include on-small-screen {
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,6 @@ export class ImageViewer extends PageElement {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super(generate());
|
super(generate());
|
||||||
|
|
||||||
document.body.addEventListener('click', (event: MouseEvent) => {
|
|
||||||
const element = event.target as HTMLElement;
|
|
||||||
|
|
||||||
if (element.classList?.contains('image')) {
|
|
||||||
this.showImage(element.querySelector('img')!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element instanceof HTMLImageElement) {
|
|
||||||
this.showImage(element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.addEventListener('keydown', (event: KeyboardEvent) => {
|
document.body.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.hideImage();
|
this.hideImage();
|
||||||
|
|
@ -26,11 +14,7 @@ export class ImageViewer extends PageElement {
|
||||||
this.htmlRoot.addEventListener('click', this.hideImage.bind(this));
|
this.htmlRoot.addEventListener('click', this.hideImage.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private showImage(source: HTMLImageElement) {
|
public showImage(source: HTMLImageElement) {
|
||||||
if (source.attributes['image-viewer-ignore'] as boolean | undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const image = this.query('img') as HTMLImageElement;
|
const image = this.query('img') as HTMLImageElement;
|
||||||
image.src = '';
|
image.src = '';
|
||||||
image.src = source.src;
|
image.src = source.src;
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { html } from '../../../types/html';
|
|
||||||
import { ResponsiveImage } from '../../../types/responsive-image';
|
|
||||||
import './image.scss';
|
|
||||||
|
|
||||||
export const Image = ({
|
|
||||||
image,
|
|
||||||
alt,
|
|
||||||
container = false,
|
|
||||||
isIgnoredByImageViewer = false,
|
|
||||||
sizes = null,
|
|
||||||
isEagerLoaded = false,
|
|
||||||
}: {
|
|
||||||
image: ResponsiveImage;
|
|
||||||
alt: string;
|
|
||||||
container?: boolean;
|
|
||||||
isIgnoredByImageViewer?: boolean;
|
|
||||||
sizes?: string | null;
|
|
||||||
isEagerLoaded?: boolean;
|
|
||||||
}): html => `
|
|
||||||
${
|
|
||||||
container
|
|
||||||
? `<div class="figure-container" style="padding-top:${
|
|
||||||
(image.height / image.width) * 100
|
|
||||||
}%">`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
<div
|
|
||||||
class="image"
|
|
||||||
style="background-size: cover; background-image: url('${image.placeholder}')"
|
|
||||||
${isIgnoredByImageViewer ? '' : 'tabindex="0"'}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
${isIgnoredByImageViewer ? 'image-viewer-ignore' : ''}
|
|
||||||
${isEagerLoaded ? '' : 'loading="lazy"'}
|
|
||||||
srcset="${image.srcSet}"
|
|
||||||
${sizes ? `sizes="${sizes}"` : ''}
|
|
||||||
src="${image.src}"
|
|
||||||
width="${image.width}"
|
|
||||||
height="${image.height}"
|
|
||||||
alt="${alt}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
${container ? '</div>' : ''}
|
|
||||||
`;
|
|
||||||
31
src/page/image/image.html.ts
Normal file
31
src/page/image/image.html.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { html } from '../../types/html';
|
||||||
|
import { ResponsiveImage } from '../../types/responsive-image';
|
||||||
|
import './image.scss';
|
||||||
|
|
||||||
|
export const Image = ({
|
||||||
|
image,
|
||||||
|
alt,
|
||||||
|
sizes = null,
|
||||||
|
isEagerLoaded = false,
|
||||||
|
}: {
|
||||||
|
image: ResponsiveImage;
|
||||||
|
alt: string;
|
||||||
|
sizes?: string | null;
|
||||||
|
isEagerLoaded?: boolean;
|
||||||
|
}): html => `
|
||||||
|
<div
|
||||||
|
class="image"
|
||||||
|
style="background-size: cover; background-image: url('${
|
||||||
|
image.placeholder
|
||||||
|
}'); aspect-ratio: ${image.width / image.height}"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
${isEagerLoaded ? '' : 'loading="lazy"'}
|
||||||
|
srcset="${image.srcSet}"
|
||||||
|
${sizes ? `sizes="${sizes}"` : ''}
|
||||||
|
src="${image.src}"
|
||||||
|
width="${image.width}"
|
||||||
|
height="${image.height}"
|
||||||
|
alt="${alt}"
|
||||||
|
/>
|
||||||
|
</div>`;
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
.image {
|
.image {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|
||||||
&:not([image-viewer-ignore]) {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import loading from '../../../static/icons/loading.svg';
|
|
||||||
import play from '../../../static/icons/play-button.svg';
|
|
||||||
import { html } from '../../types/html';
|
|
||||||
import { ResponsiveImage } from '../../types/responsive-image';
|
|
||||||
import { Image } from '../image-viewer/image/image.html';
|
|
||||||
import './preview.scss';
|
|
||||||
|
|
||||||
export const generate = ({
|
|
||||||
alt,
|
|
||||||
poster,
|
|
||||||
}: {
|
|
||||||
alt: string;
|
|
||||||
poster: ResponsiveImage;
|
|
||||||
}): html => `
|
|
||||||
<div class="preview">
|
|
||||||
${Image({
|
|
||||||
image: poster,
|
|
||||||
alt,
|
|
||||||
container: true,
|
|
||||||
isIgnoredByImageViewer: true,
|
|
||||||
})}
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="loading">${loading}</div>
|
|
||||||
<iframe title="${alt}" allowfullscreen loading="lazy"></iframe>
|
|
||||||
<div class="start-button">${play}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { ResponsiveImage } from '../../types/responsive-image';
|
|
||||||
import { PageElement } from '../page-element';
|
|
||||||
import { generate } from './preview.html';
|
|
||||||
|
|
||||||
export class Preview extends PageElement {
|
|
||||||
public constructor(poster: ResponsiveImage, private readonly url: string, alt: string) {
|
|
||||||
super(generate({ poster, alt }));
|
|
||||||
this.url += '?portfolioView';
|
|
||||||
this.query('.start-button').addEventListener('click', this.loadContent.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize() {
|
|
||||||
new IntersectionObserver((e) => {
|
|
||||||
if (!e[0].isIntersecting) {
|
|
||||||
this.unloadContent();
|
|
||||||
}
|
|
||||||
}).observe(this.htmlRoot.parentElement!);
|
|
||||||
|
|
||||||
super.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadContent() {
|
|
||||||
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 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { html } from '../../types/html';
|
import { html } from '../../types/html';
|
||||||
import { Preview } from '../preview/preview';
|
import { Figure } from '../figure/figure';
|
||||||
import { Video } from '../video/video';
|
|
||||||
|
|
||||||
export interface TimelineElementParameters {
|
export interface TimelineElementParameters {
|
||||||
date: string;
|
date: string;
|
||||||
figure: html | Video | Preview;
|
figure: Figure;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
more?: Array<html>;
|
more?: Array<html>;
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@
|
||||||
background-color: var(--blurred-card-color);
|
background-color: var(--blurred-card-color);
|
||||||
transition: background-color var(--transition-time);
|
transition: background-color var(--transition-time);
|
||||||
|
|
||||||
|
> .figure-container {
|
||||||
|
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
> .lower {
|
> .lower {
|
||||||
> * {
|
> * {
|
||||||
padding: 0 var(--normal-margin);
|
padding: 0 var(--normal-margin);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { titleToFragment } from '../../helper/title-to-fragment';
|
import { titleToFragment } from '../../helper/title-to-fragment';
|
||||||
|
import { BorderedImage } from '../figure/bordered-image/bordered-image';
|
||||||
|
import { ImageViewer } from '../image-viewer/image-viewer';
|
||||||
import { PageElement } from '../page-element';
|
import { PageElement } from '../page-element';
|
||||||
import { TimelineElementParameters } from './timeline-element-parameters';
|
import { TimelineElementParameters } from './timeline-element-parameters';
|
||||||
import { generate } from './timeline-element.html';
|
import { generate } from './timeline-element.html';
|
||||||
|
|
@ -10,7 +12,8 @@ export class TimelineElement extends PageElement {
|
||||||
public constructor(
|
public constructor(
|
||||||
private timelineElement: TimelineElementParameters,
|
private timelineElement: TimelineElementParameters,
|
||||||
private readonly showMore: string,
|
private readonly showMore: string,
|
||||||
private readonly showLess: string
|
private readonly showLess: string,
|
||||||
|
imageViewer?: ImageViewer
|
||||||
) {
|
) {
|
||||||
super(generate(timelineElement, showMore));
|
super(generate(timelineElement, showMore));
|
||||||
|
|
||||||
|
|
@ -24,12 +27,11 @@ export class TimelineElement extends PageElement {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attachElementByReplacing(
|
if (timelineElement.figure instanceof BorderedImage) {
|
||||||
'.figure',
|
timelineElement.figure.imageViewer = imageViewer;
|
||||||
timelineElement.figure instanceof PageElement
|
}
|
||||||
? timelineElement.figure
|
|
||||||
: new PageElement(timelineElement.figure)
|
this.attachElementByReplacing('.figure', timelineElement.figure);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
protected initialize(): void {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import play from '../../../static/icons/play-button.svg';
|
|
||||||
import { html } from '../../types/html';
|
|
||||||
import { Image } from '../image-viewer/image/image.html';
|
|
||||||
import { VideoParameters } from './video-parameters';
|
|
||||||
import './video.scss';
|
|
||||||
|
|
||||||
export const generate = ({
|
|
||||||
webm,
|
|
||||||
mp4,
|
|
||||||
poster,
|
|
||||||
invertButton,
|
|
||||||
altText,
|
|
||||||
}: VideoParameters): html => `
|
|
||||||
<div class="figure-container video-container" style="padding-top:${
|
|
||||||
(poster.height / poster.width) * 100
|
|
||||||
}%">
|
|
||||||
${Image({
|
|
||||||
image: poster,
|
|
||||||
alt: altText,
|
|
||||||
isIgnoredByImageViewer: true,
|
|
||||||
})}
|
|
||||||
<video playsinline controls preload="none">
|
|
||||||
<source src="${webm}" type="video/webm"/>
|
|
||||||
<source src="${mp4}" type="video/mp4"/>
|
|
||||||
</video>
|
|
||||||
<div class="start-button ${invertButton ? 'inverted' : ''}" tabindex=0>${play}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
@use '../../style/mixins' as *;
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
&.loaded > .start-button,
|
|
||||||
&:not(.loaded) > video {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
--blurred-card-color: transparent;
|
--blurred-card-color: transparent;
|
||||||
--blur-radius: 16px;
|
--blur-radius: 16px;
|
||||||
--special-text-color: var(--accent-color);
|
--special-text-color: var(--accent-color);
|
||||||
|
--inset-shadow: inset 0 0 4px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include on-large-screen {
|
@include on-large-screen {
|
||||||
|
|
@ -23,7 +24,6 @@
|
||||||
--normal-margin: 45px;
|
--normal-margin: 45px;
|
||||||
--small-margin: 25px;
|
--small-margin: 25px;
|
||||||
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1), 0 0 1px rgba(0, 0, 0, 0.2);
|
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.1), 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
--inset-shadow: inset 0 -9px 7px -7px rgb(0, 0, 0, 0.15);
|
|
||||||
--icon-size: 45px;
|
--icon-size: 45px;
|
||||||
--large-icon-size: 60px;
|
--large-icon-size: 60px;
|
||||||
--body-width: min(80%, 900px);
|
--body-width: min(80%, 900px);
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
--normal-margin: 30px;
|
--normal-margin: 30px;
|
||||||
--small-margin: 15px;
|
--small-margin: 15px;
|
||||||
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.075), 0 0 1px rgba(0, 0, 0, 0.125);
|
--shadow: 0 0 10px 2px rgba(0, 0, 0, 0.075), 0 0 1px rgba(0, 0, 0, 0.125);
|
||||||
--inset-shadow: inset 0 -9px 7px -7px rgb(0, 0, 0, 0.15);
|
|
||||||
--icon-size: 35px;
|
--icon-size: 35px;
|
||||||
--large-icon-size: 55px;
|
--large-icon-size: 55px;
|
||||||
--body-width: 90%;
|
--body-width: 90%;
|
||||||
|
|
@ -51,5 +50,5 @@
|
||||||
--blurred-card-color: #212f4a77;
|
--blurred-card-color: #212f4a77;
|
||||||
--blur-radius: 30px;
|
--blur-radius: 30px;
|
||||||
--special-text-color: #ffffff;
|
--special-text-color: #ffffff;
|
||||||
--inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.25), inset 0 0 1px rgba(0, 0, 0, 0.4);
|
--inset-shadow: inset 0 0 10px 2px rgba(0, 0, 0, 0.3), inset 0 0 4px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<svg version="1.1" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
<svg version="1.1" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
<path d="M256,0C114.833,0,0,114.844,0,256s114.833,256,256,256s256-114.844,256-256S397.167,0,256,0z M256,490.667
|
<path d="M256,0C114.833,0,0,114.844,0,256s114.833,256,256,256s256-114.844,256-256S397.167,0,256,0z M256,490.667
|
||||||
C126.604,490.667,21.333,385.396,21.333,256S126.604,21.333,256,21.333S490.667,126.604,490.667,256S385.396,490.667,256,490.667
|
C126.604,490.667,21.333,385.396,21.333,256S126.604,21.333,256,21.333S490.667,126.604,490.667,256S385.396,490.667,256,490.667
|
||||||
z" />
|
z" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 814 B |
Loading…
Add table
Add a link
Reference in a new issue