-
-
+
+
{latest && }
@@ -108,9 +108,9 @@ export default function Epics({ loaderData }: Route.ComponentProps) {
title="Epics"
facts={
<>
-
-
-
+
+
+
>
}
/>
diff --git a/site/app/routes/member-changelog.tsx b/site/app/routes/member-changelog.tsx
index 89e5184..20b265c 100644
--- a/site/app/routes/member-changelog.tsx
+++ b/site/app/routes/member-changelog.tsx
@@ -5,6 +5,7 @@ import { Page } from '../components/Shell';
import { Link } from 'react-router';
import type { Route } from './+types/member-changelog';
import Timeline from '../components/Timeline';
+import { plural } from '../format';
import { found, modelFrom, useHub } from '../hub';
import { changelogPath } from '../model';
import { topicsFor } from '../topics';
@@ -38,8 +39,8 @@ export default function MemberChangelog({ loaderData }: Route.ComponentProps) {
actions={
profile }
facts={
<>
-
-
+
+
{releases[0] &&
}
>
}
diff --git a/site/app/routes/members.tsx b/site/app/routes/members.tsx
index 18f6938..4dd0523 100644
--- a/site/app/routes/members.tsx
+++ b/site/app/routes/members.tsx
@@ -6,6 +6,7 @@ import { Fact, loadRail, PageHeader, StandardRail } from '../components/kit/page
import { Page } from '../components/Shell';
import TagList from '../components/TagList';
import { hubContext } from '../context';
+import { plural } from '../format';
import { useHub } from '../hub';
import { useOnline } from '../live';
import { useSpotlight, useTilt } from '../motion/motion';
@@ -61,14 +62,14 @@ function Card({
{member.bio &&
{member.bio}
}
- {projects} {projects === 1 ? 'project' : 'projects'}
+ {projects} {plural(projects, 'project')}
- {releases} {releases === 1 ? 'release' : 'releases'}
+ {releases} {plural(releases, 'release')}
{member.followers !== null && (
- {member.followers} followers
+ {member.followers} {plural(member.followers, 'follower')}
)}
{member.joined && (
@@ -112,10 +113,13 @@ export default function Members({ loaderData }: Route.ComponentProps) {
title="Members"
facts={
<>
-
-
-
-
+
+
+
+
>
}
/>
diff --git a/site/app/routes/messages.tsx b/site/app/routes/messages.tsx
index 0184b05..158cacf 100644
--- a/site/app/routes/messages.tsx
+++ b/site/app/routes/messages.tsx
@@ -100,13 +100,20 @@ function Leave() {
) : (
-
+
)}
-
- {wall.length === 0 && nothing on the wall yet
}
- {wall.map((message) => (
-
- ))}
-
+ {wall.length === 0 ? (
+ nothing on the wall yet
+ ) : (
+
+ {wall.map((message) => (
+
+ ))}
+
+ )}
);
}
diff --git a/site/app/routes/post.tsx b/site/app/routes/post.tsx
index 35777e2..dee833d 100644
--- a/site/app/routes/post.tsx
+++ b/site/app/routes/post.tsx
@@ -9,7 +9,7 @@ import { SubscribePanel } from '../components/kit/subscribe';
import { Page } from '../components/Shell';
import TagList from '../components/TagList';
import { hubContext } from '../context';
-import { day } from '../format';
+import { day, plural } from '../format';
import { useHub, useRoot } from '../hub';
import { useView, useViews } from '../live';
import { createModel } from '../model';
@@ -203,7 +203,7 @@ export default function Post({ loaderData }: Route.ComponentProps) {
/>
)}
- {views > 0 && }
+ {views > 0 && }
>
}
>
diff --git a/site/app/routes/project-changelog.tsx b/site/app/routes/project-changelog.tsx
index 4d972ff..cfdabf5 100644
--- a/site/app/routes/project-changelog.tsx
+++ b/site/app/routes/project-changelog.tsx
@@ -7,7 +7,7 @@ import type { Route } from './+types/project-changelog';
import Status from '../components/Status';
import TagList from '../components/TagList';
import Timeline from '../components/Timeline';
-import { day } from '../format';
+import { day, plural } from '../format';
import { found, modelFrom, useHub } from '../hub';
import { changelogPath } from '../model';
import { topicsFor } from '../topics';
@@ -63,7 +63,7 @@ export default function ProjectChangelog({ loaderData }: Route.ComponentProps) {
}
facts={
<>
-
+
{releases[0] && }
{first && releases.length > 1 && }
{owner && (
diff --git a/site/app/routes/projects.tsx b/site/app/routes/projects.tsx
index 1c52449..180e2c2 100644
--- a/site/app/routes/projects.tsx
+++ b/site/app/routes/projects.tsx
@@ -7,7 +7,7 @@ import { Fact, loadRail, PageHeader, StandardRail } from '../components/kit/page
import Status from '../components/Status';
import { Page } from '../components/Shell';
import { hubContext } from '../context';
-import { day } from '../format';
+import { day, plural } from '../format';
import { useHub } from '../hub';
import { STATUS_LABEL } from '../model';
import { useSpotlight, useTilt } from '../motion/motion';
@@ -89,7 +89,7 @@ function Entry({
<>
v{latest.version} {day(latest.date)}
- {releases.length} {releases.length === 1 ? 'release' : 'releases'}
+ {releases.length} {plural(releases.length, 'release')}
>
) : (
@@ -147,7 +147,7 @@ export default function Projects({ loaderData }: Route.ComponentProps) {
a.name.localeCompare(b.name),
);
const slugs = new Set(all.map((project) => project.slug));
- const latest = hub.releases.find((release) => slugs.has(release.project));
+ const released = hub.releases.filter((release) => slugs.has(release.project));
const pickTag = (item: string) => setTag((current) => (current === item ? null : item));
return (
@@ -168,12 +168,9 @@ export default function Projects({ loaderData }: Route.ComponentProps) {
}
facts={
<>
-
- slugs.has(r.project)).length}
- label="releases"
- />
- {latest && }
+
+
+ {released[0] && }
{tag && (
setTag(null)}>
{tag} ×
diff --git a/site/app/routes/search.tsx b/site/app/routes/search.tsx
index 5c01937..f5b16ec 100644
--- a/site/app/routes/search.tsx
+++ b/site/app/routes/search.tsx
@@ -7,7 +7,7 @@ import { Fact, PageHeader } from '../components/kit/page';
import Status from '../components/Status';
import { Page } from '../components/Shell';
import { hubContext } from '../context';
-import { day } from '../format';
+import { day, plural } from '../format';
import { useHub, useRoot } from '../hub';
import { changelogPath } from '../model';
import { search, type Hit } from '../search';
@@ -136,13 +136,16 @@ export default function Search({ loaderData }: Route.ComponentProps) {
}
facts={
query.trim() ? (
-
+
) : (
<>
-
-
-
-
+
+
+
+
>
)
}
diff --git a/site/app/routes/subscribe.tsx b/site/app/routes/subscribe.tsx
index 186c212..3025d5e 100644
--- a/site/app/routes/subscribe.tsx
+++ b/site/app/routes/subscribe.tsx
@@ -8,6 +8,7 @@ import { PostRow } from '../components/kit/posts';
import { Chooser, SubscribeForm } from '../components/kit/subscribe';
import { Page } from '../components/Shell';
import { hubContext } from '../context';
+import { plural } from '../format';
import { useHub } from '../hub';
import { isCadence, knownScope, parseScope, type Cadence, type Scope } from '../subscribe';
import { topicsFor } from '../topics';
@@ -88,9 +89,11 @@ export default function Subscribe({ loaderData }: Route.ComponentProps) {
}
facts={
<>
-
-
-
+ {loaderData.readers > 0 && }
+ {hub.posts.length > 0 && (
+
+ )}
+ {writers > 0 && }
>
}
/>
diff --git a/site/app/share.ts b/site/app/share.ts
index 7538872..01af17b 100644
--- a/site/app/share.ts
+++ b/site/app/share.ts
@@ -1,4 +1,4 @@
-import type { Model } from './model';
+import { STATUS_LABEL, type Model } from './model.ts';
export interface ShareCard {
label: string;
@@ -15,6 +15,9 @@ const FIXED: Record = {
'/status': { label: 'status', title: 'What is running right now' },
'/messages': { label: 'messages', title: 'Leave a message for the members' },
'/blog': { label: 'blog', title: 'Writing by the members' },
+ '/subscribe': { label: 'subscribe', title: 'New writing, by email' },
+ '/search': { label: 'search', title: 'Members, projects, posts and releases' },
+ '/privacy': { label: 'privacy', title: 'What this site keeps' },
};
export function shareCard(
@@ -28,7 +31,7 @@ export function shareCard(
return {
label: 'project-graphite.com',
title: headline,
- subtitle: `${model.members.length} members · ${model.projects.length} projects`,
+ subtitle: `${model.members.length} members · ${model.rootProjects.length} projects`,
accent: accents[0],
};
}
@@ -66,7 +69,8 @@ export function shareCard(
}
if (project) {
return {
- label: project.url?.replace('https://', '') ?? `${project.slug} · source only`,
+ label:
+ project.url?.replace('https://', '') ?? `${project.slug} · ${STATUS_LABEL[project.status]}`,
title: project.name,
subtitle: project.summary,
accent: model.memberByHandle(project.owner)?.accent ?? accents[0],
From dc099e0e033afa158eb1fb5fee831fb69f9981d0 Mon Sep 17 00:00:00 2001
From: Amr Mohammed El-Sheraey <141947355+AmrMsCLL@users.noreply.github.com>
Date: Sat, 7 Nov 2026 03:33:19 +0200
Subject: [PATCH 4/4] fix(motion): let the orbit obey site.motion; drop
orphaned components and styles
The orbit spun from CSS whatever the orbit setting said; it now stands still when the piece is off. ProjectCard, EpicCard, the .film-* rules and the .cover span rules were referenced by nothing.
---
site/app/components/kit/members.tsx | 8 +-
site/app/styles/graphite.css | 103 +-------------------------
site/app/styles/pages/blog.css | 3 -
site/app/styles/pages/directories.css | 3 -
4 files changed, 9 insertions(+), 108 deletions(-)
diff --git a/site/app/components/kit/members.tsx b/site/app/components/kit/members.tsx
index 81f4c16..ba6e0a2 100644
--- a/site/app/components/kit/members.tsx
+++ b/site/app/components/kit/members.tsx
@@ -1,7 +1,8 @@
import type { CSSProperties } from 'react';
import { Link } from 'react-router';
+import { plural } from '../../format';
import { useOnline } from '../../live';
-import { useSpotlight } from '../../motion/motion';
+import { useMotion, useSpotlight } from '../../motion/motion';
import type { Member } from '../../types';
import { accentStyle, Avatar } from './basics';
@@ -22,7 +23,7 @@ function MemberRow({
{member.name}
{member.headline ?? `@${member.github}`}
-
+
{projects}
@@ -61,8 +62,9 @@ export function Orbit({
label: string;
}) {
const online = useOnline();
+ const spinning = useMotion('orbit');
return (
-
+
{members.map((member, index) => (
* {
- scroll-snap-align: start;
-}
-.film-wrap .chevron {
- position: absolute;
- top: 38%;
- width: 2.75rem;
- height: 2.75rem;
- border-radius: 50%;
- border: 1px solid var(--color-line);
- background: rgba(19, 19, 22, 0.78);
- backdrop-filter: blur(8px);
- color: var(--color-ink);
- display: grid;
- place-items: center;
- cursor: pointer;
- opacity: 0;
- transition:
- opacity 0.2s,
- transform 0.2s cubic-bezier(0.16, 1, 0.3, 1),
- border-color 0.2s;
- z-index: 2;
-}
-.film-wrap:hover .chevron,
-.film-wrap:focus-within .chevron,
-.film-wrap .chevron:focus-visible {
- opacity: 1;
-}
-.film-wrap .chevron:disabled {
- opacity: 0.2;
- pointer-events: none;
-}
-.film-wrap .chevron.prev {
- left: -1.375rem;
-}
-.film-wrap .chevron.next {
- right: -1.375rem;
-}
-.film-wrap .chevron svg {
- width: 1.1rem;
- height: 1.1rem;
-}
-.film-bar {
- height: 2px;
- background: var(--color-line-soft);
- margin-top: 0.75rem;
- position: relative;
- overflow: hidden;
- border-radius: 2px;
-}
-.film-bar i {
- position: absolute;
- inset-block: 0;
- left: 0;
- width: 20%;
- background: var(--color-ink);
- border-radius: 2px;
-}
-.film-hint {
- display: flex;
- justify-content: space-between;
- font-family: var(--font-mono);
- font-size: 0.78rem;
- color: var(--color-faint);
- margin-top: 0.5rem;
-}
-@media (max-width: 1000px) {
- .film-wrap .chevron.prev {
- left: 0.25rem;
- }
- .film-wrap .chevron.next {
- right: 0.25rem;
- }
-}
-
.panel {
border: 1px solid var(--color-line-soft);
background: var(--color-surface);
@@ -1764,6 +1665,10 @@ textarea.field {
.orbit:hover .pin {
animation-play-state: paused;
}
+ .orbit[data-still] .ring,
+ .orbit[data-still] .pin {
+ animation: none;
+ }
.orbit .orb:hover .avatar,
.people > a:hover .avatar,
.panel-list a:hover .avatar {
diff --git a/site/app/styles/pages/blog.css b/site/app/styles/pages/blog.css
index 1c6a074..932875c 100644
--- a/site/app/styles/pages/blog.css
+++ b/site/app/styles/pages/blog.css
@@ -39,9 +39,6 @@
.post-card:hover .cover::before {
transform: translateX(70%);
}
-.post-card .cover span {
- font-size: clamp(1.4rem, 3vw, 2.2rem);
-}
.post-card .body {
flex: 1;
display: flex;
diff --git a/site/app/styles/pages/directories.css b/site/app/styles/pages/directories.css
index ecc6338..ef3ce3a 100644
--- a/site/app/styles/pages/directories.css
+++ b/site/app/styles/pages/directories.css
@@ -81,9 +81,6 @@
border-bottom: 1px solid var(--color-line-soft);
border-radius: 0;
}
-.entry .face .cover span {
- font-size: clamp(1.8rem, 3.5vw, 2.6rem);
-}
.entry .face .cover::after {
content: '';
position: absolute;