|
@ -1,4 +1,4 @@ |
|
|
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"; |
|
|
|
|
|
|
|
@ -39,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) => { |
|
@ -58,12 +61,14 @@ const tripleBlink = async (el: HTMLElement) => { |
|
|
el.classList.remove(halfVisible); |
|
|
el.classList.remove(halfVisible); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const Flicker: React.FC<{ |
|
|
export const Tooltip = forwardRef< |
|
|
|
|
|
HTMLButtonElement, |
|
|
|
|
|
{ |
|
|
children: React.ReactNode; |
|
|
children: React.ReactNode; |
|
|
index: number; |
|
|
description: React.ReactNode; |
|
|
description: string; |
|
|
|
|
|
style?: React.CSSProperties; |
|
|
style?: React.CSSProperties; |
|
|
}> = ({ children, index, description, style }) => { |
|
|
} |
|
|
|
|
|
>(({ children, description, style }, ref) => { |
|
|
return ( |
|
|
return ( |
|
|
<span |
|
|
<span |
|
|
style={style} |
|
|
style={style} |
|
@ -71,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 |
|
@ -118,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; |
|
@ -143,14 +122,31 @@ const Flicker: React.FC<{ |
|
|
} |
|
|
} |
|
|
`,
|
|
|
`,
|
|
|
)}> |
|
|
)}> |
|
|
|
|
|
<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} |
|
|
{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 }) => { |
|
|
}> = ({ list, style }) => { |
|
|
return ( |
|
|
return ( |
|
@ -159,13 +155,13 @@ const FlickerList: React.FC<{ |
|
|
className={css` |
|
|
className={css` |
|
|
display: flex; |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
flex-wrap: wrap; |
|
|
gap: 0.5rem; |
|
|
gap: 0.8rem; |
|
|
margin: 0; |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
padding: 0; |
|
|
list-style: none; |
|
|
list-style: none; |
|
|
|
|
|
|
|
|
&:has(> li button:focus) li button:not(:focus), |
|
|
&:has(:focus) li button:not(:focus), |
|
|
&:has(> li button:hover) li button:not(:hover) { |
|
|
&:has(:hover) li button:not(:hover) { |
|
|
opacity: 0.5; |
|
|
opacity: 0.5; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -184,9 +180,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>, |
|
|