diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 49cf3dd..66e1d7b 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -40,3 +40,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771668002,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","finish profile page"]],"content":"Signed commit: finish profile page","id":"8a5aed2f8ac370f781dca9db96ade991c18b7cc3b0d27149d9e2741e8276f16f","sig":"16e9a9242f7c22dab8e37fd9d618419b4d51d7c0156f52c1289e275d2528312f4006696473c6836b5a661425fe0412fe54127291fb9b0d14777f93c8228cffb0"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771669826,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","user badge is a universal hyperlink to the profile page"]],"content":"Signed commit: user badge is a universal hyperlink to the profile page","id":"973a406714e586037d81cca323024ff5e2cc1fbaeda8846f6f2994c3829c4fe0","sig":"e7a58526a3786fc1b9ab1f957c87c13a42d3c2cc95effcf4ce4f4710e01ecc45fcff3ca542c5fa223961d7b99fe336a2851c133aebe3bfc1a591ffe1c34b221a"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771680916,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix profile feeds"]],"content":"Signed commit: fix profile feeds","id":"33f33d76f6c79e68fdab72c8fdfc7e1f0ecc53a879a7f5aef02481f17384a06f","sig":"8f9056eab081d66edb693eb35a2e400368aa897746b97ca40a216604dc14ee877eb7f4f16dd2eeac257025b3adfe82e23734c7c106b6cec5e8a1ca661c872cc5"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771681068,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","remove landing page search bar"]],"content":"Signed commit: remove landing page search bar","id":"71087b100ce14a1f2eb975be23450c62143ee11a8fd0429ec7440bfea1751741","sig":"695c704503ed1397f6871770ad55822a17a503bcfb71a0db7b3f2477cacb0e767b9f122075b0216f884e41e175c0ac9f9e3d743086a2aa34db4aa1207c900703"} diff --git a/src/lib/components/NostrLinkRenderer.svelte b/src/lib/components/NostrLinkRenderer.svelte new file mode 100644 index 0000000..955ce14 --- /dev/null +++ b/src/lib/components/NostrLinkRenderer.svelte @@ -0,0 +1,267 @@ + + +{#each parts as part} + {#if part.type === 'text'} + {part.value} + {:else if part.type === 'event' && part.event} + + {:else if part.type === 'profile' && part.pubkey} + + {:else if part.type === 'placeholder'} + {part.value} + {/if} +{/each} + + diff --git a/src/lib/components/RepoHeader.svelte b/src/lib/components/RepoHeader.svelte new file mode 100644 index 0000000..1972f6e --- /dev/null +++ b/src/lib/components/RepoHeader.svelte @@ -0,0 +1,312 @@ + + +
+
+
+

{repoName}

+ {#if isPrivate} + Private + {/if} +
+
+ +
+
+ + {#if repoDescription} +

{repoDescription}

+ {/if} + +
+
+ Owner: + +
+ + {#if cloneUrls.length > 0} +
+ + {#if showCloneMenu} +
+ {#each cloneUrls as url} + + {/each} +
+ {/if} +
+ {/if} + + {#if showMenu} +
+ + {#if showMoreMenu} +
+ {#if isMaintainer} + + + {/if} + +
+ {/if} +
+ {/if} +
+
+ + diff --git a/src/lib/components/RepoHeaderEnhanced.svelte b/src/lib/components/RepoHeaderEnhanced.svelte new file mode 100644 index 0000000..a28526d --- /dev/null +++ b/src/lib/components/RepoHeaderEnhanced.svelte @@ -0,0 +1,569 @@ + + +
+
+
+

{repoName}

+ {#if isPrivate} + Private + {/if} + {#if userPubkey && onToggleBookmark} + + {/if} +
+
+ {#if userPubkey} + + {/if} +
+
+ + {#if repoDescription} +

{repoDescription}

+ {/if} + +
+
+ Owner: + +
+ + {#if cloneUrls.length > 0} +
+ + {#if showCloneMenu} +
+ {#each cloneUrls as url} + + {/each} +
+ {/if} +
+ {/if} + + {#if branches.length > 0 && currentBranch} +
+ + {#if showBranchMenu} +
+ {#each branches as branch} + {@const branchName = typeof branch === 'string' ? branch : branch.name} + + {/each} +
+ {/if} + {#if isMaintainer && currentBranch && currentBranch !== defaultBranch && onDeleteBranch} + + {/if} +
+ {/if} + + {#if isRepoCloned === true && onCopyCloneUrl} + + {/if} +
+ + {#if showMoreMenu && userPubkey} +
showMoreMenu = false} + onkeydown={(e) => { + if (e.key === 'Escape') { + showMoreMenu = false; + } + }} + role="button" + tabindex="0" + aria-label="Close menu" + >
+
+ {#if onFork} + + {/if} + {#if onCreateIssue} + + {/if} + {#if onCreatePR} + + {/if} + {#if onCreatePatch} + + {/if} + {#if hasUnlimitedAccess && (isRepoCloned === false || isRepoCloned === null) && onCloneToServer} + + {/if} + {#if isMaintainer && onSettings} + + {/if} + {#if onGenerateVerification} + + {/if} + {#if onDeleteAnnouncement} + + {/if} + {#if isMaintainer && onCreateBranch} + + {/if} +
+ {/if} +
+ + diff --git a/src/lib/components/RepoTabs.svelte b/src/lib/components/RepoTabs.svelte new file mode 100644 index 0000000..87063c3 --- /dev/null +++ b/src/lib/components/RepoTabs.svelte @@ -0,0 +1,219 @@ + + + + + diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 724d11f..9e27f56 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -6,6 +6,9 @@ import PRDetail from '$lib/components/PRDetail.svelte'; import UserBadge from '$lib/components/UserBadge.svelte'; import EventCopyButton from '$lib/components/EventCopyButton.svelte'; + import RepoHeaderEnhanced from '$lib/components/RepoHeaderEnhanced.svelte'; + import RepoTabs from '$lib/components/RepoTabs.svelte'; + import NostrLinkRenderer from '$lib/components/NostrLinkRenderer.svelte'; import { getPublicKeyWithNIP07, isNIP07Available, signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS, combineRelays } from '$lib/config.js'; @@ -682,6 +685,9 @@ let repoImage = $state(null); let repoBanner = $state(null); + // Repository owner pubkey (decoded from npub) + let repoOwnerPubkey = $state(null); + // Mobile view toggle for file list/file viewer let showFileListOnMobile = $state(true); @@ -1754,17 +1760,17 @@ try { const decoded = nip19.decode(npub); if (decoded.type === 'npub') { - const repoOwnerPubkey = decoded.data as string; + repoOwnerPubkey = decoded.data as string; repoAddress = `${KIND.REPO_ANNOUNCEMENT}:${repoOwnerPubkey}:${repo}`; } } catch (err) { console.warn('Failed to decode npub for bookmark address:', err); } - // Close menu when clicking outside + // Close menu when clicking outside (handled by RepoHeaderEnhanced component) function handleClickOutside(event: MouseEvent) { const target = event.target as HTMLElement; - if (showRepoMenu && !target.closest('.repo-menu-container')) { + if (showRepoMenu && !target.closest('.repo-header')) { showRepoMenu = false; } } @@ -2833,6 +2839,15 @@ } } + function handleBranchChangeDirect(branch: string) { + currentBranch = branch; + // Create a synthetic event for the existing handler + const syntheticEvent = { + target: { value: branch } + } as unknown as Event; + handleBranchChange(syntheticEvent); + } + async function handleBranchChange(event: Event) { const target = event.target as HTMLSelectElement; currentBranch = target.value; @@ -3590,205 +3605,110 @@
-
- {#if repoBanner} -
- { - console.error('[Repo Images] Failed to load banner:', repoBanner); - const target = e.target as HTMLImageElement; - if (target) target.style.display = 'none'; - }} /> -
- {/if} -
-
-
- {#if repoImage} - { - console.error('[Repo Images] Failed to load image:', repoImage); - const target = e.target as HTMLImageElement; - if (target) target.style.display = 'none'; - }} /> - {/if} -
-
-

{pageData.repoName || repo}

- {#if userPubkey && repoAddress} - - {/if} - {#if userPubkey} -
- - {#if showRepoMenu} - - {/if} -
- {/if} -
- {#if pageData.repoDescription} -

{pageData.repoDescription}

- {:else} -

No description

- {/if} -
-
-
- {#if pageData.repoLanguage} - - - {pageData.repoLanguage} - - {/if} - {#if pageData.repoIsPrivate} - Private - {:else} - Public - {/if} - {#if pageData.repoTopics && pageData.repoTopics.length > 0} -
- {#each pageData.repoTopics as topic} - {topic} + + {#if repoBanner} +
+ { + console.error('[Repo Images] Failed to load banner:', repoBanner); + const target = e.target as HTMLImageElement; + if (target) target.style.display = 'none'; + }} /> +
+ {/if} + + {#if repoOwnerPubkey} + showRepoMenu = !showRepoMenu} + showMenu={showRepoMenu} + userPubkey={userPubkey} + isBookmarked={isBookmarked} + loadingBookmark={loadingBookmark} + onToggleBookmark={toggleBookmark} + onFork={forkRepository} + forking={forking} + onCloneToServer={cloneRepository} + cloning={cloning} + checkingCloneStatus={checkingCloneStatus} + onCreateIssue={() => showCreateIssueDialog = true} + onCreatePR={() => showCreatePRDialog = true} + onCreatePatch={() => showCreatePatchDialog = true} + onCreateBranch={async () => { + if (!userPubkey || !isMaintainer || needsClone) return; + try { + const settings = await settingsStore.getSettings(); + defaultBranchName = settings.defaultBranch || 'master'; + } catch { + defaultBranchName = 'master'; + } + showCreateBranchDialog = true; + }} + onSettings={() => goto(`/signup?npub=${npub}&repo=${repo}`)} + onGenerateVerification={pageData.repoOwnerPubkey && userPubkeyHex === pageData.repoOwnerPubkey && verificationStatus?.verified !== true ? generateAnnouncementFileForRepo : undefined} + onDeleteAnnouncement={pageData.repoOwnerPubkey && userPubkeyHex === pageData.repoOwnerPubkey ? deleteAnnouncement : undefined} + deletingAnnouncement={deletingAnnouncement} + hasUnlimitedAccess={hasUnlimitedAccess($userStore.userLevel)} + needsClone={needsClone} + /> + {/if} + + + {#if allMaintainers.length > 0 || pageData.repoOwnerPubkey} + +
+ {/if} + + {#if pageData.repoWebsite || (pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0) || pageData.repoLanguage || (pageData.repoTopics && pageData.repoTopics.length > 0) || forkInfo?.isFork} + -
+ {/if}
{#if error} @@ -3916,57 +3791,19 @@ {/if} -
- - - - - - - -
+ activeTab = tab as typeof activeTab} + />
@@ -5216,32 +5053,22 @@ overflow: hidden; } - header { - display: flex; - flex-direction: column; - border-bottom: 1px solid var(--border-color); - background: var(--card-bg); - } - - .header-content { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; - padding: 2rem 2rem 1.5rem 2rem; - gap: 2rem; - margin-top: 1rem; - } - - .header-main { + .repo-metadata-section { + padding: 0.75rem 1rem; + background: var(--card-bg, #ffffff); + border-bottom: 1px solid var(--border-color, #e0e0e0); display: flex; - flex-direction: column; - flex: 1; + flex-wrap: wrap; gap: 1rem; - min-width: 0; - position: relative; + align-items: center; + font-size: 0.875rem; + } + + @media (min-width: 768px) { + .repo-metadata-section { + padding: 1rem 1.5rem; + } } - .repo-banner { @@ -5251,6 +5078,21 @@ background: var(--bg-secondary); margin-bottom: 0; position: relative; + display: none; /* Hidden on mobile by default */ + } + + .desktop-only { + display: none; /* Hidden on mobile */ + } + + @media (min-width: 768px) { + .desktop-only { + display: block; /* Show on desktop */ + } + + .repo-banner { + display: block; /* Show on desktop */ + } } .repo-banner::before { @@ -5294,219 +5136,11 @@ display: block; } - .repo-title-section { - display: flex; - align-items: flex-start; - gap: 1rem; - margin-bottom: 0.5rem; - width: 100%; - } - - .repo-title-text { - flex: 1; - min-width: 0; /* Allow text to shrink */ - } - - .repo-title-with-menu { - display: flex; - align-items: center; - gap: 0.75rem; - } - - .bookmark-icon-button { - padding: 0.375rem; - background: var(--card-bg); - border: 2px solid var(--border-color); - border-radius: 0.375rem; - cursor: pointer; - color: var(--text-primary); - display: inline-flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - width: 2rem; - height: 2rem; - flex-shrink: 0; - } - - .bookmark-icon-button:hover:not(:disabled) { - background: var(--bg-secondary); - border-color: var(--accent); - color: var(--accent); - } - - .bookmark-icon-button:disabled { - opacity: 0.5; - cursor: not-allowed; - border-color: var(--border-light); - } - - .bookmark-icon-button.bookmarked { - background: var(--accent); - border-color: var(--accent); - color: var(--accent-text, #ffffff); - } - - .bookmark-icon-button.bookmarked:hover:not(:disabled) { - background: var(--accent-hover); - border-color: var(--accent-hover); - opacity: 1; - } - - .bookmark-icon-button .icon-inline { - width: 1rem; - height: 1rem; - filter: brightness(0) saturate(100%) invert(1) !important; /* Default white for dark themes */ - opacity: 1 !important; - } - - /* Light theme: black icon */ - :global([data-theme="light"]) .bookmark-icon-button .icon-inline { - filter: brightness(0) saturate(100%) !important; /* Black in light theme */ - opacity: 1 !important; - } - - /* Dark themes: white icon */ - :global([data-theme="dark"]) .bookmark-icon-button .icon-inline, - :global([data-theme="black"]) .bookmark-icon-button .icon-inline { - filter: brightness(0) saturate(100%) invert(1) !important; /* White in dark themes */ - opacity: 1 !important; - } - - /* Hover: white for visibility */ - .bookmark-icon-button:hover:not(:disabled) .icon-inline { - filter: brightness(0) saturate(100%) invert(1) !important; - opacity: 1 !important; - } - - /* Light theme hover: keep black */ - :global([data-theme="light"]) .bookmark-icon-button:hover:not(:disabled) .icon-inline { - filter: brightness(0) saturate(100%) !important; - opacity: 1 !important; - } - - /* Bookmarked state: icon should be white (on accent background) */ - .bookmark-icon-button.bookmarked .icon-inline { - filter: brightness(0) saturate(100%) invert(1) !important; /* White on accent background */ - opacity: 1 !important; - } - - .repo-title-text h1 { - margin: 0; - word-wrap: break-word; - color: var(--text-primary); - font-weight: 600; - } - - .repo-menu-container { - position: relative; - } - - .repo-menu-button { - padding: 0.375rem; - background: var(--card-bg); - border: 2px solid var(--border-color); - border-radius: 0.375rem; - cursor: pointer; - color: var(--text-primary); - display: inline-flex; - align-items: center; - justify-content: center; - transition: all 0.2s ease; - width: 2rem; - height: 2rem; - } - - .repo-menu-button:hover { - background: var(--bg-secondary); - border-color: var(--accent); - color: var(--accent); - } - - .repo-menu-button svg { - width: 16px; - height: 16px; - } - - .repo-menu-dropdown { - position: absolute; - top: calc(100% + 0.5rem); - right: 0; - background: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: 0.5rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - min-width: 240px; - white-space: nowrap; - z-index: 1000; - display: flex; - flex-direction: column; - overflow: hidden; - } - - .repo-menu-item { - padding: 0.75rem 1rem; - background: transparent; - border: none; - border-bottom: 1px solid var(--border-color); - text-align: left; - cursor: pointer; - color: var(--text-primary); - font-size: 0.875rem; - font-family: 'IBM Plex Serif', serif; - transition: background 0.2s ease; - text-decoration: none; - display: block; - width: 100%; - white-space: nowrap; - } - - .repo-menu-item:last-child { - border-bottom: none; - } - - .repo-menu-item:hover:not(:disabled) { - background: var(--bg-secondary); - } - - .repo-menu-item:disabled { - opacity: 0.6; - cursor: not-allowed; - } - - .repo-menu-item-danger { - color: var(--error-text, #dc2626); - } - - .repo-menu-item-danger:hover:not(:disabled) { - background: var(--error-bg, rgba(220, 38, 38, 0.1)); - } + - .repo-image { - width: 80px; - height: 80px; - border-radius: 50%; - object-fit: cover; - flex-shrink: 0; - display: block; - background: var(--bg-secondary); - border: 3px solid var(--card-bg); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - } /* Position repo image over banner if banner exists */ - header:has(.repo-banner) .header-content { - margin-top: -30px; /* Overlap banner slightly */ - position: relative; - z-index: 2; - padding-left: 2.5rem; /* Extra padding on left to create space from banner edge */ - } - - header:has(.repo-banner) .repo-image { - border-color: var(--card-bg); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); - } - + /* Responsive design for smaller screens */ .mobile-toggle-button { display: none; /* Hidden by default on desktop */ @@ -5534,30 +5168,9 @@ } @media (max-width: 768px) { - .header-content { - flex-direction: column; - align-items: flex-start; - gap: 1rem; - } - - .header-actions { - width: 100%; - justify-content: flex-start; - flex-wrap: wrap; - } - .repo-banner { height: 150px; } - - header:has(.repo-banner) .header-content { - margin-top: -30px; - } - - .repo-image { - width: 60px; - height: 60px; - } /* Mobile toggle button visible on narrow screens */ .mobile-toggle-button { @@ -5595,14 +5208,6 @@ min-height: 0; } - .repo-image { - width: 64px; - height: 64px; - } - - .repo-title-text h1 { - font-size: 1.5rem; - } /* Editor header wraps on mobile */ .editor-header { @@ -5666,32 +5271,6 @@ padding: 0.75rem; } - /* Tab buttons responsive */ - .tabs { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - -ms-overflow-style: none; - } - - .tabs::-webkit-scrollbar { - display: none; - } - - .tab-button { - font-size: 0.85rem; - padding: 0.5rem 0.75rem; - white-space: nowrap; - flex-shrink: 0; - } - - /* Repo menu dropdown responsive */ - .repo-menu-dropdown { - right: 0; - left: auto; - min-width: 200px; - max-width: calc(100vw - 2rem); - } /* Issue and PR lists responsive */ .issue-item, .pr-item { @@ -5772,48 +5351,6 @@ line-height: 1.3; } - /* Make tabs more readable and responsive on mobile */ - .tabs { - padding: 0.5rem 0.75rem; - gap: 0.5rem; - -webkit-overflow-scrolling: touch; - scroll-behavior: smooth; - scroll-padding: 0.5rem; - } - - .tab-button { - padding: 0.5rem 0.875rem; - font-size: 0.875rem; - font-weight: 500; - min-height: 2.5rem; - border-bottom-width: 3px; - touch-action: manipulation; /* Better touch response */ - } - - .tab-button.active { - font-weight: 600; - border-bottom-width: 3px; - } - - /* Better visual feedback for touch */ - .tab-button:active { - transform: scale(0.98); - transition: transform 0.1s ease; - } - } - - /* Extra small screens - make tabs even more readable */ - @media (max-width: 480px) { - .tabs { - padding: 0.5rem; - gap: 0.375rem; - } - - .tab-button { - padding: 0.625rem 0.75rem; - font-size: 0.875rem; - min-height: 2.75rem; - } } /* Desktop: always show both file tree and editor */ @@ -5831,31 +5368,11 @@ } } - .repo-image[src=""], - .repo-image:not([src]) { - display: none; - } - .repo-banner img[src=""], .repo-banner img:not([src]) { display: none; } - .repo-description-header { - margin: 0.25rem 0 0 0; - color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.4; - max-width: 100%; - word-wrap: break-word; - } - - .repo-description-placeholder { - color: var(--text-secondary); - font-style: italic; - opacity: 0.8; - } - .fork-badge { padding: 0.25rem 0.5rem; background: var(--accent); @@ -5878,13 +5395,6 @@ opacity: 1; } - .repo-meta-info { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0.75rem; - margin-top: 0.5rem; - } .repo-language { display: inline-flex; @@ -5899,27 +5409,6 @@ opacity: 0.9; } - .repo-privacy-badge { - padding: 0.125rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - border: 1px solid transparent; - } - - .repo-privacy-badge.private { - background: var(--error-bg); - color: var(--error-text); - border-color: var(--error-text); - } - - .repo-privacy-badge.public { - background: var(--success-bg); - color: var(--success-text); - border-color: var(--success-text); - } - .repo-topics { display: flex; flex-wrap: wrap; @@ -6073,40 +5562,6 @@ } - header h1 { - margin: 0; - font-size: 1.5rem; - color: var(--text-primary); - } - .header-actions { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.75rem; - flex-shrink: 0; - flex-wrap: wrap; - } - - .branch-select { - padding: 0.5rem; - border: 1px solid var(--input-border); - border-radius: 0.25rem; - background: var(--input-bg); - color: var(--text-primary); - font-family: 'IBM Plex Serif', serif; - } - - .branch-select-empty { - padding: 0.5rem; - border: 1px solid var(--input-border); - border-radius: 0.25rem; - background: var(--input-bg); - color: var(--text-muted); - font-family: 'IBM Plex Serif', serif; - opacity: 0.7; - cursor: not-allowed; - user-select: none; - } .repo-view { @@ -6502,65 +5957,6 @@ background: var(--button-primary-hover); } - /* Tabs */ - .tabs { - display: flex; - gap: 0.25rem; - padding: 0.5rem 1rem; - border-bottom: 1px solid var(--border-color); - background: var(--card-bg); - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: thin; - -webkit-overflow-scrolling: touch; - scroll-behavior: smooth; - position: relative; - } - - .tabs::-webkit-scrollbar { - height: 6px; - } - - .tabs::-webkit-scrollbar-track { - background: var(--bg-secondary); - } - - .tabs::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 3px; - } - - .tabs::-webkit-scrollbar-thumb:hover { - background: var(--accent); - } - - .tab-button { - padding: 0.5rem 0.875rem; - background: none; - border: none; - border-bottom: 2px solid transparent; - cursor: pointer; - font-size: 0.875rem; - color: var(--text-secondary); - font-family: 'IBM Plex Serif', serif; - transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease; - white-space: nowrap; - flex-shrink: 0; - font-weight: 500; - border-radius: 0.25rem 0.25rem 0 0; - } - - .tab-button:hover { - color: var(--text-primary); - background: var(--bg-secondary); - } - - .tab-button.active { - color: var(--accent); - border-bottom-color: var(--accent); - font-weight: 600; - background: var(--bg-secondary); - } /* File tree actions */ .file-tree-actions {