|
@ -1,8 +1,36 @@ |
|
|
import React from "react"; |
|
|
import React, { forwardRef } from "react"; |
|
|
import { css, cx } from "@emotion/css"; |
|
|
import { css, cx } from "@emotion/css"; |
|
|
import { intersperse, sleep } from "../util"; |
|
|
import { intersperse, sleep } from "../util"; |
|
|
|
|
|
|
|
|
// && is used to increase specificity to override global styles
|
|
|
// && is a hack to increase specificity, NEVER try to understand this
|
|
|
|
|
|
// if you need to increase specificity just add more &&
|
|
|
|
|
|
// no, actually don't do this, this is a bad practice
|
|
|
|
|
|
// but I'm doing it here because YOLO
|
|
|
|
|
|
// see if I care, I'm a bad person
|
|
|
|
|
|
// psst
|
|
|
|
|
|
// hey
|
|
|
|
|
|
// if you're reading this and you think this is a good idea
|
|
|
|
|
|
// you're a bad person too
|
|
|
|
|
|
// don't do this
|
|
|
|
|
|
// this is bad
|
|
|
|
|
|
//
|
|
|
|
|
|
// are you still reading this?
|
|
|
|
|
|
// why are you still reading this?
|
|
|
|
|
|
// this is a bad idea
|
|
|
|
|
|
// stop reading this
|
|
|
|
|
|
// go do something else
|
|
|
|
|
|
// like, anything else
|
|
|
|
|
|
// literally anything else
|
|
|
|
|
|
// why are you still reading this
|
|
|
|
|
|
// stop
|
|
|
|
|
|
//
|
|
|
|
|
|
// Wait, can you fix this?
|
|
|
|
|
|
// Please?
|
|
|
|
|
|
// I'm sorry
|
|
|
|
|
|
// Please send help
|
|
|
|
|
|
// Send PRs
|
|
|
|
|
|
//
|
|
|
|
|
|
// I hope these comments are removed by the minifier
|
|
|
|
|
|
|
|
|
// prettier-ignore
|
|
|
// prettier-ignore
|
|
|
const opaque = css`&& { opacity: 1 }`; |
|
|
const opaque = css`&& { opacity: 1 }`; |
|
@ -11,6 +39,9 @@ const opaque = css`&& { opacity: 1 }`; |
|
|
const halfVisible = css`&&& {opacity: 0.5}`; |
|
|
const halfVisible = css`&&& {opacity: 0.5}`; |
|
|
|
|
|
|
|
|
// prettier-ignore
|
|
|
// prettier-ignore
|
|
|
|
|
|
const invisible = css`& { opacity: 0 }`; |
|
|
|
|
|
|
|
|
|
|
|
// prettier-ignore
|
|
|
const opacities = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45].map(each => css`&&& { opacity: ${0.5 + each} }`); |
|
|
const opacities = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45].map(each => css`&&& { opacity: ${0.5 + each} }`); |
|
|
|
|
|
|
|
|
const tripleBlink = async (el: HTMLElement) => { |
|
|
const tripleBlink = async (el: HTMLElement) => { |
|
@ -30,12 +61,14 @@ const tripleBlink = async (el: HTMLElement) => { |
|
|
el.classList.remove(halfVisible); |
|
|
el.classList.remove(halfVisible); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const Flicker: React.FC<{ |
|
|
export const Tooltip = forwardRef< |
|
|
children: React.ReactNode; |
|
|
HTMLButtonElement, |
|
|
index: number; |
|
|
{ |
|
|
description: string; |
|
|
children: React.ReactNode; |
|
|
style?: React.CSSProperties; |
|
|
description: React.ReactNode; |
|
|
}> = ({ children, index, description, style }) => { |
|
|
style?: React.CSSProperties; |
|
|
|
|
|
} |
|
|
|
|
|
>(({ children, description, style }, ref) => { |
|
|
return ( |
|
|
return ( |
|
|
<span |
|
|
<span |
|
|
style={style} |
|
|
style={style} |
|
@ -43,45 +76,27 @@ const Flicker: React.FC<{ |
|
|
position: relative; |
|
|
position: relative; |
|
|
|
|
|
|
|
|
&&& button:focus ~ .tooltip, |
|
|
&&& button:focus ~ .tooltip, |
|
|
&&& button:hover ~ .tooltip { |
|
|
&&&:hover .tooltip { |
|
|
opacity: 1; |
|
|
opacity: 1; |
|
|
|
|
|
pointer-events: all; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
&&& button:focus, |
|
|
&&& button:focus, |
|
|
&&& button:hover { |
|
|
&&&:hover button { |
|
|
opacity: 1; |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
`}>
|
|
|
`}>
|
|
|
<button |
|
|
<button |
|
|
className={css` |
|
|
className={css` |
|
|
border-bottom: 1px dashed var(--text-colour); |
|
|
border-bottom: 1px dashed var(--text-colour); |
|
|
opacity: 0; |
|
|
|
|
|
background-color: transparent; |
|
|
background-color: transparent; |
|
|
border: none; |
|
|
border: none; |
|
|
color: inherit; |
|
|
color: inherit; |
|
|
position: relative; |
|
|
position: relative; |
|
|
font-size: 0.9rem; |
|
|
font-size: inherit; |
|
|
|
|
|
padding: 0; |
|
|
`}
|
|
|
`}
|
|
|
ref={async el => { |
|
|
ref={ref}> |
|
|
if (!el) return; |
|
|
|
|
|
|
|
|
|
|
|
await sleep(500); |
|
|
|
|
|
await sleep(300 * index); |
|
|
|
|
|
el.classList.add(opaque); |
|
|
|
|
|
|
|
|
|
|
|
await sleep(1000 + Math.random() * 1000); |
|
|
|
|
|
tripleBlink(el); |
|
|
|
|
|
|
|
|
|
|
|
while (true) { |
|
|
|
|
|
await sleep(5000 + Math.random() * 10000); |
|
|
|
|
|
const chosen = |
|
|
|
|
|
opacities[Math.floor(Math.random() * opacities.length)]; |
|
|
|
|
|
el.classList.add(chosen); |
|
|
|
|
|
await sleep(2000); |
|
|
|
|
|
el.classList.remove(chosen); |
|
|
|
|
|
tripleBlink(el); |
|
|
|
|
|
} |
|
|
|
|
|
}}> |
|
|
|
|
|
{children} |
|
|
{children} |
|
|
</button> |
|
|
</button> |
|
|
<span |
|
|
<span |
|
@ -90,22 +105,14 @@ const Flicker: React.FC<{ |
|
|
css` |
|
|
css` |
|
|
/* tooltip */ |
|
|
/* tooltip */ |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
top: 150%; |
|
|
top: 100%; |
|
|
left: 50%; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
transform: translateX(-50%); |
|
|
background: var(--card-tags); |
|
|
|
|
|
color: var(--text-colour); |
|
|
|
|
|
border-radius: 0.5rem; |
|
|
|
|
|
padding: 0.5rem 0.8rem; |
|
|
|
|
|
font-size: 0.8rem; |
|
|
|
|
|
min-width: 20rem; |
|
|
|
|
|
width: fit-content; |
|
|
|
|
|
max-width: 80vw; |
|
|
|
|
|
opacity: 0; |
|
|
opacity: 0; |
|
|
transition: opacity 0.2s; |
|
|
transition: opacity 0.2s; |
|
|
user-select: none; |
|
|
user-select: none; |
|
|
text-align: left; |
|
|
|
|
|
pointer-events: none; |
|
|
pointer-events: none; |
|
|
|
|
|
z-index: 999; |
|
|
|
|
|
|
|
|
@media screen and (max-width: 65rem) { |
|
|
@media screen and (max-width: 65rem) { |
|
|
position: fixed; |
|
|
position: fixed; |
|
@ -115,39 +122,60 @@ const Flicker: React.FC<{ |
|
|
} |
|
|
} |
|
|
`,
|
|
|
`,
|
|
|
)}> |
|
|
)}> |
|
|
{description} |
|
|
<span |
|
|
|
|
|
className={css` |
|
|
|
|
|
margin: 0.5rem; |
|
|
|
|
|
|
|
|
|
|
|
display: block; |
|
|
|
|
|
min-width: 20rem; |
|
|
|
|
|
width: fit-content; |
|
|
|
|
|
max-width: 80vw; |
|
|
|
|
|
background: var(--card-tags); |
|
|
|
|
|
color: var(--text-colour); |
|
|
|
|
|
border-radius: 0.5rem; |
|
|
|
|
|
padding: 0.5rem 0.8rem; |
|
|
|
|
|
font-size: 0.8rem; |
|
|
|
|
|
text-align: left; |
|
|
|
|
|
line-height: 1.4; |
|
|
|
|
|
`}>
|
|
|
|
|
|
{description} |
|
|
|
|
|
</span> |
|
|
</span> |
|
|
</span> |
|
|
</span> |
|
|
</span> |
|
|
); |
|
|
); |
|
|
}; |
|
|
}); |
|
|
|
|
|
|
|
|
const FlickerList: React.FC<{ |
|
|
const FlickerList: React.FC<{ |
|
|
list: { text: string; description: string }[]; |
|
|
list: { text: string; description: React.ReactNode }[]; |
|
|
style?: React.CSSProperties; |
|
|
style?: React.CSSProperties; |
|
|
}> = ({ list, style }) => { |
|
|
className?: string; |
|
|
|
|
|
}> = ({ list, style, className }) => { |
|
|
return ( |
|
|
return ( |
|
|
<ul |
|
|
<ul |
|
|
style={style} |
|
|
style={style} |
|
|
className={css` |
|
|
className={cx( |
|
|
display: flex; |
|
|
className, |
|
|
flex-wrap: wrap; |
|
|
css` |
|
|
gap: 0.5rem; |
|
|
display: flex; |
|
|
margin: 0; |
|
|
flex-wrap: wrap; |
|
|
padding: 0; |
|
|
gap: 0.8rem; |
|
|
list-style: none; |
|
|
margin: 0; |
|
|
|
|
|
padding: 0; |
|
|
&:has(> li button:focus) li button:not(:focus), |
|
|
list-style: none; |
|
|
&:has(> li button:hover) li button:not(:hover) { |
|
|
|
|
|
opacity: 0.5; |
|
|
&:has(:focus) li button:not(:focus), |
|
|
} |
|
|
&:has(:hover) li button:not(:hover) { |
|
|
|
|
|
opacity: 0.5; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/* any button that has a subsequent button focused, hide its tooltip */ |
|
|
/* any button that has a subsequent button focused, hide its tooltip */ |
|
|
&&&& li:has(~ li button:focus) .tooltip, |
|
|
&&&& li:has(~ li button:focus) .tooltip, |
|
|
/* any button that has a previous button focused, hide its tooltip */ |
|
|
/* any button that has a previous button focused, hide its tooltip */ |
|
|
&&&& li:has(button:focus) ~ li .tooltip { |
|
|
&&&& li:has(button:focus) ~ li .tooltip { |
|
|
opacity: 0; |
|
|
opacity: 0; |
|
|
} |
|
|
} |
|
|
`}>
|
|
|
`,
|
|
|
|
|
|
)}> |
|
|
{[ |
|
|
{[ |
|
|
...intersperse( |
|
|
...intersperse( |
|
|
list.map((item, index) => ( |
|
|
list.map((item, index) => ( |
|
@ -156,9 +184,32 @@ const FlickerList: React.FC<{ |
|
|
className={css` |
|
|
className={css` |
|
|
display: inline-block; |
|
|
display: inline-block; |
|
|
`}>
|
|
|
`}>
|
|
|
<Flicker index={index} description={item.description}> |
|
|
<Tooltip |
|
|
|
|
|
description={item.description} |
|
|
|
|
|
ref={async el => { |
|
|
|
|
|
if (!el) return; |
|
|
|
|
|
|
|
|
|
|
|
el.classList.add(invisible); |
|
|
|
|
|
|
|
|
|
|
|
await sleep(500); |
|
|
|
|
|
await sleep(300 * index); |
|
|
|
|
|
el.classList.add(opaque); |
|
|
|
|
|
|
|
|
|
|
|
await sleep(1000 + Math.random() * 1000); |
|
|
|
|
|
tripleBlink(el); |
|
|
|
|
|
|
|
|
|
|
|
while (true) { |
|
|
|
|
|
await sleep(5000 + Math.random() * 10000); |
|
|
|
|
|
const chosen = |
|
|
|
|
|
opacities[Math.floor(Math.random() * opacities.length)]; |
|
|
|
|
|
el.classList.add(chosen); |
|
|
|
|
|
await sleep(2000); |
|
|
|
|
|
el.classList.remove(chosen); |
|
|
|
|
|
tripleBlink(el); |
|
|
|
|
|
} |
|
|
|
|
|
}}> |
|
|
{item.text} |
|
|
{item.text} |
|
|
</Flicker> |
|
|
</Tooltip> |
|
|
</li> |
|
|
</li> |
|
|
)), |
|
|
)), |
|
|
index => <li key={index}>·</li>, |
|
|
index => <li key={index}>·</li>, |
|
|