mirror of https://github.com/mkrhere/pw2
Muthu Kumar
1 year ago
5 changed files with 270 additions and 157 deletions
@ -0,0 +1,101 @@ |
|||
import { css } from "@emotion/css"; |
|||
import React from "react"; |
|||
import { setupDynamicGradient } from "../../util"; |
|||
import { Experience } from "./types"; |
|||
|
|||
const Circle: React.FC = () => ( |
|||
<div className="timeline-segment" aria-hidden> |
|||
<div |
|||
className={css` |
|||
width: 200vw; |
|||
height: 1px; |
|||
background: #333333; |
|||
left: -50vw; |
|||
position: absolute; |
|||
top: calc(-3rem + 0.2rem / 2); |
|||
z-index: 0; |
|||
`}></div>
|
|||
<div |
|||
className={css` |
|||
width: 0.25rem; |
|||
height: 0.25rem; |
|||
background: #333333; |
|||
background: #ffffff; |
|||
border-radius: 100%; |
|||
position: absolute; |
|||
top: -3rem; |
|||
left: 0; |
|||
z-index: 100; |
|||
`}></div>
|
|||
</div> |
|||
); |
|||
|
|||
const btn = css` |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 0.6rem; |
|||
cursor: pointer; |
|||
padding: 1rem var(--item-padding); |
|||
border-radius: 0.5rem; |
|||
position: relative; |
|||
height: max-content; |
|||
width: 100%; |
|||
|
|||
background-color: transparent; |
|||
border: none; |
|||
text-align: left; |
|||
display: inherit; |
|||
|
|||
& > * { |
|||
z-index: 10; |
|||
} |
|||
|
|||
@media (pointer: fine) { |
|||
&:hover { |
|||
background-color: var(--card-hover); |
|||
z-index: 1000; |
|||
box-shadow: 0 0 25rem 2rem rgba(190, 190, 190, 0.1); |
|||
} |
|||
} |
|||
|
|||
& .timeline-segment { |
|||
position: absolute; |
|||
} |
|||
|
|||
& .position { |
|||
color: var(--text-colour); |
|||
} |
|||
|
|||
& .year, |
|||
& h5 { |
|||
font-size: 0.8rem; |
|||
font-weight: 300; |
|||
color: var(--text-subdued); |
|||
} |
|||
|
|||
& h5 { |
|||
font-weight: 400; |
|||
margin-block-start: 0.2rem; |
|||
} |
|||
`;
|
|||
|
|||
export const Content = ({ |
|||
onClick, |
|||
title, |
|||
year, |
|||
position, |
|||
location, |
|||
}: Experience) => { |
|||
return ( |
|||
<button className={btn} onClick={onClick} ref={setupDynamicGradient}> |
|||
<div className="dynamic-gradient" /> |
|||
<Circle /> |
|||
<h4> |
|||
{title} |
|||
<span className="year"> · ({year})</span> |
|||
</h4> |
|||
<span className="position">{position}</span> |
|||
<h5>{location}</h5> |
|||
</button> |
|||
); |
|||
}; |
@ -0,0 +1,66 @@ |
|||
import React from "react"; |
|||
import { css, cx } from "@emotion/css"; |
|||
import { Experience } from "./types"; |
|||
|
|||
const story = css` |
|||
position: absolute; |
|||
left: 0; |
|||
width: 100%; |
|||
border-radius: 0.5rem; |
|||
display: flex; |
|||
|
|||
overflow: hidden; |
|||
|
|||
& .contents { |
|||
padding: 1.5rem; |
|||
line-height: 1.25rem; |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: flex-start; |
|||
gap: 2rem; |
|||
margin-block-start: 1rem; |
|||
height: var(--story-height); |
|||
|
|||
& ul { |
|||
max-height: 100%; |
|||
margin: 0; |
|||
column-count: 3; |
|||
column-gap: 2rem; |
|||
color: var(--text-subdued); |
|||
font-weight: 400; |
|||
|
|||
& li + li { |
|||
margin-block-start: 0.5rem; |
|||
} |
|||
|
|||
& li::marker { |
|||
content: ""; |
|||
font-weight: 800; |
|||
padding-top: 1rem; |
|||
} |
|||
} |
|||
} |
|||
`;
|
|||
|
|||
export const Story = ({ description, logo }: Experience) => { |
|||
return ( |
|||
<div className={cx(story, "story")}> |
|||
<div className="contents"> |
|||
<img |
|||
src={`/assets/logos/` + logo} |
|||
className={cx( |
|||
"story-logo", |
|||
css` |
|||
height: 4rem; |
|||
width: 4rem; |
|||
|
|||
background: rgba(40, 40, 40); |
|||
border-radius: 100%; |
|||
`,
|
|||
)} |
|||
/> |
|||
<ul>{description}</ul> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
@ -0,0 +1,70 @@ |
|||
import React from "react"; |
|||
import { css, cx } from "@emotion/css"; |
|||
import { Story } from "./Story"; |
|||
import { Experience } from "./types"; |
|||
import { Content } from "./Content"; |
|||
|
|||
const expUnit = css` |
|||
--final-height: 20rem; |
|||
--unit-height: 9rem; |
|||
--story-height: calc(var(--final-height) - var(--unit-height)); |
|||
--transition-time: 300ms; |
|||
|
|||
& > * { |
|||
line-height: 1em; |
|||
font-size: 1rem; |
|||
} |
|||
|
|||
& button { |
|||
border: 1px solid transparent; |
|||
transition: all calc(var(--transition-time) * 2); |
|||
} |
|||
|
|||
&.active button { |
|||
background-color: var(--card-active); |
|||
border: 1px solid var(--card-active-border); |
|||
box-shadow: 0 0 50rem 0 rgba(190, 190, 190, 0.5); |
|||
z-index: 800; |
|||
|
|||
& .year, |
|||
& h5 { |
|||
color: var(--text-colour); |
|||
} |
|||
} |
|||
|
|||
margin-block-end: 0.5rem; |
|||
|
|||
/* -- Animation stuff -- */ |
|||
|
|||
height: var(--unit-height); |
|||
transition: height var(--transition-time) ease-in-out; |
|||
|
|||
& .story { |
|||
opacity: 0; |
|||
transition: opacity var(--transition-time) ease-in-out; |
|||
transition-delay: 0; |
|||
} |
|||
|
|||
&.active { |
|||
height: var(--final-height); |
|||
transition-delay: 0; |
|||
transition-delay: var(--transition-time); |
|||
|
|||
.story { |
|||
opacity: 1; |
|||
transition: opacity calc(var(--transition-time) * 2) ease-in-out; |
|||
transition-delay: var(--transition-time); |
|||
} |
|||
} |
|||
|
|||
/* -- */ |
|||
`;
|
|||
|
|||
export const ExpUnit = (props: Experience) => { |
|||
return ( |
|||
<div className={cx(expUnit, { active: props.active })}> |
|||
<Content {...props} /> |
|||
<Story {...props} /> |
|||
</div> |
|||
); |
|||
}; |
@ -0,0 +1,10 @@ |
|||
export interface Experience { |
|||
active: boolean; |
|||
title: string; |
|||
location: string; |
|||
position: string; |
|||
year: string; |
|||
description: React.ReactElement | string; |
|||
logo: string; |
|||
onClick?: (e: React.MouseEvent) => void; |
|||
} |
Loading…
Reference in new issue