Compare commits
4 Commits
a73b035932
...
1798313c9c
Author | SHA1 | Date | |
---|---|---|---|
1798313c9c | |||
1957927896 | |||
7d4167b33d | |||
8453a7ccfe |
BIN
public/photo.png
BIN
public/photo.png
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 697 KiB |
@ -3,13 +3,14 @@ import { PersonalData } from "./PersonalDataTypes";
|
|||||||
export const personalData: PersonalData = {
|
export const personalData: PersonalData = {
|
||||||
updatedDate: '2023-05-26',
|
updatedDate: '2023-05-26',
|
||||||
name: "David Hrdina Němeček",
|
name: "David Hrdina Němeček",
|
||||||
brief: "Software developer, people manager.",
|
brief: "Software developer. Engineering lead & manager.",
|
||||||
|
|
||||||
contacts: [
|
contacts: [
|
||||||
{icon: 'browser-firefox', text: 'www.dejvino.cz'},
|
{icon: 'browser-firefox', text: 'www.dejvino.cz'},
|
||||||
{icon: 'envelope-at', text: 'explosive@dejvino.cz'},
|
{icon: 'envelope-at', text: 'explosive@dejvino.cz'},
|
||||||
{icon: 'git', text: 'https://git.dejvino.cz'},
|
{icon: 'git', text: 'https://git.dejvino.cz'},
|
||||||
{icon: 'telephone', text: '+420 111 222 333'},
|
{icon: 'file-earmark-person', text: 'https://cv.dejvino.cz'},
|
||||||
|
{icon: 'telephone', text: '+420 775 26 26 32'},
|
||||||
{icon: 'geo-alt', text: 'Brno, Czechia'}
|
{icon: 'geo-alt', text: 'Brno, Czechia'}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ export const personalData: PersonalData = {
|
|||||||
position: `Personal projects`,
|
position: `Personal projects`,
|
||||||
description: `Various hardware and software projects. Usually open sourced and published on [projects.dejvino.com](https://projects.dejvino.com) .
|
description: `Various hardware and software projects. Usually open sourced and published on [projects.dejvino.com](https://projects.dejvino.com) .
|
||||||
These include video games, utilities, 3D models, devices with embedded microcontrollers etc.`,
|
These include video games, utilities, 3D models, devices with embedded microcontrollers etc.`,
|
||||||
tags: ['Java', 'Python', 'C/C++', 'Embedded devices', 'OpenSCAD', 'TypeScript', 'Linux', 'Git', 'Self-hosting']
|
tags: ['Java', 'Python', 'C/C++', 'Embedded Software', 'OpenSCAD', 'TypeScript', 'Linux', 'Open Source', 'Git', 'Self-hosting']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -75,21 +76,23 @@ export const personalData: PersonalData = {
|
|||||||
company: `Faculty of Informatics, Masaryk University Brno`,
|
company: `Faculty of Informatics, Masaryk University Brno`,
|
||||||
timerange: '2011 - 2013',
|
timerange: '2011 - 2013',
|
||||||
description: `Master's thesis: Efficient computation and visualization of correlations in medical signals.`,
|
description: `Master's thesis: Efficient computation and visualization of correlations in medical signals.`,
|
||||||
|
tags: ['C/C++', 'Supercomputers', 'OpenMPI', 'Python', 'Visualization', 'Data science']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
position: `Bachelor's degree, Parallel and Distributed Systems`,
|
position: `Bachelor's degree, Parallel and Distributed Systems`,
|
||||||
company: `Faculty of Informatics, Masaryk University Brno`,
|
company: `Faculty of Informatics, Masaryk University Brno`,
|
||||||
timerange: '2008 - 2011',
|
timerange: '2008 - 2011',
|
||||||
description: `Bachelor's thesis: Parallel implementation of force-field decompression algorithm.`,
|
description: `Bachelor's thesis: Parallel implementation of force-field decompression algorithm.`,
|
||||||
|
tags: ['C/C++', 'Supercomputers', 'OpenMPI', 'Data science']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
skills: {
|
skills: {
|
||||||
primary: ['Java', 'TypeScript', 'JavaScript', 'Engineering leadership', 'Linux'],
|
primary: ['Java', 'TypeScript', 'JavaScript', 'Linux', 'Engineering Leadership'],
|
||||||
secondary: ['SQL', 'Kotlin', 'Go', 'C/C++', 'NodeJs', 'Git', 'Preact', 'Embedded devices'],
|
secondary: ['SQL', 'Kotlin', 'C/C++', 'NodeJs', 'Git', 'Preact', 'Embedded Software'],
|
||||||
languages: ['Czech (native)', 'English (proficient)', 'German (elementary)'],
|
languages: ['Czech (native)', 'English (proficient)', 'German (elementary)'],
|
||||||
others: ['Driver\'s license (B)']
|
//others: ['Driver\'s license (B)']
|
||||||
},
|
},
|
||||||
interests: ['Guitars', 'Heavy Metal', 'Mazda MX-5', 'DIY electronics', 'Linux', 'Open source'],
|
interests: ['Guitars', 'Heavy Metal', 'Mazda MX-5', 'DIY electronics', 'Linux', 'Open source'],
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ export default function AboutBrief() {
|
|||||||
const person = usePersonContext()
|
const person = usePersonContext()
|
||||||
return (
|
return (
|
||||||
<Container className='about-brief' fluid>
|
<Container className='about-brief' fluid>
|
||||||
<h1>{person.name}</h1>
|
<h1 className='person-name'>{person.name}</h1>
|
||||||
<div className='brief'>{md(person.brief)}</div>
|
<div className='brief'>{md(person.brief)}</div>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,6 @@ import Image from 'react-bootstrap/Image'
|
|||||||
|
|
||||||
export default function Photo() {
|
export default function Photo() {
|
||||||
return (
|
return (
|
||||||
<Image alt='Photograph of the person' rounded fluid src='photo.png'></Image>
|
<Image className="photo" alt='Photograph of the person' rounded fluid src='photo.png'></Image>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import TagCloud from './TagCloud';
|
|||||||
export default function Skills() {
|
export default function Skills() {
|
||||||
const person = useContext(PersonContext)
|
const person = useContext(PersonContext)
|
||||||
return (
|
return (
|
||||||
<Container className='skills'>
|
<Container className='skills' fluid>
|
||||||
<h2>Skills</h2>
|
<h2>Skills</h2>
|
||||||
<TagCloud title='Primary' icon='bookmark-star' style='primary' tags={person.skills.primary} />
|
<TagCloud title='Primary' icon='bookmark-star' style='primary' tags={person.skills.primary} />
|
||||||
{person.skills.secondary && (
|
{person.skills.secondary && (
|
||||||
|
@ -12,9 +12,9 @@ export type Props = {
|
|||||||
export default function TagCloud(props: Props) {
|
export default function TagCloud(props: Props) {
|
||||||
const containerClasses = ['tag-cloud', 'cloud-' + (props.style || 'standard')]
|
const containerClasses = ['tag-cloud', 'cloud-' + (props.style || 'standard')]
|
||||||
return (
|
return (
|
||||||
<Container className={containerClasses.join(' ')}>
|
<Container className={containerClasses.join(' ')} fluid>
|
||||||
<h4>{props.icon && (<i className={'bi-' + props.icon}> </i>)}{props.title}</h4>
|
<h4>{props.icon && (<i className={'bi-' + props.icon}> </i>)}{props.title}</h4>
|
||||||
<Container className='tag-badges'>
|
<Container className='tag-badges' fluid>
|
||||||
{props.tags.map((tag: string) => (<Tag key={tag} text={tag} />) )}
|
{props.tags.map((tag: string) => (<Tag key={tag} text={tag} />) )}
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Container from 'react-bootstrap/Container';
|
import Container from 'react-bootstrap/Container';
|
||||||
import useSize from '../../hooks/Size';
|
import useInWidthRange from '../../hooks/InWidthRange';
|
||||||
import { JobListProps } from './types';
|
import { JobListProps } from './types';
|
||||||
import JobsAccordion from './JobsAccordion';
|
import JobsAccordion from './JobsAccordion';
|
||||||
import JobsCards, { JobsCardsPlaceholder } from './JobsCards';
|
import JobsCards, { JobsCardsPlaceholder } from './JobsCards';
|
||||||
@ -10,19 +10,14 @@ export type Props = {
|
|||||||
heading: string,
|
heading: string,
|
||||||
} & JobListProps
|
} & JobListProps
|
||||||
|
|
||||||
const defaultProps = {
|
|
||||||
entriesPerRow: 2,
|
|
||||||
currentHeading: 'Currently',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function JobHistory(props: Props) {
|
export default function JobHistory(props: Props) {
|
||||||
const {SizeWrapper, size} = useSize()
|
const {SizeWrapper, inRange} = useInWidthRange(600)
|
||||||
|
|
||||||
const jobsList = size.width === 0 ? <JobsCardsPlaceholder /> : (
|
const jobsList = inRange === undefined ? <JobsCardsPlaceholder /> : (
|
||||||
size.width < 600 ? <JobsAccordion {...props} /> : <JobsCards {...props} />)
|
inRange ? <JobsAccordion {...props} /> : <JobsCards {...props} />)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container fluid>
|
||||||
<h2>{props.heading}</h2>
|
<h2>{props.heading}</h2>
|
||||||
<SizeWrapper>
|
<SizeWrapper>
|
||||||
{jobsList}
|
{jobsList}
|
||||||
|
@ -8,6 +8,19 @@ body {
|
|||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
padding-bottom: 0.2rem;
|
padding-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.person-name {
|
||||||
|
margin-top: calc(min(6vw, 5rem));
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: calc(1.375rem + min(1.4vw, 1rem));
|
||||||
|
}
|
||||||
|
.brief {
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
.contacts {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
filter: opacity(75%)
|
filter: opacity(75%)
|
||||||
@ -17,7 +30,7 @@ body {
|
|||||||
filter: opacity(75%)
|
filter: opacity(75%)
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > h2, h3, h4 {
|
.container > h2, .container-fluid > h2, h3, h4 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +68,7 @@ body {
|
|||||||
}
|
}
|
||||||
.tag-badges > span {
|
.tag-badges > span {
|
||||||
margin: 0.4em;
|
margin: 0.4em;
|
||||||
|
color: #222;
|
||||||
}
|
}
|
||||||
.contacts .contact {
|
.contacts .contact {
|
||||||
margin: 0.75em;
|
margin: 0.75em;
|
||||||
@ -65,6 +79,14 @@ body {
|
|||||||
.accordion {
|
.accordion {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
.main-container .accordion-button:not(.collapsed) {
|
||||||
|
color: #222;
|
||||||
|
background-color: RGBA(217, 216, 216, 0.3);
|
||||||
|
}
|
||||||
|
.main-container .accordion-button.collapsed {
|
||||||
|
color: #0e489d;
|
||||||
|
background-color: RGBA(153, 165, 213, 0.3);
|
||||||
|
}
|
||||||
.multiline {
|
.multiline {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
28
src/app/hooks/InWidthRange.tsx
Normal file
28
src/app/hooks/InWidthRange.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, { ForwardedRef, ReactNode, forwardRef, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
export default function useInWidthRange(threshold: number) {
|
||||||
|
const areaRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [inRange, setInRange] = useState<boolean | undefined>(undefined)
|
||||||
|
|
||||||
|
function SizeWrapper(props: {children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div ref={areaRef}>{props.children}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function updateState() {
|
||||||
|
if (areaRef.current) {
|
||||||
|
const width = areaRef.current.offsetWidth
|
||||||
|
setInRange(width <= threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateState()
|
||||||
|
addEventListener("resize", updateState);
|
||||||
|
return () => {
|
||||||
|
removeEventListener("resize", updateState)
|
||||||
|
};
|
||||||
|
}, [areaRef, threshold, setInRange])
|
||||||
|
|
||||||
|
return {SizeWrapper, inRange}
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
import React, { ForwardedRef, ReactNode, forwardRef, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
export default function useSize() {
|
|
||||||
const areaRef = useRef<HTMLDivElement>(null)
|
|
||||||
const [size, setSize] = useState({ width: 0, height: 0 })
|
|
||||||
|
|
||||||
function SizeWrapper(props: {children: ReactNode }) {
|
|
||||||
return (
|
|
||||||
<div ref={areaRef}>{props.children}</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function updateSize() {
|
|
||||||
if (areaRef.current) {
|
|
||||||
setSize({
|
|
||||||
width: areaRef.current.offsetWidth,
|
|
||||||
height: areaRef.current.offsetHeight
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateSize()
|
|
||||||
addEventListener("resize", updateSize);
|
|
||||||
return () => {
|
|
||||||
removeEventListener("resize", updateSize)
|
|
||||||
};
|
|
||||||
}, [areaRef, setSize])
|
|
||||||
|
|
||||||
return {SizeWrapper, size}
|
|
||||||
}
|
|
@ -16,13 +16,9 @@ export default function Home() {
|
|||||||
|
|
||||||
<Container className='main-container' fluid='xxl'>
|
<Container className='main-container' fluid='xxl'>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={'auto'} sm={4} lg={2}><Photo /></Col>
|
<Col xs={3} sm={3} lg={2}><Photo /></Col>
|
||||||
<Col>
|
<Col xs={9} sm={9} lg={5}><AboutBrief /></Col>
|
||||||
<Row>
|
<Col xs={12} lg={5}><Contacts /></Col>
|
||||||
<Col xs={'auto'} lg={6}><AboutBrief /></Col>
|
|
||||||
<Col xs={'auto'} lg={6}><Contacts /></Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={12} xl={7}>
|
<Col xs={12} xl={7}>
|
||||||
@ -31,7 +27,7 @@ export default function Home() {
|
|||||||
<Row><Education /></Row>
|
<Row><Education /></Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Row><Skills /></Row>
|
<Skills />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row><Footer /></Row>
|
<Row><Footer /></Row>
|
||||||
|
Loading…
Reference in New Issue
Block a user