diff --git a/.idea/dictionaries/Schme.xml b/.idea/dictionaries/Schme.xml index 03e5b5e..491e446 100644 --- a/.idea/dictionaries/Schme.xml +++ b/.idea/dictionaries/Schme.xml @@ -1,11 +1,20 @@ + contenthash ffffff + gifsicle + imagemin + jpegtran lato + mozjpeg + noquotes opacify + optipng + pngquant raleway transparentize + webp \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index af9b85e..221eb63 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,40 +2,29 @@ - - + - - - + + + + + + - - - - - - - - - - - - - - - + + diff --git a/package.json b/package.json index d9a449c..9c46bf3 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,13 @@ "type": "git", "url": "git+https://github.com/schmelczerandras/timeline.git" }, - "keywords": [], - "author": "", + "keywords": [ + "CV", + "portfolio", + "resume", + "resumé" + ], + "author": "András Schmelczer", "license": "ISC", "bugs": { "url": "https://github.com/schmelczerandras/timeline/issues" @@ -24,12 +29,16 @@ "css-loader": "^3.4.0", "file-loader": "^5.0.2", "html-webpack-plugin": "^3.2.0", + "image-webpack-loader": "^6.0.0", "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.13.0", + "optimize-css-assets-webpack-plugin": "^5.0.3", "prettier": "^1.19.1", "resolve-url-loader": "^3.1.1", "sass-loader": "^8.0.0", "style-loader": "^1.0.2", + "svg-url-loader": "^3.0.3", + "terser-webpack-plugin": "^2.3.1", "ts-loader": "^6.2.1", "typescript": "^3.7.3", "webpack": "^4.41.4", diff --git a/src/index.html b/src/index.html index e645d1f..9eed1b7 100644 --- a/src/index.html +++ b/src/index.html @@ -10,12 +10,17 @@ + - + - Portfolio + Portfolio - András Schmelczer -
+
+ +
diff --git a/src/page/about/about.scss b/src/page/about/about.scss index c3623f0..b992a44 100644 --- a/src/page/about/about.scss +++ b/src/page/about/about.scss @@ -4,15 +4,7 @@ #about { @include important-card(); - $img-size: 190px; - - position: relative; - width: $body-width; - margin-top: calc(#{$normal-margin} + #{$img-size} * 1 / 3); - - h1 { - text-align: left; - } + $img-size: 125px; h1, img, @@ -21,29 +13,49 @@ @include title-font(); } - .placeholder { - @include square(calc(#{$img-size} * 2 / 3 - #{$normal-margin})); - box-sizing: content-box; - float: left; - margin: 0 0.75ex 0.75ex 0; - } - img { @include square($img-size); - position: absolute; - left: 0; - top: 0; - transform: translateY(-$img-size * 1/3) translateX(-$img-size * 1/3); border-radius: 100%; cursor: pointer; } - @media (max-width: $breakpoint-width) { - } - p { @include main-font(); text-align: justify; margin-top: $small-margin; } + + @media (max-width: $breakpoint-width) { + h1 { + margin-top: $small-margin; + } + } + + @media (min-width: $breakpoint-width) { + $img-size: 190px; + + width: $body-width; + margin: calc(#{$normal-margin} + #{$img-size} * 1 / 3) auto 0 auto; + position: relative; + border-radius: $border-radius; + + img { + @include square($img-size); + position: absolute; + left: 0; + top: 0; + transform: translateY(-$img-size * 1/3) translateX(-$img-size * 1/3); + } + + .placeholder { + @include square(calc(#{$img-size} * 2 / 3 - #{$normal-margin})); + box-sizing: content-box; + float: left; + margin: 0 0.75ex 0.75ex 0; + } + + h1 { + text-align: left; + } + } } diff --git a/src/page/background/background.scss b/src/page/background/background.scss index d5d7073..895d584 100644 --- a/src/page/background/background.scss +++ b/src/page/background/background.scss @@ -9,16 +9,16 @@ transform-style: preserve-3d; overflow: hidden; - transition: height $slow-transition-time, width $slow-transition-time; + transition: height $long-transition-time, width $long-transition-time; div { border-radius: 10000px; position: absolute; left: 0; top: 0; - width: 160px; + width: 130px; - transition: transform $slow-transition-time, opacity $slow-transition-time; + transition: transform $long-transition-time, opacity $long-transition-time; animation: fade-in 1s linear; @keyframes fade-in { diff --git a/src/page/background/background.ts b/src/page/background/background.ts index 5bc1461..66c5767 100644 --- a/src/page/background/background.ts +++ b/src/page/background/background.ts @@ -11,12 +11,12 @@ import { generate } from "./background.html"; export class PageBackground extends PageElement { private blobs: Array = []; - private blobSpacing = 300; + private blobSpacing = 350; public constructor(private start: PageElement, private end: PageElement) { super(); this.setElement(createElement(generate())); - Blob.initialize(20, 40, 5); + Blob.initialize(10, 30, 5); } protected handleEvent(event: PageEvent, parent: PageElement) { @@ -24,7 +24,7 @@ export class PageBackground extends PageElement { this.bindListeners(parent); this.resize(parent); } else if (event.type === PageEventType.onBodyDimensionsChanged) { - this.resize(parent, event.data.deltaHeight); + this.resize(parent, event.data?.deltaHeight); } } @@ -44,10 +44,9 @@ export class PageBackground extends PageElement { this.getElement().style.width = `${width}px`; this.getElement().style.height = `${height}px`; - const requiredBlobCount = - width > 900 ? Math.round((width * height) / this.blobSpacing ** 2) : 0; - - console.log(requiredBlobCount); + const requiredBlobCount = Math.round( + (width * height) / this.blobSpacing ** 2 + ); while (requiredBlobCount > this.blobs.length) { const blob = new Blob(); @@ -55,7 +54,7 @@ export class PageBackground extends PageElement { this.blobs.push(blob); } - const random = randomFactory(2322); + const random = randomFactory(2662); this.blobs.forEach((b, i) => { if (i >= requiredBlobCount) { diff --git a/src/page/background/blob.ts b/src/page/background/blob.ts index f24e38b..be1caee 100644 --- a/src/page/background/blob.ts +++ b/src/page/background/blob.ts @@ -7,7 +7,7 @@ import { } from "../../framework/helper"; export class Blob { - private static readonly creatorRandom = randomFactory(42); + private static readonly creatorRandom = randomFactory(44); private static readonly colors = ["#fff9e0", "#ffd6d6"]; private static zMin: number; private static zMax: number; diff --git a/src/page/content/content.ts b/src/page/content/content.ts index 779ec11..9aa9d08 100644 --- a/src/page/content/content.ts +++ b/src/page/content/content.ts @@ -18,7 +18,7 @@ export class PageContent extends PageElement { .map(element => { if (PageContent.isTyped(element)) { if (element.type === "a") { - return ` ${element.text} `; + return ` ${element.text} `; } if (element.type === "video") { return ``; diff --git a/src/page/footer/footer.html.ts b/src/page/footer/footer.html.ts index 9bf3a34..c06bf50 100644 --- a/src/page/footer/footer.html.ts +++ b/src/page/footer/footer.html.ts @@ -19,11 +19,11 @@ export const generate = ({

${title}

diff --git a/src/page/footer/footer.scss b/src/page/footer/footer.scss index 8982263..d29342b 100644 --- a/src/page/footer/footer.scss +++ b/src/page/footer/footer.scss @@ -4,10 +4,8 @@ footer#page-footer { text-align: center; - margin: $normal-margin auto 0 auto; - padding: $normal-margin $normal-margin $line-height $normal-margin; + margin: $large-margin auto 0 auto; width: 100%; - // backdrop-filter: blur($blur-radius); h2 { @include title-font(); @@ -16,7 +14,7 @@ footer#page-footer { ul { list-style: none; display: inline-block; - margin-top: calc(#{$small-margin} / 2 + #{$normal-margin} / 2); + margin-top: $normal-margin; text-align: left; li { @@ -40,7 +38,7 @@ footer#page-footer { aside.other { @include center-children(); - margin: calc(2 * #{$normal-margin}) auto 0 auto; + margin: $large-margin auto $line-height auto; width: $body-width; h6 { diff --git a/src/page/image-viewer/image-viewer.ts b/src/page/image-viewer/image-viewer.ts index 82e5e8a..e48298d 100644 --- a/src/page/image-viewer/image-viewer.ts +++ b/src/page/image-viewer/image-viewer.ts @@ -24,7 +24,9 @@ export class PageImageViewer extends PageElement { ); images .filter( - (img: HTMLImageElement) => img.parentElement !== this.getElement() + (img: HTMLImageElement) => + img.parentElement !== this.getElement() && + !img.classList.contains("no-open") ) .forEach( (img: HTMLImageElement) => (img.onclick = this.handleClick.bind(this)) diff --git a/src/page/index.ts b/src/page/index.ts index 1653af5..262e4cf 100644 --- a/src/page/index.ts +++ b/src/page/index.ts @@ -7,7 +7,6 @@ import { PageImageViewer } from "./image-viewer/image-viewer"; import { Page } from "../framework/page"; export const create = ({ config, header, timeline, footer }: Portfolio) => { - document.title = header.name; const pageHeader = new PageHeader(header, config.aPictureOf); const pageFooter = new PageFooter(footer); diff --git a/src/page/timeline/timeline-element/timeline-element.html.ts b/src/page/timeline/timeline-element/timeline-element.html.ts index 865729b..f25f85f 100644 --- a/src/page/timeline/timeline-element/timeline-element.html.ts +++ b/src/page/timeline/timeline-element/timeline-element.html.ts @@ -9,11 +9,10 @@ export const generate = ( ): html => `
-

${date}

+

${date}

${title}

-

${date}

${picture}
@@ -21,10 +20,10 @@ export const generate = ( ${ more ? ` -
+
` : "" diff --git a/src/page/timeline/timeline-element/timeline-element.scss b/src/page/timeline/timeline-element/timeline-element.scss index 86057ab..7cfe3ec 100644 --- a/src/page/timeline/timeline-element/timeline-element.scss +++ b/src/page/timeline/timeline-element/timeline-element.scss @@ -4,46 +4,63 @@ .timeline-element { display: flex; - .date-narrow-screen, - .date-wide-screen { - @include insignificant-font(); - } - - .date-wide-screen { - color: $accent-color; - } - .line { - @media (max-width: $breakpoint-width) { - display: none; - } - position: relative; - margin: 0 $small-margin 0 $icon-size / 2; border-left: $line-width solid $accent-color; &:before { content: ""; @include square($icon-size); position: absolute; - top: 33%; left: calc(-0.5 * #{$icon-size} - (1.5 * #{$line-width})); border: $line-width solid $accent-color; border-radius: 100%; background: $background; } - .date-wide-screen { - position: relative; - top: calc(33% + #{$icon-size} + 2ch); - transform: rotate(30deg); - margin: 0 $normal-margin 0 calc(#{$line-width} + 1ex); - width: 100px; + .date { + @include insignificant-font(); } } - &:not(:first-of-type) .card { - margin-top: $normal-margin; + @media (min-width: $breakpoint-width) { + &:not(:first-of-type) .card { + margin-top: $large-margin; + } + + .line { + &:before { + top: calc(33% - #{$icon-size} / 2); + } + + .date { + position: relative; + top: calc(33% + #{$icon-size} / 2 + 1ch); + transform: rotate(30deg); + margin: 0 $normal-margin 0 calc(#{$line-width} + 1ex); + width: 100px; + } + } + } + + @media (max-width: $breakpoint-width) { + flex-direction: column; + align-items: center; + + &:before { + top: calc(50% - #{$icon-size} / 2); + } + + .line { + @include center-children(); + height: 150px; + width: 50%; + + .date { + margin-left: calc(#{$icon-size} / 2 + #{$small-margin}); + width: 200px; + } + } } .card { @@ -58,18 +75,9 @@ @include sub-title-font(); } - .date-narrow-screen { - @media (min-width: $breakpoint-width) { - display: none; - } - - margin: $small-margin 0 0 0; - color: $light-text-color; - } - .image-container { font-size: 0; - box-shadow: inset $shadow; + box-shadow: inset $shadow1, inset $shadow2; pointer-events: none; img { pointer-events: all; @@ -83,11 +91,11 @@ font-style: italic; } - #more { + .more { overflow: hidden; height: 0; margin-top: 0; - transition: height $slow-transition-time; + transition: height $long-transition-time; } .buttons { @@ -95,14 +103,14 @@ margin-top: $small-margin; * { - transition: opacity $slow-transition-time; + transition: opacity $long-transition-time; } - #show-more { + .show-more { opacity: 1; } - #show-less { + .show-less { opacity: 0; visibility: hidden; position: absolute; diff --git a/src/page/timeline/timeline-element/timeline-element.ts b/src/page/timeline/timeline-element/timeline-element.ts index 5d45998..e4fbe06 100644 --- a/src/page/timeline/timeline-element/timeline-element.ts +++ b/src/page/timeline/timeline-element/timeline-element.ts @@ -20,7 +20,7 @@ export class PageTimelineElement extends PageElement { const content = new PageContent(timelineElement.more); super([content]); this.isOpen = false; - this.more = root.querySelector("#more"); + this.more = root.querySelector(".more"); this.more.appendChild(content.getElement()); window.addEventListener("resize", this.handleResize.bind(this)); root @@ -31,8 +31,8 @@ export class PageTimelineElement extends PageElement { } private toggleOpen() { - const showMore = this.query("#show-more") as HTMLElement; - const showLess = this.query("#show-less") as HTMLElement; + 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); @@ -51,6 +51,14 @@ export class PageTimelineElement extends PageElement { type: PageEventType.onBodyDimensionsChanged, data: { deltaHeight } }); + + setTimeout( + () => + this.eventBroadcaster?.broadcastEvent({ + type: PageEventType.onBodyDimensionsChanged + }), + 350 + ); } private static hide(element: HTMLElement) { diff --git a/src/page/timeline/timeline.scss b/src/page/timeline/timeline.scss index e57c375..0cf23da 100644 --- a/src/page/timeline/timeline.scss +++ b/src/page/timeline/timeline.scss @@ -1,5 +1,10 @@ @import "../../style/vars"; -main#timeline { - margin-top: $normal-margin; +#timeline { + width: $body-width; + margin: $large-margin auto 0 auto; + + @media (max-width: $breakpoint-width) { + margin: auto; + } } diff --git a/src/style/a.scss b/src/style/a.scss index c5391df..3d3bc7a 100644 --- a/src/style/a.scss +++ b/src/style/a.scss @@ -12,7 +12,7 @@ a { $border-shift: 10px; - transition: transform $slow-transition-time; + transition: transform $long-transition-time; $dot-size: 4px; &:before { @@ -38,7 +38,7 @@ a { width: calc(100% + #{$border-shift}); z-index: 0; border-bottom: $dot-size dotted $accent-color; - transition: transform $slow-transition-time; + transition: transform $long-transition-time; } &:hover { diff --git a/src/style/fonts.scss b/src/style/fonts.scss index ab7966a..f161a10 100644 --- a/src/style/fonts.scss +++ b/src/style/fonts.scss @@ -1,3 +1,5 @@ +// https://google-webfonts-helper.herokuapp.com/fonts/montserrat?subsets=latin + /* lato-regular - latin */ @font-face { font-family: "Lato"; diff --git a/src/style/mixins.scss b/src/style/mixins.scss index a59e5e5..07b558d 100644 --- a/src/style/mixins.scss +++ b/src/style/mixins.scss @@ -8,30 +8,27 @@ %card { text-align: center; - border-radius: $border-radius; padding: $normal-margin; - box-shadow: $shadow; + box-shadow: $shadow1, $shadow2; z-index: 1; - @media (max-width: $breakpoint-width) { - & { - transition: box-shadow $fast-transition-time; + @media (min-width: $breakpoint-width) { + transition: box-shadow $long-transition-time; - &:hover { - box-shadow: 0 0 3px rgba(0, 0, 0, 0.05); - } + &:hover { + box-shadow: $shadow3, $shadow2; } } } @mixin card() { @extend %card; + border-radius: $border-radius; background-color: $card-color; } @mixin important-card() { @extend %card; - background-color: $accent-color; * { @@ -53,6 +50,11 @@ font: 400 3.5rem "Montserrat", serif; font-style: normal; line-height: 1; + + @media (max-width: $breakpoint-width) { + font-size: 3rem; + line-height: 1.1; + } } @mixin sub-title-font() { diff --git a/src/style/vars.scss b/src/style/vars.scss index 7edbd9f..248b10f 100644 --- a/src/style/vars.scss +++ b/src/style/vars.scss @@ -1,8 +1,6 @@ @import "fonts"; -$background-start: white; -$background-end: white; + $background: white; -$background-gradient: linear-gradient(90deg, #fff9e0 0, #ffd6d6 100%); $normal-text-color: #31343f; $light-text-color: #7a7d8e; @@ -12,32 +10,40 @@ $card-color: #ffffff; $accent-color: #aa4465; $scrollbar-color: #ffd6d6; -$fast-transition-time: 220ms; -$slow-transition-time: 350ms; +$short-transition-time: 220ms; +$long-transition-time: 350ms; $line-width: 3px; -$border-radius: 25px; -$breakpoint-width: 900px; +$border-radius: 15px; +$breakpoint-width: 925px; +$large-margin: var(--large-margin); $normal-margin: var(--normal-margin); $small-margin: var(--small-margin); $line-height: 18px; -$shadow: 0 0 5px rgba(0, 0, 0, 0.125); +$shadow1: var(--shadow1); +$shadow2: var(--shadow2); +$shadow3: 0 0 15px 4px rgba(0, 0, 0, 0.1); -$blur-radius: 9px; $icon-size: var(--icon-size); $body-width: var(--body-width); :root { + --large-margin: 70px; --normal-margin: 45px; --small-margin: 25px; --icon-size: 35px; --body-width: 765px; + --shadow1: 0 0 10px 2px rgba(0, 0, 0, 0.075); + --shadow2: 0 0 1px rgba(0, 0, 0, 0.2); @media (max-width: $breakpoint-width) { - --normal-margin: 35px; + --large-margin: 60px; + --normal-margin: 30px; --small-margin: 15px; - --icon-size: 20px; - --body-width: 85%; + --icon-size: 25px; + --body-width: 90%; + --shadow1: 0 0 10px 2px rgba(0, 0, 0, 0.05); + --shadow2: 0 0 1px rgba(0, 0, 0, 0.125); } } diff --git a/src/styles.scss b/src/styles.scss index 109a1ac..2c1e9e4 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -20,17 +20,20 @@ } html { - background-color: white; + background-color: $background; height: 100%; } body { @include main-font(); - margin: auto; height: 100%; - width: 100%; & > main { + noscript { + @include square(100%); + @include center-children(); + } + height: 100%; overflow-y: auto; overflow-x: hidden; @@ -48,11 +51,6 @@ body { border-radius: $border-radius; } } - - & > * { - width: $body-width; - margin: auto; - } } } @@ -62,3 +60,9 @@ video { height: auto; object-fit: contain; } + +@media (max-width: $breakpoint-width) { + html { + font-size: 0.8em; + } +} diff --git a/webpack.config.js b/webpack.config.js index bb72d6e..0f54730 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,12 @@ const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const TerserJSPlugin = require("terser-webpack-plugin"); +const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const isProduction = process.env.NODE_ENV === "production"; + module.exports = { watchOptions: { ignored: /node_modules/ @@ -10,6 +14,9 @@ module.exports = { devServer: { host: "0.0.0.0" }, + optimization: { + minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})] + }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ @@ -17,7 +24,10 @@ module.exports = { xhtml: true, template: "./src/index.html" }), - new MiniCssExtractPlugin() + new MiniCssExtractPlugin({ + filename: "[name].[contenthash].css", + chunkFilename: "[id].[contenthash].css" + }) ], entry: { index: "./src/index.ts" @@ -25,12 +35,46 @@ module.exports = { module: { rules: [ { - test: /\.(png|svg|jpe?g|gif|mp4)$/i, - use: { - loader: "file-loader", - query: { - outputPath: "static/" + test: /\.(png|jpe?g|gif|mp4)$/i, + use: [ + { + loader: "file-loader", + query: { + outputPath: "static/" + } + }, + { + loader: "image-webpack-loader", + options: { + disable: !isProduction, + mozjpeg: { + progressive: true, + quality: 65 + }, + optipng: { + enabled: true + }, + pngquant: { + quality: [0.65, 0.9], + speed: 4 + }, + gifsicle: { + interlaced: false + }, + // the webp option will enable WEBP + webp: { + quality: 75 + } + } } + ] + }, + { + test: /\.svg$/, + loader: "svg-url-loader", + options: { + limit: 10 * 1024, + noquotes: true } }, { @@ -98,7 +142,7 @@ module.exports = { extensions: [".ts", ".js"] }, output: { - filename: "[name].bundle.js", + filename: "[name].[contenthash].js", path: path.resolve(__dirname, "dist") } };