Add dark mode

This commit is contained in:
Schmelczer András 2020-01-07 21:30:23 +01:00
parent 48a55a4a97
commit 073f087e52
40 changed files with 864 additions and 531 deletions

View file

@ -1,64 +1,75 @@
@import '../../style/mixins';
@import '../../style/vars';
#about {
@include important-card();
@include responsive() using ($vars) {
#about {
@include card-base($vars);
$img-size: 125px;
background-color: map_get($vars, $important-card-color);
h1,
img,
.placeholder,
& {
@include title-font();
}
img {
@include square($img-size);
border-radius: 100%;
}
p {
@include main-font();
text-align: justify;
margin-top: $small-margin;
}
h1 {
hyphens: none;
}
@media (max-width: $breakpoint-width) {
h1 {
margin-top: $small-margin;
* {
color: map_get($vars, $important-card-text-color);
}
}
@media (min-width: $breakpoint-width) {
$img-size: 190px;
$img-size: 125px;
width: $body-width;
margin: calc(#{$normal-margin} + #{$img-size} * 1 / 3) auto 0 auto;
position: relative;
border-radius: $border-radius;
h1,
img,
.placeholder,
& {
@include title-font();
}
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%;
}
.placeholder {
@include square(calc(#{$img-size} * 2 / 3 - #{$normal-margin}));
box-sizing: content-box;
float: left;
margin: 0 0.75ex 0.75ex 0;
p {
@include main-font();
text-align: justify;
margin-top: map_get($vars, $small-margin);
}
h1 {
text-align: left;
hyphens: none;
}
@include on-small-screen {
h1 {
margin-top: map_get($vars, $small-margin);
}
}
@include on-large-screen {
$img-size: 190px;
width: map_get($vars, $body-width);
margin: calc(#{map_get($vars, $normal-margin)} + #{$img-size} * 1 / 3)
auto 0 auto;
position: relative;
border-radius: map_get($vars, $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 - #{map_get($vars, $normal-margin)})
);
box-sizing: content-box;
float: left;
margin: 0 0.75ex 0.75ex 0;
}
h1 {
text-align: left;
}
}
}
}

View file

@ -5,9 +5,13 @@ import { PageElement } from '../../framework/page-element';
import { generate } from './about.html';
import { createElement } from '../../framework/helper/create-element';
import { ContainerPage } from '../../framework/container-page';
import { PageThemeSwitcher } from '../theme-switcher/theme-switcher';
export class PageHeader extends ContainerPage {
public constructor(header: Header) {
super(createElement(generate(header)), [new PageContent(header.about)]);
super(createElement(generate(header)), [
new PageContent(header.about),
new PageThemeSwitcher(),
]);
}
}

View file

@ -2,7 +2,5 @@ import { html } from '../../model/misc';
import './background.scss';
export const generate = (): html => `
<section id="background-container">
<section id="background"></section>
</section>
<div class="background-element"></div>
`;

View file

@ -1,43 +1,39 @@
@import '../../style/vars';
@import '../../style/mixins';
#background-container {
position: fixed;
left: 0;
top: 0;
height: 100vh;
width: 100%;
@include responsive() using ($vars) {
div.background-element {
position: -webkit-sticky;
position: absolute;
left: 0;
top: 0;
border-radius: 100px;
width: 140px;
z-index: -1;
-webkit-overflow-scrolling: touch;
perspective: 5px;
perspective-origin: center center;
overflow: hidden;
overflow: visible !important; // IE11 fix for disappearing elements
#background {
overflow: hidden;
will-change: width, height;
transition: height $long-transition-time, width $long-transition-time;
transform-style: flat;
@media (prefers-reduced-motion: reduce) {
& {
display: none;
}
}
div {
position: -webkit-sticky;
position: absolute;
left: 0;
top: 0;
border-radius: 100px;
width: 140px;
@media print {
& {
display: none;
}
}
transition: transform $long-transition-time, opacity $long-transition-time;
will-change: transform, opacity;
animation: fade-in 1s linear;
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
transition: transform map_get($vars, $long-transition-time),
opacity map_get($vars, $long-transition-time);
will-change: transform, opacity;
animation: fade-in 1s linear;
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}

View file

@ -1,8 +1,6 @@
import { PageElement } from '../../framework/page-element';
import { PageEvent, PageEventType } from '../../framework/page-event';
import { createElement } from '../../framework/helper/create-element';
import { Blob } from './blob';
import { generate } from './background.html';
import { Random } from '../../framework/helper/random';
import { getHeight } from '../../framework/helper/get-height';
import { sum } from '../../framework/helper/sum';
@ -12,7 +10,7 @@ export class PageBackground extends PageElement {
private readonly blobSpacing = 350;
public constructor(private start: PageElement, private end: PageElement) {
super(createElement(generate()));
super();
Blob.initialize(10, 30, 5);
}
@ -21,37 +19,33 @@ export class PageBackground extends PageElement {
this.bindListeners(parent);
} else if (event.type === PageEventType.onBodyDimensionsChanged) {
this.resize(parent, event.data?.deltaHeight);
} else if (event.type === PageEventType.pageThemeChanged) {
Blob.changeTheme(event.data);
this.blobs.forEach(b => b.decideColor());
}
}
private bindListeners(parent: PageElement) {
window.addEventListener('resize', () => this.resize(parent));
window.addEventListener('load', () => this.resize(parent));
parent.element.addEventListener(
'scroll',
() => (this.element.scrollTop = parent.element.scrollTop)
);
}
private resize(parent: PageElement, heightChange?: number) {
const siblings: Array<HTMLElement> = this.getSiblings(parent);
const siblings: Array<HTMLElement> = PageBackground.getSiblings(parent);
const width = parent.element.clientWidth;
const width = document.body.clientWidth;
let height = sum(siblings.map(getHeight));
if (heightChange) {
height += heightChange;
}
this.query('#background').style.width = `${width}px`;
this.query('#background').style.height = `${height}px`;
const requiredBlobCount = Math.round(
(width * height) / this.blobSpacing ** 2
);
while (requiredBlobCount > this.blobs.length) {
const blob = new Blob();
this.query('#background').appendChild(blob.htmlElement);
parent.element.appendChild(blob.htmlElement);
this.blobs.push(blob);
}
@ -74,9 +68,9 @@ export class PageBackground extends PageElement {
});
}
private getSiblings(parent: PageElement): Array<HTMLElement> {
private static getSiblings(parent: PageElement): Array<HTMLElement> {
return Array.prototype.slice
.call(parent.element.children)
.filter(e => e !== this.element);
.filter((e: HTMLElement) => !e.classList.contains('background-element'));
}
}

View file

@ -1,10 +1,14 @@
import { mixColors } from '../../framework/helper/mix-colors';
import { createElement } from '../../framework/helper/create-element';
import { Random } from '../../framework/helper/random';
import { generate } from './background.html';
export class Blob {
private static readonly creatorRandom = new Random(44);
private static readonly colors = ['#fff9e0', '#ffd6d6'];
private static colorPickerRandom = new Random(132);
private static readonly lightColors = ['#fff9e0', '#ffd6d6'];
private static readonly darkColors = ['#2C477A'];
private static isDarkThemed = false;
private static zMin: number;
private static zMax: number;
private static perspective: number;
@ -14,25 +18,36 @@ export class Blob {
Blob.perspective = perspective;
}
public static changeTheme(isDarkThemed: boolean) {
Blob.colorPickerRandom = new Random(132);
Blob.isDarkThemed = isDarkThemed;
}
private readonly z = Blob.creatorRandom.randomInInterval(
Blob.zMin,
Blob.zMax
);
private readonly element: HTMLElement = createElement('<div></div>');
private readonly element: HTMLElement = createElement(generate());
constructor() {
this.element.style.backgroundColor = mixColors(
'#ffffff',
Blob.creatorRandom.choose(Blob.colors),
(this.z - Blob.zMin) / (Blob.zMax - Blob.zMin)
);
this.element.style.zIndex = (-this.z).toString();
this.decideColor();
this.element.style.zIndex = Math.round(-this.z).toString();
this.element.style.height = `${Blob.creatorRandom.randomInInterval(
160,
740
)}px`;
}
public decideColor() {
this.element.style.backgroundColor = mixColors(
Blob.isDarkThemed ? '#242638 ' : '#ffffff',
Blob.colorPickerRandom.choose(
Blob.isDarkThemed ? Blob.darkColors : Blob.lightColors
),
(this.z - Blob.zMin) / (Blob.zMax - Blob.zMin)
);
}
get htmlElement(): HTMLElement {
return this.element;
}

View file

@ -0,0 +1,10 @@
import { Content } from '../../model/portfolio';
import { html } from '../../model/misc';
import './content.scss';
export const generate = (content: Content): html => `
<div class="content">
${content.map(element => element.toHTML()).join('\n')}
</div>
`;

View file

@ -1,5 +1,8 @@
@import '../../style/vars';
@import '../../style/mixins';
.content {
margin-top: $small-margin;
@include responsive() using ($vars) {
.content {
margin-top: map_get($vars, $small-margin);
}
}

View file

@ -1,16 +1,10 @@
import './content.scss';
import { PageElement } from '../../framework/page-element';
import { createElement } from '../../framework/helper/create-element';
import { Content } from '../../model/portfolio';
import { generate } from './content.html';
export class PageContent extends PageElement {
public constructor(content: Content) {
super(
createElement(`
<div class="content">
${content.map(element => element.toHTML()).join('\n')}
</div>
`)
);
super(createElement(generate(content)));
}
}

View file

@ -1,7 +1,5 @@
import { Footer } from '../../model/portfolio';
import { html } from '../../model/misc';
import emailIcon from '../../static/icons/at.svg';
import cvIcon from '../../static/icons/cv.svg';
import './footer.scss';
@ -20,13 +18,23 @@ export const generate = ({
.map(
cv =>
`<li>
<img src="${cvIcon}" alt="CV" class="no-open" />
<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 512.002 512.002" style="enable-background:new 0 0 512.002 512.002;" xml:space="preserve" width="512px" height="512px" class="">
<path d="M394.667,42.667h-128c-11.782,0-21.333,9.551-21.333,21.333v128c0,11.782,9.551,21.333,21.333,21.333h128 c11.782,0,21.333-9.551,21.333-21.333V64C416,52.218,406.449,42.667,394.667,42.667z M290.347,192 c7.707-22.268,32.007-34.072,54.275-26.365c12.366,4.28,22.085,13.999,26.365,26.365H290.347z M309.333,117.333 c0-11.782,9.551-21.333,21.333-21.333C342.449,96,352,105.551,352,117.333c0,11.782-9.551,21.333-21.333,21.333 C318.885,138.667,309.333,129.116,309.333,117.333z M394.667,192h-1.387c-4.028-18.843-16.331-34.868-33.493-43.627 c17.25-16.053,18.221-43.051,2.167-60.301c-16.053-17.25-43.051-18.221-60.301-2.167c-17.25,16.053-18.221,43.051-2.167,60.301 c0.696,0.748,1.419,1.471,2.167,2.167c-17.203,8.734-29.548,24.763-33.6,43.627h-1.387V64h128V192z" class="active-path" fill="#31343F"/>
<path d="M432.429,0.002C432.357,0.001,432.285,0,432.214,0h-282.88c-2.835-0.016-5.56,1.097-7.573,3.093L56.427,88.427 c-1.997,2.013-3.11,4.738-3.093,7.573v389.547c0.058,14.586,11.868,26.395,26.453,26.453h352 c14.668,0.177,26.701-11.57,26.878-26.238c0.001-0.072,0.001-0.144,0.002-0.215V26.88 C458.844,12.213,447.097,0.179,432.429,0.002z M437.334,485.547c0,2.828-2.292,5.12-5.12,5.12h-352 c-2.818,0.236-5.293-1.858-5.529-4.675c-0.012-0.148-0.018-0.296-0.018-0.445V100.374l64-64v44.16 c-0.057,2.627-2.173,4.743-4.8,4.8h-27.2v21.333h32c12.352-2.351,21.299-13.133,21.333-25.707V21.333h272.213 c2.828,0,5.12,2.292,5.12,5.12V485.547z" class="active-path" fill="#31343F"/>
<rect x="192" y="256" width="170.667" height="21.333" class="active-path" fill="#31343F"/>
<rect x="149.333" y="309.333" width="213.333" height="21.333" class="active-path" fill="#31343F"/>
<rect x="149.333" y="362.667" width="213.333" height="21.333" class="active-path" fill="#31343F"/>
<rect x="149.333" y="416" width="170.667" height="21.333" class="active-path" fill="#31343F"/>
<rect x="341.334" y="416" width="21.333" height="21.333" class="active-path" fill="#31343F"/>
</svg>
<a id="cv" href="${cv.url}" target="_blank">${cv.name}</a>
</li>`
)
.join('\n')}
<li>
<img src="${emailIcon}" alt="email" class="no-open"/>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="512px" viewBox="0 0 512.00018 512" width="512px">
<path d="m401.738281 442.710938c6.046875 9.242187 3.453125 21.636718-5.792969 27.683593-41.585937 27.195313-89.9375 41.582031-139.824218 41.605469-.039063 0-.082032 0-.121094 0-68.097656.003906-132.101562-26.234375-180.253906-73.890625-48.183594-47.683594-75.078125-111.46875-75.7343752-179.597656-.6601568-68.558594 25.4453122-133.210938 73.5039062-182.054688 48.066406-48.851562 112.265625-76 180.773437-76.45312475.5625 0 1.136719-.00390625 1.699219-.00390625 53.46875 0 104.816407 16.546875 148.613281 47.90625 43.277344 30.988281 75.519532 73.652344 93.238282 123.378906 3.707031 10.40625-1.722656 21.847656-12.125 25.554688-10.40625 3.707031-21.84375-1.722656-25.554688-12.125-30.898437-86.707032-112.84375-144.714844-204.195312-144.714844-.476563 0-.9375 0-1.414063.003906-57.796875.378906-111.964843 23.289063-152.523437 64.507813-40.546875 41.210937-62.570313 95.765625-62.015625 153.613281 1.132812 117.949219 98.019531 213.875 215.992187 213.875h.097656c42.097657-.019531 82.886719-12.148438 117.953126-35.082031 9.242187-6.046875 21.636718-3.453125 27.683593 5.792969zm110.261719-165.710938c0 50.179688-40.820312 91-91 91-32.0625 0-60.304688-16.667969-76.519531-41.796875-20.71875 26.058594-52.679688 42.796875-88.480469 42.796875-62.308594 0-113-50.691406-113-113s50.691406-113 113-113c28.277344 0 54.160156 10.441406 74 27.667969v-3.535157c0-11.042968 8.953125-20 20-20s20 8.957032 20 20v109.867188c0 28.121094 22.878906 51 51 51s51-22.878906 51-51c0-11.046875 8.953125-20 20-20s20 8.953125 20 20zm-183-21c0-40.25-32.746094-73-73-73-40.25 0-73 32.75-73 73 0 40.253906 32.75 73 73 73 40.253906 0 73-32.746094 73-73zm0 0" class="active-path" fill="#31343F"/>
</svg>
<a id="email" href="mailto:${email}">${email}</a>
</li>
</ul>

View file

@ -1,51 +1,58 @@
@import '../../style/mixins';
@import '../../style/vars';
footer#page-footer {
text-align: center;
@include responsive() using ($vars) {
footer#page-footer {
text-align: center;
margin: $large-margin auto 0 auto;
width: 100%;
margin: map_get($vars, $large-margin) auto 0 auto;
width: 100%;
h2 {
@include title-font();
}
h2 {
@include title-font();
}
ul {
list-style: none;
display: inline-block;
margin-top: $normal-margin;
text-align: left;
ul {
list-style: none;
display: inline-block;
margin-top: map_get($vars, $normal-margin);
text-align: left;
li {
@include center-children();
justify-content: flex-start;
li {
@include center-children();
justify-content: flex-start;
&:not(:first-child) {
padding-top: $line-height;
}
&:not(:first-child) {
padding-top: map_get($vars, $line-height);
}
img {
@include max-square($icon-size);
margin-right: $small-margin;
}
img,
svg {
@include max-square(map_get($vars, $icon-size));
* {
fill: map_get($vars, $normal-text-color);
}
margin-right: map_get($vars, $small-margin);
}
a {
font-size: 1.4rem;
a {
font-size: 1.4rem;
}
}
}
}
aside.other {
@include center-children();
flex-direction: column;
margin: $large-margin auto $line-height auto;
width: $body-width;
aside.other {
@include center-children();
flex-direction: column;
margin: map_get($vars, $large-margin) auto map_get($vars, $line-height)
auto;
width: map_get($vars, $body-width);
h6 {
@include insignificant-font();
display: inline;
opacity: 0.75;
h6 {
@include insignificant-font();
display: inline;
opacity: 0.75;
}
}
}
}

View file

@ -1,10 +1,11 @@
import { html } from "../../model/misc";
import cancel from "../../static/icons/cancel.svg";
import "./image-viewer.scss";
import { html } from '../../model/misc';
import cancel from '../../static/icons/cancel.svg';
import './image-viewer.scss';
export const generate = (): html => `
<section id="image-viewer">
<img id="photo" alt="currently opened photo"/>
<div id="container"></div>
<img id="cancel" src="${cancel}" alt="cancel"/>
</section>
`;

View file

@ -1,30 +1,41 @@
@import '../../style/vars';
@import '../../style/mixins';
#image-viewer {
@include center-children();
display: none;
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
margin: 0;
z-index: 2;
background-color: rgba(0, 0, 0, 0.75);
#photo {
max-width: 80vw;
max-height: 80vh;
}
#cancel {
@include square($icon-size);
position: absolute;
box-sizing: content-box;
padding: $normal-margin;
right: 0;
@include responsive() using ($vars) {
#image-viewer {
@include center-children();
display: none;
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: pointer;
margin: 0;
z-index: 2;
background-color: rgba(0, 0, 0, 0.85);
@include on-large-screen {
#container > * {
max-width: 80vw;
max-height: 80vh;
}
}
@include on-small-screen {
#container > * {
max-width: 95vw;
max-height: 95vh;
}
}
#cancel {
@include square(map_get($vars, $icon-size));
position: absolute;
box-sizing: content-box;
padding: map_get($vars, $normal-margin);
right: 0;
top: 0;
cursor: pointer;
}
}
}

View file

@ -6,9 +6,8 @@ import { createElement } from '../../framework/helper/create-element';
export class PageImageViewer extends PageElement {
public constructor() {
const root = createElement(generate());
root.onclick = () => PageImageViewer.hide(root);
super(root);
super(createElement(generate()));
this.element.onclick = () => PageImageViewer.hide(this.element);
}
protected handleEvent(event: PageEvent, parent: PageElement) {
@ -18,25 +17,26 @@ export class PageImageViewer extends PageElement {
document.body.addEventListener('keydown', this.handleKeydown.bind(this));
const images = Array.prototype.slice.call(
parent.element.querySelectorAll('img')
const media = Array.prototype.slice.call(
parent.element.querySelectorAll('img, video')
);
images
media
.filter(
(img: HTMLImageElement) =>
img.parentElement !== this.element &&
!img.classList.contains('no-open')
(e: HTMLElement) =>
e.parentElement !== this.element && !e.classList.contains('no-open')
)
.forEach(
(img: HTMLImageElement) => (img.onclick = this.handleClick.bind(this))
(e: HTMLImageElement) => (e.onclick = this.handleClick.bind(this))
);
}
private handleClick(event: Event) {
(this.query(
'#photo'
) as HTMLImageElement).src = (event.target as HTMLImageElement).src;
const container = this.query('#container');
Array.prototype.forEach.call(container.childNodes, (e: HTMLElement) =>
e.remove()
);
container.appendChild((event.target as HTMLElement).cloneNode());
PageImageViewer.show(this.element);
}

View file

@ -12,7 +12,7 @@ export const create = ({ header, timeline, footer }: Portfolio) => {
new ContainerPage(document.body, [
new PageImageViewer(),
new ContainerPage(document.body.querySelector('main'), [
new ContainerPage(document.body.querySelector('.main'), [
pageHeader,
new PageTimeline(timeline),
pageFooter,

View file

@ -0,0 +1,7 @@
import { html } from '../../model/misc';
import './theme-switcher.scss';
export const generate = (): html => `
<input id="theme-switcher" type="checkbox" name="switch-theme"/>
`;

View file

@ -0,0 +1,94 @@
@import '../../style/mixins';
@import '../../style/vars';
@include responsive using($vars) {
input[type='checkbox']#theme-switcher {
@include on-large-screen {
position: fixed;
top: map_get($vars, $large-margin);
right: map_get($vars, $large-margin);
}
@include on-small-screen {
position: relative;
margin-top: map_get($vars, $small-margin);
}
$size: map_get($vars, $icon-size);
$small-size: 4 / 5 * $size;
z-index: 10;
-webkit-appearance: none;
-moz-appearance: none;
width: 2 * $size;
height: $size;
border-radius: 1000px;
box-shadow: map_get($vars, $shadow1), map_get($vars, $shadow2);
cursor: pointer;
&:before,
&:after {
content: '';
position: absolute;
display: block;
border-radius: 1000px;
@include square($size);
transition: height map_get($vars, $long-transition-time),
width map_get($vars, $long-transition-time),
left map_get($vars, $long-transition-time),
top map_get($vars, $long-transition-time),
transform map_get($vars, $long-transition-time),
background-color map_get($vars, $long-transition-time);
}
&:before {
left: 0;
background-color: transparent;
}
&:after {
$delta: 4px;
top: -$delta / 2;
left: $size;
@include square($size + $delta);
background-color: map_get($vars, $theme-switcher-color);
animation: shine 3s linear alternate infinite;
@keyframes shine {
from {
filter: brightness(1.01);
box-shadow: 0 0 4px 2px map_get($vars, $theme-switcher-color);
}
to {
filter: brightness(1.1);
box-shadow: 0 0 15px 2px map_get($vars, $theme-switcher-color);
}
}
}
&:checked {
&:before {
background-color: map_get($vars, $normal-text-color);
}
&:after {
@include square($small-size);
$offset: $small-size / 5.5;
left: $size - $small-size + $offset;
top: ($size - $small-size) / 2;
background-color: map_get($vars, $background);
}
}
&:focus {
outline: 0;
}
}
}

View file

@ -0,0 +1,40 @@
import { PageElement } from '../../framework/page-element';
import { createElement } from '../../framework/helper/create-element';
import { generate } from './theme-switcher.html';
import {
isSystemLevelDarkModeEnabled,
turnOnDarkMode,
turnOnLightMode,
} from '../../framework/helper/dark-mode';
import { PageEvent, PageEventType } from '../../framework/page-event';
import { EventBroadcaster } from '../../framework/event-broadcaster';
export class PageThemeSwitcher extends PageElement {
public constructor() {
super(createElement(generate()));
if (isSystemLevelDarkModeEnabled()) {
(this.element as HTMLInputElement).checked = true;
}
this.element.onchange = this.handleThemeChange.bind(this);
}
protected handleEvent(event: PageEvent, parent: EventBroadcaster) {
if (event.type === PageEventType.onLoad) {
console.log('a');
this.handleThemeChange();
}
}
private handleThemeChange() {
const isDark = (this.element as HTMLInputElement).checked;
if (isDark) {
turnOnDarkMode();
} else {
turnOnLightMode();
}
this.eventBroadcaster.broadcastEvent({
type: PageEventType.pageThemeChanged,
data: isDark,
});
}
}

View file

@ -1,112 +1,126 @@
@import '../../../style/mixins';
@import '../../../style/vars';
.timeline-element {
display: flex;
.line {
position: relative;
border-left: $line-width solid $accent-color;
&:before {
content: '';
@include square($icon-size);
position: absolute;
left: calc(-0.5 * #{$icon-size} - (1.5 * #{$line-width}));
border: $line-width solid $accent-color;
border-radius: 100%;
background: $background;
}
.date {
@include insignificant-font();
}
}
@media (min-width: $breakpoint-width) {
&:not(:first-of-type) .card {
margin-top: $large-margin;
}
@include responsive() using ($vars) {
.timeline-element {
display: flex;
width: map_get($vars, $body-width);
margin: auto;
.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();
justify-content: flex-start;
height: 150px;
width: 50%;
.date {
margin-left: calc(#{$icon-size} / 2 + #{$small-margin});
width: 200px;
}
}
}
.card {
@include card();
overflow: hidden;
& > *:not(:first-child) {
margin-top: $line-height;
}
h2 {
@include sub-title-font();
}
& > p {
font-style: italic;
text-align: center;
}
.more {
overflow: hidden;
height: 0;
margin-top: 0;
transition: height $long-transition-time;
}
.buttons {
position: relative;
margin-top: $small-margin;
border-left: map_get($vars, $line-width) solid
map_get($vars, $accent-color);
* {
transition: opacity $long-transition-time;
}
.show-more {
opacity: 1;
}
.show-less {
opacity: 0;
visibility: hidden;
&:before {
content: '';
@include square(map_get($vars, $icon-size));
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
left: calc(
-0.5 * #{map_get($vars, $icon-size)} -
(1.5 * #{map_get($vars, $line-width)})
);
border: map_get($vars, $line-width) solid map_get($vars, $accent-color);
border-radius: 100%;
background: map_get($vars, $background);
}
.date {
@include insignificant-font();
color: map_get($vars, $accent-color);
}
}
@include on-large-screen {
&:not(:first-of-type) .card {
margin-top: map_get($vars, $large-margin);
}
.line {
&:before {
top: calc(33% - #{map_get($vars, $icon-size)} / 2);
}
.date {
position: relative;
top: calc(33% + #{map_get($vars, $icon-size)} / 2 + 1ch);
transform: rotate(30deg);
margin: 0 map_get($vars, $normal-margin) 0
calc(#{map_get($vars, $line-width)} + 1ex);
width: 100px;
}
}
}
@include on-small-screen {
flex-direction: column;
align-items: center;
&:before {
top: calc(50% - #{map_get($vars, $icon-size)} / 2);
}
.line {
@include center-children();
justify-content: flex-start;
height: 150px;
width: 50%;
.date {
margin-left: calc(
#{map_get($vars, $icon-size)} / 2 + #{map_get($vars, $small-margin)}
);
width: 200px;
}
}
}
.card {
@include card-base($vars);
border-radius: map_get($vars, $border-radius);
background-color: map_get($vars, $card-color);
overflow: hidden;
& > *:not(:first-child) {
margin-top: map_get($vars, $line-height);
}
h2 {
@include sub-title-font();
}
& > p {
font-style: italic;
text-align: center;
}
.more {
overflow: hidden;
height: 0;
margin-top: 0;
transition: height map_get($vars, $long-transition-time);
}
.buttons {
position: relative;
margin-top: map_get($vars, $small-margin);
* {
transition: opacity map_get($vars, $long-transition-time);
}
.show-more {
opacity: 1;
}
.show-less {
opacity: 0;
visibility: hidden;
position: absolute;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
}
}
}
}

View file

@ -1,10 +1,13 @@
@import '../../style/vars';
@import '../../style/mixins';
#timeline {
width: $body-width;
margin: $large-margin auto 0 auto;
@media (max-width: $breakpoint-width) {
margin: auto;
@include responsive() using ($vars) {
#timeline {
@include on-large-screen {
// workaround for IE
& > :first-child {
margin-top: map_get($vars, $large-margin);
}
}
}
}