You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
5.6 KiB
TypeScript
141 lines
5.6 KiB
TypeScript
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
|
|
import Link from 'next/link'
|
|
|
|
import styles from '~/styles/index.module.css'
|
|
import sharp from 'sharp'
|
|
|
|
const mimes = {
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
png: 'image/png',
|
|
bmp: 'image/bmp',
|
|
gif: 'image/gif'
|
|
}
|
|
|
|
export default async function Index() {
|
|
const buttonDir = 'images/buttons/'
|
|
const buttonFiles = (await fs.readdir(path.join(process.cwd(), buttonDir)).catch(() => {}) || [])
|
|
.filter(buttonName => buttonName.match(/\.(png|gif|jpg|jpeg)$/))
|
|
|
|
const buttonLinks = (await fs.readFile(path.join(process.cwd(), 'images/buttons/urls.txt'), {encoding: 'utf8'}).catch(() => {}) || '')
|
|
.split('\n').filter(line => !!line)
|
|
.map(line => line.match(/^([^:]+):\s+(.*)$/))
|
|
.filter(match => !!match)
|
|
.reduce((acc,[_, name, url]) => ({...acc, [name]: url}), {})
|
|
|
|
const buttons = await Promise.all(buttonFiles.map(async buttonFile => {
|
|
const origImageData = await fs.readFile(path.join(process.cwd(), buttonDir, buttonFile))
|
|
let preview = null
|
|
|
|
const metadata = await sharp(origImageData, {pages: -1}).metadata()
|
|
|
|
// Generate preview if there's more than one frame
|
|
if(metadata.pages > 1) {
|
|
preview = await sharp(origImageData, {pages: 1})
|
|
.png({quality: 80, force: true})
|
|
.toBuffer()
|
|
}
|
|
|
|
return {
|
|
name: buttonFile.split('.').slice(0, -1).join('.'),
|
|
mime: mimes[buttonFile.split('.').at(-1)] || 'image/*',
|
|
url: buttonLinks[buttonFile],
|
|
data: origImageData.toString('base64'),
|
|
preview: preview?.toString('base64')
|
|
}
|
|
}))
|
|
|
|
const friendButtons = buttons.filter(button => button.name !== 'tempest')
|
|
const ourButton = buttons.filter(button => button.name === 'tempest')[0]
|
|
|
|
return (
|
|
<>
|
|
<main className="mainColumn card">
|
|
<p>
|
|
Hi, we're tempest! We're a median plural
|
|
system of six members, but most of the time you'll probably see us
|
|
operating as one
|
|
</p>
|
|
|
|
<p>We like coding, VR, and making CG art</p>
|
|
|
|
<h2>At a glance</h2>
|
|
<div className={styles.glance}>
|
|
<span className={styles.label}>Pronouns:</span>
|
|
<span>they/it</span>
|
|
|
|
<span className={styles.label}>Cohort:</span>
|
|
<span>millenial</span>
|
|
|
|
<span className={styles.label}>Orientation:</span>
|
|
<span>ace . . . ish</span>
|
|
|
|
<span className={styles.label}>Partners:</span>
|
|
<span>several</span>
|
|
|
|
<span className={styles.label}>Children:</span>
|
|
<span>two</span>
|
|
|
|
<span className={styles.label}>Capitalize name:</span>
|
|
<span>not unless we're at work</span>
|
|
</div>
|
|
|
|
<p>
|
|
<em>Note:</em> This is the information for our system in aggregate,
|
|
for individual info see our <Link href="/about">about</Link> page
|
|
</p>
|
|
</main>
|
|
{friendButtons.length > 0 && (
|
|
<div className="mainColumn postscript">
|
|
<h2>Friends and other neighbors:
|
|
{ourButton && <a target="_blank" href={`data:${ourButton.mime};base64,${ourButton.data}`} className="asideLink" aria-label="Link back to us">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentcolor" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
|
|
</a>}
|
|
</h2>
|
|
<div className={styles.the88x31s}>
|
|
{friendButtons.map(button => {
|
|
const image = <img
|
|
data-button-canonical
|
|
alt={button.name}
|
|
title={button.name}
|
|
src={`data:${button.mime};base64,${button.data}`}
|
|
/>
|
|
|
|
const preview = button.preview ? <img
|
|
data-button-preview
|
|
alt={button.name}
|
|
title={button.name}
|
|
src={`data:image/png;base64,${button.preview}`}
|
|
/> : null
|
|
|
|
if(button.url)
|
|
return (
|
|
<a target="_blank" rel="noopener" key={button.name} href={button.url}>{image}{preview}</a>
|
|
)
|
|
else
|
|
return (
|
|
<a key={button.name}>{image}{preview}</a>
|
|
)
|
|
})}
|
|
<script type="text/javascript" dangerouslySetInnerHTML={{__html: `
|
|
const buttons = document.currentScript.parentElement.querySelectorAll('a:has([data-button-preview])')
|
|
|
|
// Slightly janky way of resetting GIF animations after they go back to the preview state
|
|
buttons.forEach(button => button.addEventListener('mouseleave', () => {
|
|
const image = button.querySelector('[data-button-canonical]')
|
|
const src = image.getAttribute('src')
|
|
image.setAttribute('src', '')
|
|
setTimeout(() => {
|
|
image.setAttribute('src', src)
|
|
}, 0)
|
|
}))
|
|
`}}/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|