diff options
-rw-r--r-- | app/about/[name]/page.tsx | 2 | ||||
-rw-r--r-- | app/contact/page.tsx | 2 | ||||
-rw-r--r-- | app/not-found.tsx | 2 | ||||
-rw-r--r-- | app/page.tsx | 153 | ||||
-rw-r--r-- | app/pay-transparency/page.tsx | 2 | ||||
-rw-r--r-- | app/posts/[slug]/page.tsx | 2 | ||||
-rw-r--r-- | app/posts/page.tsx | 2 | ||||
-rw-r--r-- | styles/index.module.css | 39 | ||||
-rw-r--r-- | styles/layout.css | 43 |
9 files changed, 206 insertions, 41 deletions
diff --git a/app/about/[name]/page.tsx b/app/about/[name]/page.tsx index afdbe29..9176572 100644 --- a/app/about/[name]/page.tsx +++ b/app/about/[name]/page.tsx @@ -19,7 +19,7 @@ export default function MemberPage({ params: { name } }) { return ( <> - <main className="mainColumn"> + <main className="mainColumn card"> <InfoBar memberName={member.name} /> <Markdown outer="short">{member.bioShort}</Markdown> <Markdown outer="long">{member.bioContinued}</Markdown> diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 6af8ad1..e75ab1f 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -8,7 +8,7 @@ export default function Contact() { <h1 className="pageTitle"> Contact </h1> - <main className="mainColumn"> + <main className="mainColumn card"> <InfoBar> Be nice. Please don't make us regret putting this here </InfoBar> diff --git a/app/not-found.tsx b/app/not-found.tsx index b3a0fd4..35861b5 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -4,7 +4,7 @@ export default function NotFound() { return ( <> <h1 className="pageTitle">Not Found (404)</h1> - <main className="mainColumn"> + <main className="mainColumn card"> <InfoBar> <strong>Error: </strong> Unable to find the requested resource diff --git a/app/page.tsx b/app/page.tsx index 507f1d7..229a803 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,43 +1,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] -export default function Index() { return ( - <main className="mainColumn"> - <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> + <> + <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> - <p>We like coding, VR, and making CG art</p> + <span className={styles.label}>Orientation:</span> + <span>ace . . . ish</span> - <h2>At a glance</h2> - <div className={styles.glance}> - <span className={styles.label}>Pronouns:</span> - <span>they/it</span> + <span className={styles.label}>Partners:</span> + <span>several</span> - <span className={styles.label}>Cohort:</span> - <span>millenial</span> + <span className={styles.label}>Children:</span> + <span>two</span> - <span className={styles.label}>Orientation:</span> - <span>ace . . . ish</span> + <span className={styles.label}>Capitalize name:</span> + <span>not unless we're at work</span> + </div> - <span className={styles.label}>Partners:</span> - <span>several</span> + <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}`} + /> - <span className={styles.label}>Children:</span> - <span>two</span> + const preview = button.preview ? <img + data-button-preview + alt={button.name} + title={button.name} + src={`data:image/png;base64,${button.preview}`} + /> : null - <span className={styles.label}>Capitalize name:</span> - <span>not unless we're at work</span> - </div> + 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])') - <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> + // 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> + )} + </> ) } diff --git a/app/pay-transparency/page.tsx b/app/pay-transparency/page.tsx index feeff47..45320d0 100644 --- a/app/pay-transparency/page.tsx +++ b/app/pay-transparency/page.tsx @@ -8,7 +8,7 @@ export default function PayTransparency() { <h1 className="pageTitle"> Pay Transparency </h1> - <main className="mainColumn"> + <main className="mainColumn card"> <InfoBar text="Last updated: November 2023" /> <p> This page lists the title and pay rate of every job we have held diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx index 2110a35..03df660 100644 --- a/app/posts/[slug]/page.tsx +++ b/app/posts/[slug]/page.tsx @@ -18,7 +18,7 @@ export default async function Post({ params: { slug } }) { <h1 className="pageTitle"> {post.title} </h1> - <main className="mainColumn"> + <main className="mainColumn card"> <InfoBar authorName={post.author} publishedDate={post.date} /> <Markdown>{post.body}</Markdown> </main> diff --git a/app/posts/page.tsx b/app/posts/page.tsx index e713259..a53772f 100644 --- a/app/posts/page.tsx +++ b/app/posts/page.tsx @@ -14,7 +14,7 @@ export default async function Posts() { </h1> <main> {sortedPosts.map((post: Post) => ( - <div className="mainColumn"> + <div className="mainColumn card"> <InfoBar authorName={post.author} publishedDate={post.date} /> <h2>{post.title}</h2> <p>{post.subtitle}</p> diff --git a/styles/index.module.css b/styles/index.module.css index 2314363..582cf74 100644 --- a/styles/index.module.css +++ b/styles/index.module.css @@ -9,3 +9,42 @@ color: var(--text-dimmed); margin-right: var(--text-padding); } + +.the88x31s { + column-width: 90px; + text-align: center; +} + +.the88x31s > a { + width: 88px; + height: 31px; + margin: 2px; + display: inline-block; +} + +.the88x31s img { + width: inherit; + height: inherit; + overflow: hidden; + object-fit: cover; + filter: grayscale(1) brightness(.8) opacity(.8); + transition: .4s ease-in-out filter; +} + +.the88x31s a:has(img + img) img:first-child { + position: absolute; + display: none; + z-index: 2; +} + +.the88x31s a:hover img { + display: initial!important; + filter: none; + transition: .025s ease-in-out filter; +} + +@media (pointer:coarse) or (pointer:none) { + .the88x31s img { + filter: contrast(.6) brightness(.6) grayscale(.2); + } +} diff --git a/styles/layout.css b/styles/layout.css index abc709a..a797740 100644 --- a/styles/layout.css +++ b/styles/layout.css @@ -13,6 +13,7 @@ --text-dimmed:rgba(255,255,255, .55); --text-padding: 16px; --card-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12); + --current-content-width: min(var(--main-width), calc(100vw - var(--text-padding))); } @media (max-width: 500px) { @@ -166,29 +167,57 @@ header:not(.homepage) ~ h1.pageTitle { margin-top: -80px; } -main.mainColumn { - min-height: calc(var(--header-overlap) + var(--min-main-overhang)); -} - .mainColumn { width: var(--main-width); max-width: calc(100vw - var(--text-padding)); box-sizing: border-box; padding: var(--text-padding); margin: 0 auto; +} + +.mainColumn.card { + min-height: calc(var(--header-overlap) + var(--min-main-overhang)); background: var(--main-background); box-shadow: var(--card-shadow); } -header.homepage ~ .mainColumn { +.mainColumn.postscript h2:first-child { + margin-top: 24px; + font-weight: 500; + opacity: .6; + font-size: 18px; + position: relative; +} + +.mainColumn.postscript h2:first-child::after { + content: ' '; + position: absolute; + display: block; + background: #666; + width: 100%; + height: 1px; +} + +.mainColumn.postscript h2 a.asideLink { + position: absolute; + right: 0; + width: 20px; + height: 20px; +} + +.mainColumn.postscript p { + opacity: .6; +} + +header.homepage ~ .mainColumn.card { min-height: calc(var(--header-overlap) + var(--min-main-overhang) - var(--header-bar-height)); } -.mainColumn ~ .mainColumn { +.mainColumn.card ~ .mainColumn.card { margin-top: calc(2 * var(--text-padding)); } -main.mainColumn > p:first-child { +main.mainColumn.card > p:first-child { margin-top: 0; } |