1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
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>
)}
</>
)
}
|