diff --git a/apps/web/app/space/[id]/(space)/layout.tsx b/apps/web/app/space/[id]/(space)/layout.tsx
index 4f1254f831..cc8223bf61 100644
--- a/apps/web/app/space/[id]/(space)/layout.tsx
+++ b/apps/web/app/space/[id]/(space)/layout.tsx
@@ -23,7 +23,7 @@ import { Spacer } from '~/design-system/spacer';
import { EditableSpaceHeading } from '~/partials/entity-page/editable-space-header';
import { EntityPageCover } from '~/partials/entity-page/entity-page-cover';
import { EntityPageInlineDescription } from '~/partials/entity-page/entity-page-inline-description';
-import { ENTITY_PAGE_WITH_SIDEBAR_MAX_WIDTH } from '~/partials/entity-page/entity-page-layout';
+import { ENTITY_PAGE_WITH_SIDEBAR_MAX_WIDTH, PROFILE_COVER_SIZE } from '~/partials/entity-page/entity-page-layout';
import { PersonalProfileBioStarterMerge } from '~/partials/entity-page/personal-profile-bio-starter-merge';
import { PersonalProfileSuggestedCard } from '~/partials/entity-page/personal-profile-suggested-card';
import { PersonalProfileSuggestedTaskSync } from '~/partials/entity-page/personal-profile-suggested-task-sync';
@@ -132,6 +132,7 @@ export default async function Layout(props0: LayoutProps) {
avatarUrl={props.avatarUrl}
coverUrl={props.coverUrl}
contentMaxWidth={isProfile ? ENTITY_PAGE_WITH_SIDEBAR_MAX_WIDTH : undefined}
+ coverSize={isProfile ? PROFILE_COVER_SIZE : undefined}
/>
{isProfile && }
-
+
{/*
* A profile renders none of this row. Types move to the rail's
* About section, the vote pair into the action row beside Edit
@@ -190,7 +191,9 @@ export default async function Layout(props0: LayoutProps) {
/>
-
+ {/* 40px above the tab bar on a profile, 24px elsewhere. The description's
+ own 20px bottom margin collapses into this one, so this is the gap. */}
+
{typeIds.includes(SystemIds.PERSON_TYPE) ? (
diff --git a/apps/web/core/claims/browse/claim-page-view.tsx b/apps/web/core/claims/browse/claim-page-view.tsx
index cc70698fc5..8d73469d58 100644
--- a/apps/web/core/claims/browse/claim-page-view.tsx
+++ b/apps/web/core/claims/browse/claim-page-view.tsx
@@ -155,9 +155,7 @@ export function ClaimPageView({
};
// Matches profile record tabs: unknown/error stays reachable, while a settled zero disappears.
const hasDebates = hasRecordToShow(
- record.debatesLoading || record.debatesError || record.debatesCountUnavailable
- ? undefined
- : record.debatesTotal
+ record.debatesLoading || record.debatesError || record.debatesCountUnavailable ? undefined : record.debatesTotal
);
const hasClaims = hasRecordToShow(
record.claimsLoading || record.claimsError || record.claimsCountUnavailable ? undefined : record.claimsTotal
@@ -347,7 +345,7 @@ function ClaimTabPanel({
isError: record.debatesError,
isCountUnavailable: record.debatesCountUnavailable,
href: hrefs.debates,
- seeAllLabel: 'See all debates',
+ seeAllLabel: 'View all debates',
onSeeAll: onSelectSystemTab ? () => onSelectSystemTab('debates') : undefined,
},
{
@@ -359,7 +357,7 @@ function ClaimTabPanel({
isError: record.claimsError,
isCountUnavailable: record.claimsCountUnavailable,
href: hrefs.claims,
- seeAllLabel: 'See all claims',
+ seeAllLabel: 'View all claims',
onSeeAll: onSelectSystemTab ? () => onSelectSystemTab('claims') : undefined,
},
];
diff --git a/apps/web/core/debates/browse/debate-feed-player.tsx b/apps/web/core/debates/browse/debate-feed-player.tsx
index 143aa60370..f5034b82c7 100644
--- a/apps/web/core/debates/browse/debate-feed-player.tsx
+++ b/apps/web/core/debates/browse/debate-feed-player.tsx
@@ -36,12 +36,7 @@ type DebateFeedPlayerProps = {
preload?: boolean;
};
-export function DebateFeedPlayer({
- debate,
- active,
- preload = false,
- reducedOverlays = false,
-}: DebateFeedPlayerProps) {
+export function DebateFeedPlayer({ debate, active, preload = false, reducedOverlays = false }: DebateFeedPlayerProps) {
// Loading is deliberately wider than playing. `useDebatePlayback`'s flag gates only the URL
// fetch and the transcript query — playback is driven by `active` in the effect below — so a
// preloading card fetches without autoplaying off-screen.
@@ -324,7 +319,8 @@ export function DebateFeedPlayer({
data-debate-autoplay-blocked={autoplayBlocked ? 'true' : 'false'}
// No gap and one radius on the outside: the two tiles are a single surface in the Figma
// frame, which is what lets the subtitle straddle the seam rather than sit inside one tile.
- className="group relative flex flex-col overflow-hidden rounded-xl"
+ // 12px in the compact gallery (a profile's or claim's Activity), 16px in the feeds.
+ className={cx('group relative flex flex-col overflow-hidden', reducedOverlays ? 'rounded-lg' : 'rounded-xl')}
>
+
);
diff --git a/apps/web/design-system/button.tsx b/apps/web/design-system/button.tsx
index 3b029da6cb..7fc3459495 100644
--- a/apps/web/design-system/button.tsx
+++ b/apps/web/design-system/button.tsx
@@ -16,7 +16,27 @@ type ButtonProps = React.ComponentPropsWithoutRef<'button'> & {
small?: boolean;
};
-const buttonClassNames = (className = '') =>
+/** The pill's shape and type, shared by both of its colourings below. */
+const PILL_BUTTON_SHAPE =
+ 'h-7 shrink-0 !gap-0 !rounded-full !px-2.5 !py-0 !text-[16px] !leading-[13px] font-normal tracking-[-0.35px] whitespace-nowrap !shadow-none';
+
+/**
+ * The black pill: the navbar's Log in button.
+ * Layered over the `primary` variant, whose colours it overrides.
+ */
+export const PILL_BUTTON_CLASS_NAME = `${PILL_BUTTON_SHAPE} !border-transparent !bg-[#151515] !text-white hover:!bg-[#151515] focus-visible:!border-text focus-visible:!shadow-inner-text`;
+
+/**
+ * The same pill in the `secondary` variant's colours — white, a grey outline that
+ * darkens on hover. Layered over `secondary`, which supplies those colours.
+ */
+export const PILL_BUTTON_SECONDARY_CLASS_NAME = PILL_BUTTON_SHAPE;
+
+/**
+ * The button's classes on their own, for a link that should look like one. A
+ * `