summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/about/[name]/page.tsx2
-rw-r--r--app/contact/page.tsx2
-rw-r--r--app/not-found.tsx2
-rw-r--r--app/page.tsx153
-rw-r--r--app/pay-transparency/page.tsx2
-rw-r--r--app/posts/[slug]/page.tsx2
-rw-r--r--app/posts/page.tsx2
-rw-r--r--styles/index.module.css39
-rw-r--r--styles/layout.css43
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:&nbsp;</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;
 }