@@ -82,10 +87,21 @@ export function MasonryGrid({ children }: MasonryGridProps) {
);
}
-function clearCardPlacement(card: HTMLElement) {
+function clearCardLayout(card: HTMLElement) {
card.style.removeProperty("grid-column-start");
card.style.removeProperty("grid-row-start");
card.style.removeProperty("grid-row-end");
+ card.style.removeProperty("--artifact-offset-x");
+ card.style.removeProperty("--artifact-offset-y");
+}
+
+function setArtifactOffset(card: HTMLElement, seed: string, index: number) {
+ const hash = hashString(`${seed}:artifact:${index}`);
+ const horizontalOffset = (hash % 17) - 8;
+ const verticalOffset = ((hash >>> 5) % 13) - 6;
+
+ card.style.setProperty("--artifact-offset-x", `${horizontalOffset}px`);
+ card.style.setProperty("--artifact-offset-y", `${verticalOffset}px`);
}
function getColumnCount(gridTemplateColumns: string): number {
diff --git a/src/components/profile-artifact.tsx b/src/components/profile-artifact.tsx
new file mode 100644
index 0000000..61b66a1
--- /dev/null
+++ b/src/components/profile-artifact.tsx
@@ -0,0 +1,36 @@
+import type { ReactNode } from "react";
+
+type ProfileArtifactProps = {
+ children: ReactNode;
+ kind?: "sigil";
+ source?: {
+ href: string;
+ name: string;
+ };
+};
+
+export function ProfileArtifact({
+ children,
+ kind,
+ source,
+}: ProfileArtifactProps) {
+ return (
+
+ {children}
+ {source && (
+
+ )}
+
+ );
+}
diff --git a/src/components/profile-section.tsx b/src/components/profile-section.tsx
deleted file mode 100644
index 0a5b803..0000000
--- a/src/components/profile-section.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { ReactNode } from "react";
-
-type ProfileSectionProps = {
- children: ReactNode;
- source?: {
- href: string;
- name: string;
- };
- title: string;
-};
-
-export function ProfileSection({
- title,
- source,
- children,
-}: ProfileSectionProps) {
- return (
-
- {source ? (
-
- ) : (
- {title}
- )}
- {children}
- {source && (
-
- )}
-
- );
-}
diff --git a/src/components/profile-sections.tsx b/src/components/profile-sections.tsx
index ac2dbe8..77c6ed5 100644
--- a/src/components/profile-sections.tsx
+++ b/src/components/profile-sections.tsx
@@ -1,11 +1,10 @@
-import type { ReactNode } from "react";
import { useEffect, useState } from "react";
-import { GrainPhotos } from "~/components/grain-photos";
-import { ProfileSection } from "~/components/profile-section";
-import { RockskyScrobbleList } from "~/components/rocksky-scrobble-list";
-import { StandardDocuments } from "~/components/standard-documents";
-import { TangledRepoList } from "~/components/tangled-repo-list";
+import { GrainPhotoArtifact } from "~/components/grain-photos";
+import { ProfileArtifact } from "~/components/profile-artifact";
+import { RockskyScrobble } from "~/components/rocksky-scrobble-list";
+import { StandardDocumentArtifact } from "~/components/standard-documents";
+import { TangledRepoArtifact } from "~/components/tangled-repo-list";
import { getDidSectionOrder } from "~/lib/layout/did-section-order";
import { getRecentGrainPhotos } from "~/lib/providers/grain-photo";
import { getRecentRocksky } from "~/lib/providers/rocksky";
@@ -53,33 +52,41 @@ export function ProfileSections({ did, handle, pds }: ProfileSectionsProps) {
if (!data) {
return (
-
+
+ Gathering signals
Searching the Atmosphere…
-
+
);
}
- const sections: ReactNode[] = [];
-
- if (data.scrobbles.length > 0) {
- sections.push(
-
,
- );
- }
-
- if (data.tangledRepos.length > 0) {
- sections.push(
);
- }
-
- if (data.documents.length > 0) {
- sections.push(
-
,
- );
- }
-
- if (data.photos.length > 0) {
- sections.push(
);
- }
-
- return <>{getDidSectionOrder(did, sections)}>;
+ const artifacts = [
+ ...data.scrobbles.map((scrobble) => (
+
+ )),
+ ...data.tangledRepos.map((repo) => (
+
+ )),
+ ...data.documents.map((document) => (
+
+ )),
+ ...data.photos.map((photo) => (
+
+ )),
+ ];
+
+ return <>{getDidSectionOrder(did, artifacts)}>;
}
diff --git a/src/components/profile-sigil-artifact.tsx b/src/components/profile-sigil-artifact.tsx
new file mode 100644
index 0000000..f8b796c
--- /dev/null
+++ b/src/components/profile-sigil-artifact.tsx
@@ -0,0 +1,14 @@
+import { HauntSigil } from "~/components/haunt-sigil";
+import { ProfileArtifact } from "~/components/profile-artifact";
+
+export function ProfileSigilArtifact({ did }: { did: string }) {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/rocksky-scrobble-list.tsx b/src/components/rocksky-scrobble-list.tsx
index 20077f8..fe40e8f 100644
--- a/src/components/rocksky-scrobble-list.tsx
+++ b/src/components/rocksky-scrobble-list.tsx
@@ -1,38 +1,11 @@
import type { RockskyScrobbleRecord } from "~/lib/providers/rocksky";
-import { ProfileSection } from "~/components/profile-section";
-
-type RockskyScrobbleListProps = {
- scrobbles: RockskyScrobbleRecord[];
-};
+import { ProfileArtifact } from "~/components/profile-artifact";
type RockskyScrobbleProps = Pick<
RockskyScrobbleRecord,
"album" | "albumArtUrl" | "artist" | "createdAt" | "spotifyLink" | "title"
>;
-export function RockskyScrobbleList({ scrobbles }: RockskyScrobbleListProps) {
- return (
-
-
- {scrobbles.map((scrobble) => (
-
- ))}
-
-
- );
-}
-
function formatRelativeTime(createdAt: string): string {
const elapsedMinutes = Math.round(
(Date.now() - new Date(createdAt).getTime()) / 60000,
@@ -47,7 +20,7 @@ function formatRelativeTime(createdAt: string): string {
return `${Math.round(elapsedHours / 24)} days ago`;
}
-function RockskyScrobble({
+export function RockskyScrobble({
title,
artist,
album,
@@ -56,34 +29,33 @@ function RockskyScrobble({
albumArtUrl,
}: RockskyScrobbleProps) {
return (
-
- {albumArtUrl ? (
-
- ) : (
-
- )}
-
-
-
{title}
-
- {artist} · {album}
-
-
-
-
- {formatRelativeTime(createdAt)}
-
-
+
+
+ {albumArtUrl ? (
+
+ ) : (
+
+ )}
+
+
+
{title}
+
+ {artist} · {album}
+
+
+
+ {formatRelativeTime(createdAt)}
+
);
}
diff --git a/src/components/standard-documents.tsx b/src/components/standard-documents.tsx
index c1b6d66..307622d 100644
--- a/src/components/standard-documents.tsx
+++ b/src/components/standard-documents.tsx
@@ -1,51 +1,42 @@
import type { StandardDocument } from "~/lib/providers/standard-site";
-import { ProfileSection } from "~/components/profile-section";
-type StandardDocumentsProps = {
- documents: StandardDocument[];
-};
-
-export function StandardDocuments({ documents }: StandardDocumentsProps) {
+import { ProfileArtifact } from "~/components/profile-artifact";
+export function StandardDocumentArtifact({
+ document,
+}: {
+ document: StandardDocument;
+}) {
return (
-
-
-
+ {document.publishedAt && (
+
+ )}
+
);
}
diff --git a/src/components/tangled-repo-list.tsx b/src/components/tangled-repo-list.tsx
index 66a8dec..40e57ea 100644
--- a/src/components/tangled-repo-list.tsx
+++ b/src/components/tangled-repo-list.tsx
@@ -1,46 +1,30 @@
import type { ReactNode } from "react";
import type { TangledRepo } from "~/lib/providers/tangled";
-import { ProfileSection } from "~/components/profile-section";
+import { ProfileArtifact } from "~/components/profile-artifact";
-type TangledRepoListProps = {
- repos: TangledRepo[];
-};
-
-export function TangledRepoList({ repos }: TangledRepoListProps) {
+export function TangledRepoArtifact({ repo }: { repo: TangledRepo }) {
return (
-
-
- {repos.map((repo) => (
-
+
-
+
+ {renderMarkdownLinks(repo.description ?? "No description")}
+
+
);
}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 414b53f..b78cffc 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -299,6 +299,13 @@ body {
grid-auto-rows: 0.5rem;
}
+.profile-grid-masonry > * {
+ transform: translate(
+ var(--artifact-offset-x, 0),
+ var(--artifact-offset-y, 0)
+ );
+}
+
.profile-card {
background-color: var(--profile-card);
border-color: color-mix(in srgb, var(--profile-border) 58%, transparent);
@@ -338,20 +345,20 @@ body {
}
.profile-identity-header {
- --profile-sigil-size: 2.75rem;
-
display: grid;
- grid-template-columns: 4rem minmax(0, 1fr) var(--profile-sigil-size);
+ grid-template-columns: 4rem minmax(0, 1fr);
align-items: start;
gap: 0.875rem;
}
-.profile-sigil {
- height: var(--profile-sigil-size);
- justify-self: end;
- margin-top: 0.125rem;
- opacity: 0.72;
- width: var(--profile-sigil-size);
+.profile-artifact--sigil {
+ display: grid;
+ place-items: center;
+}
+
+.profile-sigil-artifact {
+ height: clamp(8rem, 18vw, 10rem);
+ width: clamp(8rem, 18vw, 10rem);
}
.profile-name {
@@ -361,12 +368,6 @@ body {
line-height: 1.05;
}
-.profile-section-title {
- font-size: 1rem;
- font-weight: 650;
- line-height: 1.3;
-}
-
.profile-source-link {
font-size: 0.75rem;
line-height: 1.4;
@@ -377,6 +378,13 @@ body {
text-underline-offset: 0.18em;
}
+.profile-artifact-source {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 1rem;
+ padding-top: 0.75rem;
+}
+
.profile-source-link:hover,
.profile-source-link:focus-visible {
opacity: 0.82;
@@ -411,15 +419,6 @@ body {
opacity: 0.62;
}
-@container (max-width: 21rem) {
- .profile-identity-header {
- --profile-sigil-size: 2.25rem;
-
- gap: 0.75rem;
- }
-}
-
-
@keyframes atmosphere-drift {
to {
background-position: 100% 100%;