Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/app/styles/profile-inline-edit.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,25 @@
outline-offset: -2px;
}

/* Centered guidance line for the empty banner box — gives the blank
gradient rectangle a purpose ("this is your banner; 3:1") instead of
reading as a featureless placeholder. Padded clear of the floating
"Change banner" pill in the bottom-right corner. */
.profile-banner-upload__hint {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 16px 16px 44px;
text-align: center;
font-family: var(--font-inter), system-ui, sans-serif;
font-size: 0.8125rem;
line-height: 1.4;
color: var(--fg-muted);
pointer-events: none;
}

.profile-banner-upload__img {
width: 100%;
height: 100%;
Expand Down
23 changes: 20 additions & 3 deletions src/components/layout/desktop-top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ export default function DesktopTopBar() {
// stays the primary "discard" action.
const isOnCreatePage = pathname === "/create" || pathname === "/project/new";
const showBackRow = isOnCertDetail || isOnProjectDetail || isOnCreatePage;
// /explore renders its own contextual "Search Certified" field inside the
// page chrome (it doubles as the cross-kind filter), so the top-bar's
// global search field is a redundant second "Search Certified" input at
// every width ≥800px. Suppress the top-bar copy there — Explore owns the
// search affordance on its own surface.
const isOnExplore = pathname === "/explore";
// Settings is its own standalone surface now (reachable from the
// site drawer); no tab strip there. The edit-profile page is the one
// exception — it borrows the profile strip (locked to Overview, see
Expand Down Expand Up @@ -344,6 +350,15 @@ export default function DesktopTopBar() {

if (isLoading) return null;

// Marketing landing (/welcome), signed out: the page is self-contained —
// it owns its own hero "Sign in with Certified" CTA and the footer. The
// full app chrome here (global search + Explore/Apps/Help icon nav + a
// second top-right "Sign in") is redundant against that hero CTA, so the
// top bar drops out entirely. Signed-in viewers keep the bar so they can
// navigate back out of the marketing page. (The mobile <Navbar> already
// renders /welcome as a transparent overlay for signed-out viewers.)
if (pathname === "/welcome" && !isAuthenticated) return null;

const tabHref = (tab: ProfileTab) => {
if (tab.href) return tab.href;
if (!pathname) return "#";
Expand Down Expand Up @@ -414,9 +429,11 @@ export default function DesktopTopBar() {
</div>

<div className="desktop-top-bar__right">
<div className="desktop-top-bar__search">
<GlobalSearch placeholder="Search Certified" />
</div>
{isOnExplore ? null : (
<div className="desktop-top-bar__search">
<GlobalSearch placeholder="Search Certified" />
</div>
)}

{isAuthenticated ? (
<div
Expand Down
11 changes: 10 additions & 1 deletion src/components/profile/banner-upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,16 @@ const BannerUpload: React.FC<BannerUploadProps> = ({
className="profile-banner-upload__img"
onError={() => setImgFailed(true)}
/>
) : null}
) : (
// Neutral guidance hint for the otherwise-featureless empty
// box. Sets reader expectations (what the banner is, the crop)
// instead of leaving a blank gradient rectangle. aria-hidden:
// the "Change banner" button already carries the accessible
// affordance, so the hint is decorative for screen readers.
<span className="profile-banner-upload__hint" aria-hidden="true">
Add a banner image — shown across the top of your profile (3:1)
</span>
)}

<div className="profile-banner-upload__btn-row">
<button
Expand Down
17 changes: 16 additions & 1 deletion src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,22 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
icon: "h-11 w-11 md:h-10 md:w-10 p-0 text-sm",
};

const disabledStyles = disabled || loading ? "opacity-50 cursor-not-allowed" : "";
// Disabled treatment. Filled variants (primary / destructive) read as
// ambiguous at half opacity — the brand fill still looks "active" — so a
// disabled filled button drops to a flat sunken fill + muted text + a
// subtle border that is unmistakably inert. Outline / text variants
// (secondary / ghost) already read clearly when dimmed, so they keep the
// lighter opacity treatment. While `loading`, every variant keeps the
// opacity dim so the spinner stays legible over the original fill.
const filledDisabledStyles =
"disabled:!bg-[var(--bg-sunken)] disabled:!text-[var(--fg-muted)] disabled:!border disabled:!border-[var(--border-default)] disabled:hover:!opacity-100 disabled:hover:!bg-[var(--bg-sunken)]";
const disabledStyles = !(disabled || loading)
? ""
: loading
? "opacity-50 cursor-not-allowed"
: variant === "primary" || variant === "destructive"
? `cursor-not-allowed ${filledDisabledStyles}`
: "opacity-50 cursor-not-allowed";

// Active visual for toggle buttons. Only secondary/ghost have an "off" look
// distinct enough that a pressed state reads as on; primary/destructive keep
Expand Down
Loading