mirror of
https://github.com/github/awesome-copilot.git
synced 2026-03-23 09:35:13 +00:00
feat(ui): comprehensive UI/UX improvements (#1069)
* feat(ui): replace emoji icons with SVG icon system Replace all emoji icons with a consistent SVG icon system to improve: - Visual consistency across platforms - Design token control and theming - Professional appearance Changes: - Add new Icon.astro component with 16 custom SVG icons - Update index.astro to use SVG icons in resource cards - Update index.ts to render SVG icons in search results - Update utils.ts to return icon names instead of emojis - Update global.css with proper SVG icon styling - Remove emoji from Footer component Icons added: robot, document, lightning, hook, workflow, plug, wrench, book, plus action icons: close, copy, download, share, external, plus, search, chevron-down Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): enhance hero section, add animations and mobile responsiveness Phase 2 & 3 UI/UX improvements: Hero Section Enhancements: - Add gradient text effect for title (purple to orange gradient) - Add animated floating particles in hero background - Increase hero padding for better visual impact Card Category Colors: - Add category-specific accent colors (AI, docs, power, automation, etc.) - Each category has unique glow effect on hover - Category colors: purple (AI), orange (docs), red (power), etc. Entrance Animations: - Add staggered fade-in animation for cards (50ms delay each) - Cards animate in with translateY + opacity - Add slide-down animation for search results Mobile Responsiveness: - Responsive grid: 4 cols → 2 cols → 1 col - Adjust font sizes for mobile screens - Add safe-area-inset support for notched devices - Ensure touch targets ≥44px Accessibility: - Add prefers-reduced-motion support - Disable animations for users who prefer reduced motion - Smooth scroll with fallback Additional Improvements: - Add arrow indicator on card hover - Add loading animation for count numbers - Enhanced scrollbar styling - Print styles for better printing Co-Authored-By: Claude <noreply@anthropic.com> * feat(ui): add theme toggle, enhanced search, and back to top button Theme Toggle: - Create ThemeToggle.astro component with sun/moon icons - Add theme initialization in Head.astro to prevent flash - Store theme preference in localStorage - Keyboard shortcut: Cmd/Ctrl + Shift + L - Smooth icon transition animation Back to Top Button: - Create BackToTop.astro component - Appears after scrolling 400px - Smooth scroll to top on click - Fixed position bottom-right - Respects reduced motion preference Enhanced Search: - Recent searches functionality with localStorage - Show recent searches on focus when empty - Remove individual items or clear all - Enhanced empty state with icon and hint - Cmd/Ctrl + K keyboard shortcut to focus search - Add search to recent when getting results CSS Enhancements: - Theme toggle container styles - Recent searches section styling - Search empty state with icon - Search loading spinner - Keyboard shortcut hint styles - Print styles for new components Co-Authored-By: Claude <noreply@anthropic.com> * fix(ui): resolve header and theme toggle issues - Add Copilot logo to header via Starlight config with automatic theme switching - Fix theme toggle slider direction (was reversed) - Fix theme toggle active icon highlighting (was backwards) - Change theme toggle from purple circle slider to bold text indicator - Fix theme toggle slider overflow by adding overflow: hidden - Remove duplicate banner image from home page - Clean up conflicting logo CSS rules to prevent duplication The header now displays: [ Copilot Icon ] Awesome GitHub Copilot [ Search ] Theme toggle indicators are now visually clear with bold text for selected theme. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix(ui): address feedback on UI/UX improvements - Remove logo from header per brand guidance (logo config and CSS) - Fix back-to-top button visibility by moving to body level and using global styles - Fix modal visibility by adding 'visible' class for CSS animations - Fix theme toggle applying site-wide by using global styles and proper theme initialization - Update icons to use GitHub Primer SVG icons with proper fill-based styling - Fix plugin modal to render SVG icons instead of icon names - Add theme initialization script to prevent flash of unstyled content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move modal to body level to fix z-index stacking context issue The modal was nested inside .main-pane which has isolation: isolate, creating a new stacking context. This caused the modal's z-index to be evaluated within that context, unable to stack above the header. This fix moves the modal to be a direct child of body on page load, allowing it to properly cover the entire viewport including navbar. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,13 +17,7 @@ export default defineConfig({
|
||||
starlight({
|
||||
title: "Awesome GitHub Copilot",
|
||||
description: siteDescription,
|
||||
social: [
|
||||
{
|
||||
icon: "github",
|
||||
label: "GitHub",
|
||||
href: "https://github.com/github/awesome-copilot",
|
||||
},
|
||||
],
|
||||
social: [],
|
||||
head: [
|
||||
{
|
||||
tag: "meta",
|
||||
|
||||
BIN
website/public/images/awesome-copilot.png
Normal file
BIN
website/public/images/awesome-copilot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 805 KiB |
129
website/src/components/BackToTop.astro
Normal file
129
website/src/components/BackToTop.astro
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
// Back to Top Button Component
|
||||
---
|
||||
|
||||
<button
|
||||
id="back-to-top"
|
||||
class="back-to-top"
|
||||
aria-label="Back to top"
|
||||
title="Back to top"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 15l-6-6-6 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const button = document.getElementById('back-to-top');
|
||||
if (!button) return;
|
||||
|
||||
// Move button to body level to escape stacking contexts
|
||||
if (button.parentElement !== document.body) {
|
||||
document.body.appendChild(button);
|
||||
}
|
||||
|
||||
// Show/hide button based on scroll position
|
||||
function toggleVisibility() {
|
||||
const scrollY = window.scrollY || document.documentElement.scrollTop;
|
||||
if (scrollY > 400) {
|
||||
button.classList.add('visible');
|
||||
} else {
|
||||
button.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll to top with smooth behavior
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// Throttled scroll handler
|
||||
let ticking = false;
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
toggleVisibility();
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Click handler
|
||||
button.addEventListener('click', scrollToTop);
|
||||
|
||||
// Initial check
|
||||
toggleVisibility();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(20px) scale(0.8);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 12px rgba(133, 52, 243, 0.4);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.back-to-top.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.back-to-top:hover {
|
||||
background: var(--color-accent-hover);
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow: 0 6px 20px rgba(133, 52, 243, 0.5);
|
||||
}
|
||||
|
||||
.back-to-top:active {
|
||||
transform: translateY(0) scale(0.95);
|
||||
}
|
||||
|
||||
.back-to-top:focus-visible {
|
||||
outline: 2px solid var(--color-text-emphasis);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.back-to-top {
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Respect reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.back-to-top {
|
||||
transition: opacity 0.2s ease;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.back-to-top.visible {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ import LastUpdated from "@astrojs/starlight/components/LastUpdated.astro";
|
||||
import Pagination from "@astrojs/starlight/components/Pagination.astro";
|
||||
import config from "virtual:starlight/user-config";
|
||||
import { Icon } from "@astrojs/starlight/components";
|
||||
import ThemeToggle from "./ThemeToggle.astro";
|
||||
---
|
||||
|
||||
<footer class="sl-flex">
|
||||
@@ -21,9 +22,11 @@ import { Icon } from "@astrojs/starlight/components";
|
||||
)
|
||||
}
|
||||
|
||||
<p class="made-by">Made with ❤️ by our amazing <a href="/contributors/">contributors</a></p>
|
||||
<p class="made-by">Made with love by our amazing <a href="/contributors/">contributors</a></p>
|
||||
</footer>
|
||||
|
||||
<ThemeToggle />
|
||||
|
||||
<style>
|
||||
footer {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -44,7 +44,29 @@ const twitterDomain =
|
||||
{socialImageUrl && <meta property="og:image:secure_url" content={socialImageUrl} />}
|
||||
{socialImageType && <meta property="og:image:type" content={socialImageType} />}
|
||||
{socialImageAlt && <meta name="twitter:image:alt" content={socialImageAlt} />}
|
||||
|
||||
<!-- Theme initialization script (runs early to prevent flash) -->
|
||||
<script is:inline>
|
||||
(function() {
|
||||
const STORAGE_KEY = 'awesome-copilot-theme';
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
// Theme handling:
|
||||
// - 'dark' or 'light' → set data-theme attribute to that value
|
||||
// - 'auto' or unset → don't set data-theme (CSS media query handles system preference)
|
||||
if (stored === 'dark' || stored === 'light') {
|
||||
document.documentElement.setAttribute('data-theme', stored);
|
||||
}
|
||||
// For 'auto' or unset, no attribute means CSS media query controls theme
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline define:vars={{ basePath }}>
|
||||
// basePath setup for runtime use
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.body.dataset.basePath = basePath;
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.body.dataset.basePath = basePath;
|
||||
});
|
||||
|
||||
120
website/src/components/Icon.astro
Normal file
120
website/src/components/Icon.astro
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
// Icon component with SVG icons
|
||||
// Icons are either fill-based (from GitHub Primer) or stroke-based (custom)
|
||||
// GitHub Primer icons are sourced from https://primer.style/foundations/icons/
|
||||
|
||||
export interface Props {
|
||||
name: 'agents' | 'instructions' | 'skills' | 'hooks' | 'workflows' | 'plugins' | 'tools' | 'learning' | 'close' | 'copy' | 'download' | 'share' | 'external' | 'plus' | 'search' | 'chevron-down' | 'document' | 'lightning' | 'hook' | 'workflow' | 'plug' | 'wrench' | 'book' | 'robot' | 'sync';
|
||||
size?: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { name, size = 24, class: className = '' } = Astro.props;
|
||||
|
||||
// Icon definitions: { path: SVG path(s), fill: true for fill-based icons }
|
||||
const icons: Record<string, { path: string; fill?: boolean }> = {
|
||||
// Resource type icons - using GitHub Primer icons where available
|
||||
|
||||
// Agent icon - using GitHub Primer's agent-24 (sparkle over workflow)
|
||||
// Source: https://primer.style/foundations/icons/agent-24
|
||||
'robot': {
|
||||
fill: true,
|
||||
path: `<path d="M22.5 13.919v-.278a5.097 5.097 0 0 0-4.961-5.086.858.858 0 0 1-.754-.497l-.149-.327A6.414 6.414 0 0 0 10.81 4a6.133 6.133 0 0 0-6.13 6.32l.019.628a.863.863 0 0 1-.67.869A3.263 3.263 0 0 0 1.5 14.996v.108A3.397 3.397 0 0 0 4.896 18.5h1.577a.75.75 0 0 1 0 1.5H4.896A4.896 4.896 0 0 1 0 15.104v-.108a4.761 4.761 0 0 1 3.185-4.493l-.004-.137A7.633 7.633 0 0 1 10.81 2.5a7.911 7.911 0 0 1 7.176 4.58C21.36 7.377 24 10.207 24 13.641v.278a.75.75 0 0 1-1.5 0Z"/><path d="m12.306 11.77 3.374 3.375a.749.749 0 0 1 0 1.061l-3.375 3.375-.057.051a.751.751 0 0 1-1.004-.051.751.751 0 0 1-.051-1.004l.051-.057 2.845-2.845-2.844-2.844a.75.75 0 1 1 1.061-1.061ZM22.5 19.8H18a.75.75 0 0 1 0-1.5h4.5a.75.75 0 0 1 0 1.5Z"/>`
|
||||
},
|
||||
|
||||
// Document icon - custom stroke-based
|
||||
'document': {
|
||||
path: `<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
// Lightning icon - custom stroke-based (for skills)
|
||||
'lightning': {
|
||||
path: `<path d="M13 2 4.09 12.11a1.23 1.23 0 0 0 .13 1.72l.16.14a1.23 1.23 0 0 0 1.52 0L13 9.5V22l8.91-10.11a1.23 1.23 0 0 0-.13-1.72l-.16-.14a1.23 1.23 0 0 0-1.52 0L13 14.5V2Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
// Hook icon - using GitHub Primer's sync-24 (represents hooks/iterations)
|
||||
// Source: https://primer.style/foundations/icons/sync-24
|
||||
'hook': {
|
||||
fill: true,
|
||||
path: `<path d="M3.38 8A9.502 9.502 0 0 1 12 2.5a9.502 9.502 0 0 1 9.215 7.182.75.75 0 1 0 1.456-.364C21.473 4.539 17.15 1 12 1a10.995 10.995 0 0 0-9.5 5.452V4.75a.75.75 0 0 0-1.5 0V8.5a1 1 0 0 0 1 1h3.75a.75.75 0 0 0 0-1.5H3.38Zm-.595 6.318a.75.75 0 0 0-1.455.364C2.527 19.461 6.85 23 12 23c4.052 0 7.592-2.191 9.5-5.451v1.701a.75.75 0 0 0 1.5 0V15.5a1 1 0 0 0-1-1h-3.75a.75.75 0 0 0 0 1.5h2.37A9.502 9.502 0 0 1 12 21.5c-4.446 0-8.181-3.055-9.215-7.182Z"/>`
|
||||
},
|
||||
|
||||
// Workflow icon - using GitHub Primer's workflow-24
|
||||
// Source: https://primer.style/foundations/icons/workflow-24
|
||||
// Also used by https://github.github.com/gh-aw/
|
||||
'workflow': {
|
||||
fill: true,
|
||||
path: `<path d="M1 3a2 2 0 0 1 2-2h6.5a2 2 0 0 1 2 2v6.5a2 2 0 0 1-2 2H7v4.063C7 16.355 7.644 17 8.438 17H12.5v-2.5a2 2 0 0 1 2-2H21a2 2 0 0 1 2 2V21a2 2 0 0 1-2 2h-6.5a2 2 0 0 1-2-2v-2.5H8.437A2.939 2.939 0 0 1 5.5 15.562V11.5H3a2 2 0 0 1-2-2Zm2-.5a.5.5 0 0 0-.5.5v6.5a.5.5 0 0 0 .5.5h6.5a.5.5 0 0 0 .5-.5V3a.5.5 0 0 0-.5-.5ZM14.5 14a.5.5 0 0 0-.5.5V21a.5.5 0 0 0 .5.5H21a.5.5 0 0 0 .5-.5v-6.5a.5.5 0 0 0-.5-.5Z"/>`
|
||||
},
|
||||
|
||||
// Plug icon - using GitHub Primer's plug-24
|
||||
// Source: https://primer.style/foundations/icons/plug-24
|
||||
'plug': {
|
||||
fill: true,
|
||||
path: `<path d="M7 11.5H2.938c-.794 0-1.438.644-1.438 1.437v8.313a.75.75 0 0 1-1.5 0v-8.312A2.939 2.939 0 0 1 2.937 10H7V6.151c0-.897.678-1.648 1.57-1.74l6.055-.626 1.006-1.174A1.752 1.752 0 0 1 16.96 2h1.29c.966 0 1.75.784 1.75 1.75V6h3.25a.75.75 0 0 1 0 1.5H20V14h3.25a.75.75 0 0 1 0 1.5H20v2.25a1.75 1.75 0 0 1-1.75 1.75h-1.29a1.75 1.75 0 0 1-1.329-.611l-1.006-1.174-6.055-.627A1.749 1.749 0 0 1 7 15.348Zm9.77-7.913v.001l-1.201 1.4a.75.75 0 0 1-.492.258l-6.353.657a.25.25 0 0 0-.224.249v9.196a.25.25 0 0 0 .224.249l6.353.657c.191.02.368.112.493.258l1.2 1.401a.252.252 0 0 0 .19.087h1.29a.25.25 0 0 0 .25-.25v-14a.25.25 0 0 0-.25-.25h-1.29a.252.252 0 0 0-.19.087Z"/>`
|
||||
},
|
||||
|
||||
// Wrench icon - custom stroke-based (for tools)
|
||||
'wrench': {
|
||||
path: `<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
// Book icon - custom stroke-based (for learning)
|
||||
'book': {
|
||||
path: `<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
// Action icons - all custom stroke-based
|
||||
'close': {
|
||||
path: `<path d="M18 6 6 18M6 6l12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'copy': {
|
||||
path: `<path d="M8 4h8a2 2 0 0 1 2 2v8M8 4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 4v10a2 2 0 0 0 2 2h8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'download': {
|
||||
path: `<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'share': {
|
||||
path: `<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8M16 6l-4-4-4 4M12 2v13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'external': {
|
||||
path: `<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14 21 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'plus': {
|
||||
path: `<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'search': {
|
||||
path: `<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="m21 21-4.35-4.35" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
'chevron-down': {
|
||||
path: `<path d="m6 9 6 6 6-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
||||
},
|
||||
|
||||
// Alias for hook - same as 'hook'
|
||||
'sync': {
|
||||
fill: true,
|
||||
path: `<path d="M3.38 8A9.502 9.502 0 0 1 12 2.5a9.502 9.502 0 0 1 9.215 7.182.75.75 0 1 0 1.456-.364C21.473 4.539 17.15 1 12 1a10.995 10.995 0 0 0-9.5 5.452V4.75a.75.75 0 0 0-1.5 0V8.5a1 1 0 0 0 1 1h3.75a.75.75 0 0 0 0-1.5H3.38Zm-.595 6.318a.75.75 0 0 0-1.455.364C2.527 19.461 6.85 23 12 23c4.052 0 7.592-2.191 9.5-5.451v1.701a.75.75 0 0 0 1.5 0V15.5a1 1 0 0 0-1-1h-3.75a.75.75 0 0 0 0 1.5h2.37A9.502 9.502 0 0 1 12 21.5c-4.446 0-8.181-3.055-9.215-7.182Z"/>`
|
||||
},
|
||||
};
|
||||
|
||||
const iconData = icons[name] || { path: '' };
|
||||
const isFill = iconData.fill ?? false;
|
||||
const iconPath = iconData.path;
|
||||
---
|
||||
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={size}
|
||||
height={size}
|
||||
fill={isFill ? 'currentColor' : 'none'}
|
||||
class={className}
|
||||
aria-hidden="true"
|
||||
set:html={iconPath}
|
||||
/>
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: 'robot' | 'document' | 'lightning' | 'hook' | 'workflow' | 'plug' | 'wrench' | 'book';
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
const { title, description, icon } = Astro.props;
|
||||
const contributingUrl = 'https://github.com/github/awesome-copilot/blob/main/CONTRIBUTING.md';
|
||||
---
|
||||
|
||||
@@ -12,7 +15,10 @@ const contributingUrl = 'https://github.com/github/awesome-copilot/blob/main/CON
|
||||
<div class="container">
|
||||
<div class="page-header-row">
|
||||
<div>
|
||||
<h1><Fragment set:html={title} /></h1>
|
||||
<h1>
|
||||
{icon && <Icon name={icon} size={28} />}
|
||||
<Fragment set:html={title} />
|
||||
</h1>
|
||||
<p><slot><Fragment set:html={description} /></slot></p>
|
||||
</div>
|
||||
<a href={contributingUrl} class="contribute-link" target="_blank" rel="noopener">
|
||||
@@ -22,3 +28,11 @@ const contributingUrl = 'https://github.com/github/awesome-copilot/blob/main/CON
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.page-header h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
170
website/src/components/ThemeToggle.astro
Normal file
170
website/src/components/ThemeToggle.astro
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
// Theme Toggle Component - 3 state slider: Auto | Dark | Light
|
||||
---
|
||||
|
||||
<div class="theme-toggle-container">
|
||||
<button
|
||||
id="theme-toggle"
|
||||
class="theme-toggle"
|
||||
aria-label="Toggle theme"
|
||||
title="Change theme"
|
||||
>
|
||||
<span class="theme-icon moon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="theme-icon auto" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 2v10l4.5 4.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="theme-icon sun" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3"/>
|
||||
<line x1="12" y1="21" x2="12" y2="23"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="theme-slider"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// Move theme toggle to body level to escape any stacking contexts
|
||||
const container = document.querySelector('.theme-toggle-container');
|
||||
if (container && container.parentElement !== document.body) {
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'awesome-copilot-theme';
|
||||
const toggle = document.getElementById('theme-toggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
const themes = ['dark', 'auto', 'light'];
|
||||
const icons = ['moon', 'auto', 'sun'];
|
||||
|
||||
function getThemeIndex() {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored && themes.includes(stored)) {
|
||||
return themes.indexOf(stored);
|
||||
}
|
||||
// Default to light theme
|
||||
return 2;
|
||||
}
|
||||
|
||||
function applyTheme(index: number) {
|
||||
const theme = themes[index];
|
||||
if (theme === 'auto') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
html.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
||||
} else {
|
||||
html.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
// Move slider
|
||||
const slider = toggle?.querySelector('.theme-slider') as HTMLElement;
|
||||
if (slider) {
|
||||
slider.style.transform = `translateX(${index * 100}%)`;
|
||||
}
|
||||
|
||||
// Highlight active icon
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
themeToggle?.setAttribute('data-active', String(index));
|
||||
}
|
||||
|
||||
function cycleTheme() {
|
||||
const currentIndex = getThemeIndex();
|
||||
const nextIndex = (currentIndex + 1) % themes.length;
|
||||
localStorage.setItem(STORAGE_KEY, themes[nextIndex]);
|
||||
applyTheme(nextIndex);
|
||||
}
|
||||
|
||||
// Initialize
|
||||
applyTheme(getThemeIndex());
|
||||
|
||||
// Click handler
|
||||
toggle?.addEventListener('click', cycleTheme);
|
||||
|
||||
// Keyboard shortcut
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'L') {
|
||||
e.preventDefault();
|
||||
cycleTheme();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for system theme changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
if (localStorage.getItem(STORAGE_KEY) === 'auto') {
|
||||
applyTheme(1); // auto
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
.theme-toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 108px;
|
||||
height: 36px;
|
||||
padding: 3px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 20px;
|
||||
background: var(--color-bg-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.theme-toggle:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 30px;
|
||||
border-radius: 16px;
|
||||
color: var(--color-text-muted);
|
||||
transition: all 0.2s ease;
|
||||
z-index: 1;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.theme-icon.moon { color: #9898a6; }
|
||||
.theme-icon.auto { color: #9898a6; }
|
||||
.theme-icon.sun { color: #9898a6; }
|
||||
|
||||
.theme-toggle[data-active="0"] .moon,
|
||||
.theme-toggle[data-active="1"] .auto,
|
||||
.theme-toggle[data-active="2"] .sun {
|
||||
color: var(--color-text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.theme-slider {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import { renderAgentsHtml, sortAgents } from '../scripts/pages/agents-render';
|
||||
|
||||
const initialItems = sortAgents(agentsData.items, 'title');
|
||||
@@ -12,7 +13,7 @@ const initialItems = sortAgents(agentsData.items, 'title');
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Custom Agents', description: 'Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="🤖 Custom Agents" description="Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains" />
|
||||
<PageHeader title="Custom Agents" description="Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains" icon="robot" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -57,6 +58,7 @@ const initialItems = sortAgents(agentsData.items, 'title');
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
<EmbeddedPageData filename="agents.json" data={agentsData} />
|
||||
|
||||
<script>
|
||||
|
||||
@@ -5,7 +5,7 @@ import PageHeader from '../components/PageHeader.astro';
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Contributors', description: 'The wonderful people who have contributed to Awesome GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="🌟 Contributors" description="The wonderful people who have contributed to Awesome GitHub Copilot" />
|
||||
<PageHeader title="Contributors" description="The wonderful people who have contributed to Awesome GitHub Copilot" icon="book" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
|
||||
@@ -4,6 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import hooksData from '../../public/data/hooks.json';
|
||||
import { renderHooksHtml, sortHooks } from '../scripts/pages/hooks-render';
|
||||
|
||||
@@ -12,7 +13,7 @@ const initialItems = sortHooks(hooksData.items, 'title');
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Hooks', description: 'Automated workflows triggered by Copilot coding agent events', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="🪝 Hooks" description="Automated workflows triggered by Copilot coding agent events" />
|
||||
<PageHeader title="Hooks" description="Automated workflows triggered by Copilot coding agent events" icon="hook" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -50,6 +51,7 @@ const initialItems = sortHooks(hooksData.items, 'title');
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
|
||||
<EmbeddedPageData filename="hooks.json" data={hooksData} />
|
||||
<script>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
||||
import Modal from '../components/Modal.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
|
||||
const base = import.meta.env.BASE_URL;
|
||||
---
|
||||
@@ -31,7 +33,10 @@ const base = import.meta.env.BASE_URL;
|
||||
<!-- Hero Section -->
|
||||
<section class="hero" aria-labelledby="hero-heading">
|
||||
<div class="container">
|
||||
<h1 id="hero-heading">Awesome GitHub Copilot</h1>
|
||||
<h1 id="hero-heading">
|
||||
<span class="gradient-text">Awesome</span>
|
||||
<span class="gradient-text-alt">GitHub Copilot</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">Community-contributed agents, instructions, and skills to enhance your GitHub Copilot experience</p>
|
||||
<div class="hero-search">
|
||||
<label for="global-search" class="sr-only">Search all resources</label>
|
||||
@@ -39,13 +44,21 @@ const base = import.meta.env.BASE_URL;
|
||||
Type at least two characters to show matching resources, then press the Down Arrow key to move into the results.
|
||||
</p>
|
||||
<p id="global-search-status" class="sr-only" aria-live="polite"></p>
|
||||
<input
|
||||
type="text"
|
||||
id="global-search"
|
||||
placeholder="Search all resources..."
|
||||
autocomplete="off"
|
||||
aria-describedby="global-search-help global-search-status"
|
||||
>
|
||||
<div class="search-row">
|
||||
<input
|
||||
type="text"
|
||||
id="global-search"
|
||||
placeholder="Search all resources..."
|
||||
autocomplete="off"
|
||||
aria-describedby="global-search-help global-search-status"
|
||||
>
|
||||
<a href="https://github.com/github/awesome-copilot" target="_blank" rel="noopener" class="github-btn" aria-label="View on GitHub">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="search-results" class="search-results hidden" aria-label="Search results"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,64 +69,72 @@ const base = import.meta.env.BASE_URL;
|
||||
<h2 id="quick-links-heading" class="sr-only">Browse Resources</h2>
|
||||
<div class="container">
|
||||
<div class="cards-grid">
|
||||
<a href={`${base}agents/`} class="card card-with-count" id="card-agents">
|
||||
<div class="card-icon" aria-hidden="true">🤖</div>
|
||||
<a href={`${base}agents/`} class="card card-with-count card-category-ai" id="card-agents" style="--animation-delay: 0ms;">
|
||||
<div class="card-icon" aria-hidden="true"><Icon name="robot" size={40} /></div>
|
||||
<div class="card-content">
|
||||
<h3>Agents</h3>
|
||||
<p>Custom agents for specialized Copilot experiences</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="agents" aria-label="Agent count">-</div>
|
||||
</a>
|
||||
<a href={`${base}instructions/`} class="card card-with-count" id="card-instructions">
|
||||
<div class="card-icon" aria-hidden="true">📋</div>
|
||||
<a href={`${base}instructions/`} class="card card-with-count card-category-docs" id="card-instructions" style="--animation-delay: 50ms;">
|
||||
<div class="card-icon" aria-hidden="true"><Icon name="document" size={40} /></div>
|
||||
<div class="card-content">
|
||||
<h3>Instructions</h3>
|
||||
<p>Coding standards and best practices for Copilot</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="instructions" aria-label="Instruction count">-</div>
|
||||
</a>
|
||||
<a href={`${base}skills/`} class="card card-with-count" id="card-skills">
|
||||
<div class="card-icon" aria-hidden="true">⚡</div>
|
||||
<a href={`${base}skills/`} class="card card-with-count card-category-power" id="card-skills" style="--animation-delay: 100ms;">
|
||||
<div class="card-icon" aria-hidden="true"><Icon name="lightning" size={40} /></div>
|
||||
<div class="card-content">
|
||||
<h3>Skills</h3>
|
||||
<p>Self-contained folders with instructions and resources</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="skills" aria-label="Skill count">-</div>
|
||||
</a>
|
||||
<a href={`${base}hooks/`} class="card card-with-count" id="card-hooks">
|
||||
<div class="card-icon" aria-hidden="true">🪝</div>
|
||||
<a href={`${base}hooks/`} class="card card-with-count card-category-automation" id="card-hooks" style="--animation-delay: 150ms;">
|
||||
<div class="card-icon" aria-hidden="true"><Icon name="hook" size={40} /></div>
|
||||
<div class="card-content">
|
||||
<h3>Hooks</h3>
|
||||
<p>Automated workflows triggered by agent events</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="hooks" aria-label="Hook count">-</div>
|
||||
</a>
|
||||
<a href={`${base}workflows/`} class="card card-with-count" id="card-workflows">
|
||||
<div class="card-icon" aria-hidden="true">⚡</div>
|
||||
<a href={`${base}workflows/`} class="card card-with-count card-category-automation" id="card-workflows" style="--animation-delay: 200ms;">
|
||||
<div class="card-icon" aria-hidden="true">
|
||||
<Icon name="workflow" size={40} />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3>Workflows</h3>
|
||||
<p>AI-powered automations for GitHub Actions</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="workflows" aria-label="Workflow count">-</div>
|
||||
</a>
|
||||
<a href={`${base}plugins/`} class="card card-with-count" id="card-plugins">
|
||||
<div class="card-icon" aria-hidden="true">🔌</div>
|
||||
<a href={`${base}plugins/`} class="card card-with-count card-category-extension" id="card-plugins" style="--animation-delay: 250ms;">
|
||||
<div class="card-icon" aria-hidden="true">
|
||||
<Icon name="plug" size={40} />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3>Plugins</h3>
|
||||
<p>Curated plugins organized by themes</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="plugins" aria-label="Plugin count">-</div>
|
||||
</a>
|
||||
<a href={`${base}tools/`} class="card card-with-count" id="card-tools">
|
||||
<div class="card-icon" aria-hidden="true">🔧</div>
|
||||
<a href={`${base}tools/`} class="card card-with-count card-category-dev" id="card-tools" style="--animation-delay: 300ms;">
|
||||
<div class="card-icon" aria-hidden="true">
|
||||
<Icon name="wrench" size={40} />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3>Tools</h3>
|
||||
<p>MCP servers and developer tools</p>
|
||||
</div>
|
||||
<div class="card-count" data-count="tools" aria-label="Tool count">-</div>
|
||||
</a>
|
||||
<a href={`${base}learning-hub/`} class="card card-with-count" id="card-learning-hub">
|
||||
<div class="card-icon" aria-hidden="true">📚</div>
|
||||
<a href={`${base}learning-hub/`} class="card card-with-count card-category-learn" id="card-learning-hub" style="--animation-delay: 350ms;">
|
||||
<div class="card-icon" aria-hidden="true">
|
||||
<Icon name="book" size={40} />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3>Learning Hub</h3>
|
||||
<p>Articles and guides to master GitHub Copilot</p>
|
||||
@@ -127,6 +148,9 @@ const base = import.meta.env.BASE_URL;
|
||||
|
||||
<Modal />
|
||||
|
||||
<!-- Back to Top Button -->
|
||||
<BackToTop />
|
||||
|
||||
<script>
|
||||
import '../scripts/pages/index';
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import { renderInstructionsHtml, sortInstructions } from '../scripts/pages/instructions-render';
|
||||
|
||||
const initialItems = sortInstructions(instructionsData.items, 'title');
|
||||
@@ -12,7 +13,7 @@ const initialItems = sortInstructions(instructionsData.items, 'title');
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Instructions', description: 'Coding standards and best practices for GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="📋 Instructions" description="Coding standards and best practices for GitHub Copilot" />
|
||||
<PageHeader title="Instructions" description="Coding standards and best practices for GitHub Copilot" icon="document" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -46,6 +47,7 @@ const initialItems = sortInstructions(instructionsData.items, 'title');
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
<EmbeddedPageData filename="instructions.json" data={instructionsData} />
|
||||
|
||||
<script>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import { renderPluginsHtml } from '../scripts/pages/plugins-render';
|
||||
|
||||
const initialItems = pluginsData.items;
|
||||
@@ -12,7 +13,7 @@ const initialItems = pluginsData.items;
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Plugins', description: 'Curated plugins of agents, hooks, and skills for specific workflows', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="🔌 Plugins" description="Curated plugins of agents, hooks, and skills for specific workflows" />
|
||||
<PageHeader title="Plugins" description="Curated plugins of agents, hooks, and skills for specific workflows" icon="plug" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -48,6 +49,7 @@ const initialItems = pluginsData.items;
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
<EmbeddedPageData filename="plugins.json" data={pluginsData} />
|
||||
|
||||
<script>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import skillsData from '../../public/data/skills.json';
|
||||
import { renderSkillsHtml, sortSkills } from '../scripts/pages/skills-render';
|
||||
|
||||
@@ -12,7 +13,7 @@ const initialItems = sortSkills(skillsData.items, 'title');
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Skills', description: 'Self-contained agent skills with instructions and bundled resources', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="⚡ Skills" description="Self-contained agent skills with instructions and bundled resources" />
|
||||
<PageHeader title="Skills" description="Self-contained agent skills with instructions and bundled resources" icon="lightning" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -52,6 +53,7 @@ const initialItems = sortSkills(skillsData.items, 'title');
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
|
||||
<EmbeddedPageData filename="skills.json" data={skillsData} />
|
||||
<script>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
||||
import toolsData from '../../public/data/tools.json';
|
||||
import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from "../components/ContributeCTA.astro";
|
||||
import EmbeddedPageData from "../components/EmbeddedPageData.astro";
|
||||
import PageHeader from "../components/PageHeader.astro";
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import { renderToolsHtml } from "../scripts/pages/tools-render";
|
||||
|
||||
const initialItems = toolsData.items.map((item) => ({
|
||||
@@ -14,7 +16,7 @@ const initialItems = toolsData.items.map((item) => ({
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Tools', description: 'MCP servers and developer tools for GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="🔧 Tools" description="MCP servers and developer tools for GitHub Copilot" />
|
||||
<PageHeader title="Tools" description="MCP servers and developer tools for GitHub Copilot" icon="wrench" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -57,6 +59,8 @@ const initialItems = toolsData.items.map((item) => ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
<EmbeddedPageData filename="tools.json" data={toolsData} />
|
||||
|
||||
<style is:global>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Modal from '../components/Modal.astro';
|
||||
import ContributeCTA from '../components/ContributeCTA.astro';
|
||||
import EmbeddedPageData from '../components/EmbeddedPageData.astro';
|
||||
import PageHeader from '../components/PageHeader.astro';
|
||||
import BackToTop from '../components/BackToTop.astro';
|
||||
import { renderWorkflowsHtml, sortWorkflows } from '../scripts/pages/workflows-render';
|
||||
|
||||
const initialItems = sortWorkflows(workflowsData.items, 'title');
|
||||
@@ -12,9 +13,7 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
|
||||
|
||||
<StarlightPage frontmatter={{ title: 'Agentic Workflows', description: 'AI-powered repository automations that run coding agents in GitHub Actions', template: 'splash', prev: false, next: false, editUrl: false }}>
|
||||
<div id="main-content">
|
||||
<PageHeader title="⚡ Agentic Workflows" description="">
|
||||
AI-powered repository automations that run coding agents in <a href="https://gh.io/gh-aw" target="_blank" rel="noopener">GitHub Actions</a>
|
||||
</PageHeader>
|
||||
<PageHeader title="Agentic Workflows" description="AI-powered repository automations that run coding agents in GitHub Actions" icon="workflow" />
|
||||
|
||||
<div class="page-content">
|
||||
<div class="container">
|
||||
@@ -48,6 +47,7 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
|
||||
</div>
|
||||
|
||||
<Modal />
|
||||
<BackToTop />
|
||||
<EmbeddedPageData filename="workflows.json" data={workflowsData} />
|
||||
|
||||
<script>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
shareFile,
|
||||
getResourceType,
|
||||
escapeHtml,
|
||||
getResourceIcon,
|
||||
getResourceIconSvg,
|
||||
sanitizeUrl,
|
||||
} from "./utils";
|
||||
import fm from "front-matter";
|
||||
@@ -489,6 +489,13 @@ function handleModalKeydown(e: KeyboardEvent, modal: HTMLElement): void {
|
||||
*/
|
||||
export function setupModal(): void {
|
||||
const modal = document.getElementById("file-modal");
|
||||
|
||||
// Move modal to body level to escape ancestor stacking contexts
|
||||
// This fixes the issue where modal appears below header/theme-toggle
|
||||
if (modal && modal.parentElement !== document.body) {
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
const closeBtn = document.getElementById("close-modal");
|
||||
const copyBtn = document.getElementById("copy-btn");
|
||||
const downloadBtn = document.getElementById("download-btn");
|
||||
@@ -867,6 +874,7 @@ export async function openFileModal(
|
||||
const fallbackName = getFileName(filePath);
|
||||
updateModalTitle(fallbackName, filePath);
|
||||
modal.classList.remove("hidden");
|
||||
modal.classList.add("visible");
|
||||
|
||||
// Set focus to close button for accessibility
|
||||
setTimeout(() => {
|
||||
@@ -1145,7 +1153,7 @@ function renderLocalPluginModal(
|
||||
<div class="collection-item" data-path="${escapeHtml(
|
||||
item.path
|
||||
)}" data-type="${escapeHtml(item.kind)}">
|
||||
<span class="collection-item-icon">${getResourceIcon(
|
||||
<span class="collection-item-icon">${getResourceIconSvg(
|
||||
item.kind
|
||||
)}</span>
|
||||
<div class="collection-item-info">
|
||||
@@ -1191,6 +1199,7 @@ export function closeModal(updateUrl = true): void {
|
||||
|
||||
if (modal) {
|
||||
modal.classList.add("hidden");
|
||||
modal.classList.remove("visible");
|
||||
}
|
||||
if (installDropdown) {
|
||||
installDropdown.classList.remove("open");
|
||||
|
||||
@@ -5,6 +5,46 @@ import { FuzzySearch, type SearchItem } from '../search';
|
||||
import { fetchData, debounce, escapeHtml, truncate, getResourceIcon } from '../utils';
|
||||
import { setupModal, openFileModal } from '../modal';
|
||||
|
||||
// SVG icon definitions for search results
|
||||
// Icons with `fill: true` use fill="currentColor", others use stroke
|
||||
const iconDefs: Record<string, { path: string; fill?: boolean }> = {
|
||||
// Agent icon - GitHub Primer's agent-24
|
||||
robot: {
|
||||
fill: true,
|
||||
path: '<path d="M22.5 13.919v-.278a5.097 5.097 0 0 0-4.961-5.086.858.858 0 0 1-.754-.497l-.149-.327A6.414 6.414 0 0 0 10.81 4a6.133 6.133 0 0 0-6.13 6.32l.019.628a.863.863 0 0 1-.67.869A3.263 3.263 0 0 0 1.5 14.996v.108A3.397 3.397 0 0 0 4.896 18.5h1.577a.75.75 0 0 1 0 1.5H4.896A4.896 4.896 0 0 1 0 15.104v-.108a4.761 4.761 0 0 1 3.185-4.493l-.004-.137A7.633 7.633 0 0 1 10.81 2.5a7.911 7.911 0 0 1 7.176 4.58C21.36 7.377 24 10.207 24 13.641v.278a.75.75 0 0 1-1.5 0Z"/><path d="m12.306 11.77 3.374 3.375a.749.749 0 0 1 0 1.061l-3.375 3.375-.057.051a.751.751 0 0 1-1.004-.051.751.751 0 0 1-.051-1.004l.051-.057 2.845-2.845-2.844-2.844a.75.75 0 1 1 1.061-1.061ZM22.5 19.8H18a.75.75 0 0 1 0-1.5h4.5a.75.75 0 0 1 0 1.5Z"/>'
|
||||
},
|
||||
document: {
|
||||
path: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>'
|
||||
},
|
||||
lightning: {
|
||||
path: '<path d="M13 2 4.09 12.11a1.23 1.23 0 0 0 .13 1.72l.16.14a1.23 1.23 0 0 0 1.52 0L13 9.5V22l8.91-10.11a1.23 1.23 0 0 0-.13-1.72l-.16-.14a1.23 1.23 0 0 0-1.52 0L13 14.5V2Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>'
|
||||
},
|
||||
// Hook icon - GitHub Primer's sync-24
|
||||
hook: {
|
||||
fill: true,
|
||||
path: '<path d="M3.38 8A9.502 9.502 0 0 1 12 2.5a9.502 9.502 0 0 1 9.215 7.182.75.75 0 1 0 1.456-.364C21.473 4.539 17.15 1 12 1a10.995 10.995 0 0 0-9.5 5.452V4.75a.75.75 0 0 0-1.5 0V8.5a1 1 0 0 0 1 1h3.75a.75.75 0 0 0 0-1.5H3.38Zm-.595 6.318a.75.75 0 0 0-1.455.364C2.527 19.461 6.85 23 12 23c4.052 0 7.592-2.191 9.5-5.451v1.701a.75.75 0 0 0 1.5 0V15.5a1 1 0 0 0-1-1h-3.75a.75.75 0 0 0 0 1.5h2.37A9.502 9.502 0 0 1 12 21.5c-4.446 0-8.181-3.055-9.215-7.182Z"/>'
|
||||
},
|
||||
// Workflow icon - GitHub Primer's workflow-24
|
||||
workflow: {
|
||||
fill: true,
|
||||
path: '<path d="M1 3a2 2 0 0 1 2-2h6.5a2 2 0 0 1 2 2v6.5a2 2 0 0 1-2 2H7v4.063C7 16.355 7.644 17 8.438 17H12.5v-2.5a2 2 0 0 1 2-2H21a2 2 0 0 1 2 2V21a2 2 0 0 1-2 2h-6.5a2 2 0 0 1-2-2v-2.5H8.437A2.939 2.939 0 0 1 5.5 15.562V11.5H3a2 2 0 0 1-2-2Zm2-.5a.5.5 0 0 0-.5.5v6.5a.5.5 0 0 0 .5.5h6.5a.5.5 0 0 0 .5-.5V3a.5.5 0 0 0-.5-.5ZM14.5 14a.5.5 0 0 0-.5.5V21a.5.5 0 0 0 .5.5H21a.5.5 0 0 0 .5-.5v-6.5a.5.5 0 0 0-.5-.5Z"/>'
|
||||
},
|
||||
// Plug icon - GitHub Primer's plug-24
|
||||
plug: {
|
||||
fill: true,
|
||||
path: '<path d="M7 11.5H2.938c-.794 0-1.438.644-1.438 1.437v8.313a.75.75 0 0 1-1.5 0v-8.312A2.939 2.939 0 0 1 2.937 10H7V6.151c0-.897.678-1.648 1.57-1.74l6.055-.626 1.006-1.174A1.752 1.752 0 0 1 16.96 2h1.29c.966 0 1.75.784 1.75 1.75V6h3.25a.75.75 0 0 1 0 1.5H20V14h3.25a.75.75 0 0 1 0 1.5H20v2.25a1.75 1.75 0 0 1-1.75 1.75h-1.29a1.75 1.75 0 0 1-1.329-.611l-1.006-1.174-6.055-.627A1.749 1.749 0 0 1 7 15.348Zm9.77-7.913v.001l-1.201 1.4a.75.75 0 0 1-.492.258l-6.353.657a.25.25 0 0 0-.224.249v9.196a.25.25 0 0 0 .224.249l6.353.657c.191.02.368.112.493.258l1.2 1.401a.252.252 0 0 0 .19.087h1.29a.25.25 0 0 0 .25-.25v-14a.25.25 0 0 0-.25-.25h-1.29a.252.252 0 0 0-.19.087Z"/>'
|
||||
},
|
||||
wrench: {
|
||||
path: '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>'
|
||||
}
|
||||
};
|
||||
|
||||
function getIconSvg(iconName: string): string {
|
||||
const icon = iconDefs[iconName] || iconDefs.document;
|
||||
const fill = icon.fill ? 'fill="currentColor"' : 'fill="none"';
|
||||
return `<svg viewBox="0 0 24 24" ${fill} aria-hidden="true">${icon.path}</svg>`;
|
||||
}
|
||||
|
||||
interface Manifest {
|
||||
counts: {
|
||||
agents: number;
|
||||
@@ -31,6 +71,38 @@ interface PluginsData {
|
||||
items: Plugin[];
|
||||
}
|
||||
|
||||
// Recent searches storage
|
||||
const RECENT_SEARCHES_KEY = 'awesome-copilot-recent-searches';
|
||||
const MAX_RECENT_SEARCHES = 5;
|
||||
|
||||
function getRecentSearches(): string[] {
|
||||
try {
|
||||
const stored = localStorage.getItem(RECENT_SEARCHES_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function addRecentSearch(query: string): void {
|
||||
if (!query.trim()) return;
|
||||
const searches = getRecentSearches();
|
||||
const filtered = searches.filter(s => s.toLowerCase() !== query.toLowerCase());
|
||||
filtered.unshift(query);
|
||||
const limited = filtered.slice(0, MAX_RECENT_SEARCHES);
|
||||
localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(limited));
|
||||
}
|
||||
|
||||
function removeRecentSearch(query: string): void {
|
||||
const searches = getRecentSearches();
|
||||
const filtered = searches.filter(s => s !== query);
|
||||
localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(filtered));
|
||||
}
|
||||
|
||||
function clearRecentSearches(): void {
|
||||
localStorage.removeItem(RECENT_SEARCHES_KEY);
|
||||
}
|
||||
|
||||
export async function initHomepage(): Promise<void> {
|
||||
// Load manifest for stats
|
||||
const manifest = await fetchData<Manifest>('manifest.json');
|
||||
@@ -56,9 +128,11 @@ export async function initHomepage(): Promise<void> {
|
||||
|
||||
if (searchInput && resultsDiv) {
|
||||
const statusEl = document.getElementById("global-search-status");
|
||||
let isShowingRecent = false;
|
||||
|
||||
const hideResults = (): void => {
|
||||
resultsDiv.classList.add("hidden");
|
||||
isShowingRecent = false;
|
||||
};
|
||||
|
||||
const showResults = (): void => {
|
||||
@@ -67,7 +141,7 @@ export async function initHomepage(): Promise<void> {
|
||||
|
||||
const getResultButtons = (): HTMLButtonElement[] =>
|
||||
Array.from(
|
||||
resultsDiv.querySelectorAll<HTMLButtonElement>(".search-result")
|
||||
resultsDiv.querySelectorAll<HTMLButtonElement>(".search-result, .search-recent-item")
|
||||
);
|
||||
|
||||
const openResult = (resultEl: HTMLElement): void => {
|
||||
@@ -79,33 +153,125 @@ export async function initHomepage(): Promise<void> {
|
||||
}
|
||||
};
|
||||
|
||||
// Render recent searches
|
||||
const renderRecentSearches = (): void => {
|
||||
const recent = getRecentSearches();
|
||||
if (recent.length === 0) return;
|
||||
|
||||
const clockIcon = `<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`;
|
||||
const xIcon = `<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
||||
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="search-recent-header">
|
||||
<span>Recent Searches</span>
|
||||
<button class="search-clear-recent" aria-label="Clear recent searches">Clear</button>
|
||||
</div>
|
||||
${recent.map(query => `
|
||||
<button type="button" class="search-recent-item" data-query="${escapeHtml(query)}">
|
||||
<span class="search-recent-icon">${clockIcon}</span>
|
||||
<span class="search-recent-text">${escapeHtml(query)}</span>
|
||||
<button type="button" class="search-recent-remove" data-query="${escapeHtml(query)}" aria-label="Remove from history">
|
||||
${xIcon}
|
||||
</button>
|
||||
</button>
|
||||
`).join('')}
|
||||
`;
|
||||
|
||||
// Add click handlers for recent items
|
||||
resultsDiv.querySelectorAll('.search-recent-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.closest('.search-recent-remove')) return;
|
||||
const query = (item as HTMLElement).dataset.query;
|
||||
if (query) {
|
||||
searchInput.value = query;
|
||||
searchInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add click handlers for remove buttons
|
||||
resultsDiv.querySelectorAll('.search-recent-remove').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const query = (btn as HTMLElement).dataset.query;
|
||||
if (query) {
|
||||
removeRecentSearch(query);
|
||||
renderRecentSearches();
|
||||
if (getRecentSearches().length === 0) {
|
||||
hideResults();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add clear all handler
|
||||
const clearBtn = resultsDiv.querySelector('.search-clear-recent');
|
||||
clearBtn?.addEventListener('click', () => {
|
||||
clearRecentSearches();
|
||||
hideResults();
|
||||
});
|
||||
|
||||
isShowingRecent = true;
|
||||
showResults();
|
||||
};
|
||||
|
||||
// Show recent searches on focus when empty
|
||||
searchInput.addEventListener('focus', () => {
|
||||
if (searchInput.value.trim().length === 0) {
|
||||
renderRecentSearches();
|
||||
}
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', debounce(() => {
|
||||
const query = searchInput.value.trim();
|
||||
if (query.length < 2) {
|
||||
resultsDiv.innerHTML = '';
|
||||
if (query.length === 0) {
|
||||
renderRecentSearches();
|
||||
} else {
|
||||
resultsDiv.innerHTML = '';
|
||||
hideResults();
|
||||
}
|
||||
if (statusEl) {
|
||||
statusEl.textContent = '';
|
||||
}
|
||||
hideResults();
|
||||
return;
|
||||
}
|
||||
|
||||
isShowingRecent = false;
|
||||
const results = search.search(query).slice(0, 10);
|
||||
if (results.length === 0) {
|
||||
resultsDiv.innerHTML = '<div class="search-result-empty">No results found</div>';
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="search-result-empty">
|
||||
<div class="search-result-empty-icon">
|
||||
<svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="M21 21l-4.35-4.35"/>
|
||||
<path d="M8 8l6 6M14 8l-6 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="search-result-empty-title">No results found</div>
|
||||
<div class="search-result-empty-hint">Try different keywords or check your spelling</div>
|
||||
</div>
|
||||
`;
|
||||
if (statusEl) {
|
||||
statusEl.textContent = 'No results found.';
|
||||
}
|
||||
} else {
|
||||
resultsDiv.innerHTML = results.map(item => `
|
||||
// Add to recent searches when user gets results
|
||||
addRecentSearch(query);
|
||||
|
||||
resultsDiv.innerHTML = results.map(item => {
|
||||
const iconName = getResourceIcon(item.type);
|
||||
return `
|
||||
<button type="button" class="search-result" data-path="${escapeHtml(item.path)}" data-type="${escapeHtml(item.type)}">
|
||||
<span class="search-result-type">${getResourceIcon(item.type)}</span>
|
||||
<span class="search-result-type" data-icon="${iconName}">${getIconSvg(iconName)}</span>
|
||||
<div>
|
||||
<div class="search-result-title">${search.highlight(item.title, query)}</div>
|
||||
<div class="search-result-description">${truncate(item.description, 60)}</div>
|
||||
</div>
|
||||
</button>
|
||||
`).join('');
|
||||
`}).join('');
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.textContent = `${results.length} result${results.length === 1 ? '' : 's'} available.`;
|
||||
@@ -171,6 +337,15 @@ export async function initHomepage(): Promise<void> {
|
||||
hideResults();
|
||||
}
|
||||
});
|
||||
|
||||
// Cmd/Ctrl + K to focus search
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
searchInput.focus();
|
||||
searchInput.select();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -344,18 +344,58 @@ export function formatResourceType(type: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon for resource type
|
||||
* Get icon for resource type (returns SVG icon name)
|
||||
*/
|
||||
export function getResourceIcon(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
agent: "🤖",
|
||||
instruction: "📋",
|
||||
skill: "⚡",
|
||||
hook: "🪝",
|
||||
workflow: "⚡",
|
||||
plugin: "🔌",
|
||||
agent: "robot",
|
||||
instruction: "document",
|
||||
skill: "lightning",
|
||||
hook: "hook",
|
||||
workflow: "workflow",
|
||||
plugin: "plug",
|
||||
};
|
||||
return icons[type] || "📄";
|
||||
return icons[type] || "document";
|
||||
}
|
||||
|
||||
// Icon definitions with fill/stroke type info
|
||||
const iconDefs: Record<string, { path: string; fill?: boolean }> = {
|
||||
// Agent icon - GitHub Primer's agent-24
|
||||
robot: {
|
||||
fill: true,
|
||||
path: '<path d="M22.5 13.919v-.278a5.097 5.097 0 0 0-4.961-5.086.858.858 0 0 1-.754-.497l-.149-.327A6.414 6.414 0 0 0 10.81 4a6.133 6.133 0 0 0-6.13 6.32l.019.628a.863.863 0 0 1-.67.869A3.263 3.263 0 0 0 1.5 14.996v.108A3.397 3.397 0 0 0 4.896 18.5h1.577a.75.75 0 0 1 0 1.5H4.896A4.896 4.896 0 0 1 0 15.104v-.108a4.761 4.761 0 0 1 3.185-4.493l-.004-.137A7.633 7.633 0 0 1 10.81 2.5a7.911 7.911 0 0 1 7.176 4.58C21.36 7.377 24 10.207 24 13.641v.278a.75.75 0 0 1-1.5 0Z"/><path d="m12.306 11.77 3.374 3.375a.749.749 0 0 1 0 1.061l-3.375 3.375-.057.051a.751.751 0 0 1-1.004-.051.751.751 0 0 1-.051-1.004l.051-.057 2.845-2.845-2.844-2.844a.75.75 0 1 1 1.061-1.061ZM22.5 19.8H18a.75.75 0 0 1 0-1.5h4.5a.75.75 0 0 1 0 1.5Z"/>',
|
||||
},
|
||||
document: {
|
||||
path: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>',
|
||||
},
|
||||
lightning: {
|
||||
path: '<path d="M13 2 4.09 12.11a1.23 1.23 0 0 0 .13 1.72l.16.14a1.23 1.23 0 0 0 1.52 0L13 9.5V22l8.91-10.11a1.23 1.23 0 0 0-.13-1.72l-.16-.14a1.23 1.23 0 0 0-1.52 0L13 14.5V2Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>',
|
||||
},
|
||||
// Hook icon - GitHub Primer's sync-24
|
||||
hook: {
|
||||
fill: true,
|
||||
path: '<path d="M3.38 8A9.502 9.502 0 0 1 12 2.5a9.502 9.502 0 0 1 9.215 7.182.75.75 0 1 0 1.456-.364C21.473 4.539 17.15 1 12 1a10.995 10.995 0 0 0-9.5 5.452V4.75a.75.75 0 0 0-1.5 0V8.5a1 1 0 0 0 1 1h3.75a.75.75 0 0 0 0-1.5H3.38Zm-.595 6.318a.75.75 0 0 0-1.455.364C2.527 19.461 6.85 23 12 23c4.052 0 7.592-2.191 9.5-5.451v1.701a.75.75 0 0 0 1.5 0V15.5a1 1 0 0 0-1-1h-3.75a.75.75 0 0 0 0 1.5h2.37A9.502 9.502 0 0 1 12 21.5c-4.446 0-8.181-3.055-9.215-7.182Z"/>',
|
||||
},
|
||||
// Workflow icon - GitHub Primer's workflow-24
|
||||
workflow: {
|
||||
fill: true,
|
||||
path: '<path d="M1 3a2 2 0 0 1 2-2h6.5a2 2 0 0 1 2 2v6.5a2 2 0 0 1-2 2H7v4.063C7 16.355 7.644 17 8.438 17H12.5v-2.5a2 2 0 0 1 2-2H21a2 2 0 0 1 2 2V21a2 2 0 0 1-2 2h-6.5a2 2 0 0 1-2-2v-2.5H8.437A2.939 2.939 0 0 1 5.5 15.562V11.5H3a2 2 0 0 1-2-2Zm2-.5a.5.5 0 0 0-.5.5v6.5a.5.5 0 0 0 .5.5h6.5a.5.5 0 0 0 .5-.5V3a.5.5 0 0 0-.5-.5ZM14.5 14a.5.5 0 0 0-.5.5V21a.5.5 0 0 0 .5.5H21a.5.5 0 0 0 .5-.5v-6.5a.5.5 0 0 0-.5-.5Z"/>',
|
||||
},
|
||||
// Plug icon - GitHub Primer's plug-24
|
||||
plug: {
|
||||
fill: true,
|
||||
path: '<path d="M7 11.5H2.938c-.794 0-1.438.644-1.438 1.437v8.313a.75.75 0 0 1-1.5 0v-8.312A2.939 2.939 0 0 1 2.937 10H7V6.151c0-.897.678-1.648 1.57-1.74l6.055-.626 1.006-1.174A1.752 1.752 0 0 1 16.96 2h1.29c.966 0 1.75.784 1.75 1.75V6h3.25a.75.75 0 0 1 0 1.5H20V14h3.25a.75.75 0 0 1 0 1.5H20v2.25a1.75 1.75 0 0 1-1.75 1.75h-1.29a1.75 1.75 0 0 1-1.329-.611l-1.006-1.174-6.055-.627A1.749 1.749 0 0 1 7 15.348Zm9.77-7.913v.001l-1.201 1.4a.75.75 0 0 1-.492.258l-6.353.657a.25.25 0 0 0-.224.249v9.196a.25.25 0 0 0 .224.249l6.353.657c.191.02.368.112.493.258l1.2 1.401a.252.252 0 0 0 .19.087h1.29a.25.25 0 0 0 .25-.25v-14a.25.25 0 0 0-.25-.25h-1.29a.252.252 0 0 0-.19.087Z"/>',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get SVG icon HTML for resource type
|
||||
*/
|
||||
export function getResourceIconSvg(type: string, size = 20): string {
|
||||
const iconName = getResourceIcon(type);
|
||||
const icon = iconDefs[iconName] || iconDefs.document;
|
||||
const fill = icon.fill ? 'fill="currentColor"' : 'fill="none"';
|
||||
return `<svg viewBox="0 0 24 24" width="${size}" height="${size}" ${fill} aria-hidden="true">${icon.path}</svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@
|
||||
--sl-color-gray-3: #60607a;
|
||||
--sl-color-gray-4: #30304a;
|
||||
--sl-color-gray-5: #1a1a2e;
|
||||
--sl-color-gray-6: #111120;
|
||||
--sl-color-black: #0a0a0f;
|
||||
--sl-color-gray-6: #0d0d12;
|
||||
--sl-color-black: #0d0d12;
|
||||
|
||||
--sl-font-system: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica,
|
||||
Arial, sans-serif;
|
||||
@@ -37,8 +37,13 @@
|
||||
--sl-color-gray-3: #30304a;
|
||||
--sl-color-gray-4: #60607a;
|
||||
--sl-color-gray-5: #9898a6;
|
||||
--sl-color-gray-6: #d0d0da;
|
||||
--sl-color-black: #f0f0f5;
|
||||
--sl-color-gray-6: #f5f5f7;
|
||||
--sl-color-black: #fafafa;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] header.header {
|
||||
background: rgba(250, 250, 250, 0.6) !important;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
|
||||
/* ── Sidebar readability ───────────────────────────────────── */
|
||||
@@ -60,6 +65,55 @@ header .site-title:hover {
|
||||
color: var(--sl-color-accent-high) !important;
|
||||
}
|
||||
|
||||
/* ── Unified background ─────────────────────────────────────── */
|
||||
/* Clean header - solid background */
|
||||
header.header {
|
||||
background: var(--sl-color-gray-6) !important;
|
||||
border-bottom: 1px solid var(--sl-color-gray-5) !important;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Header inner content for alignment */
|
||||
header.header .header-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
header.header .header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Hide social icons in header - GitHub link moved to hero */
|
||||
header .social-icons {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Move theme toggle inside header - align with header elements */
|
||||
.theme-toggle-container {
|
||||
position: fixed !important;
|
||||
top: 12px !important;
|
||||
right: 80px !important;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
/* On custom splash pages, body already has the gradient bg.
|
||||
Make Starlight's wrapper layers transparent so it shows through. */
|
||||
body:has(#main-content) {
|
||||
--sl-color-bg: transparent;
|
||||
--sl-color-bg-nav: transparent;
|
||||
}
|
||||
|
||||
/* ── Hide Starlight's built-in theme selector (dropdown) ─── */
|
||||
/* We use our own custom ThemeToggle component instead */
|
||||
starlight-theme-select {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ── Browse Resources sidebar group ────────────────────────── */
|
||||
nav[aria-label="Main"] > ul > li:first-child details summary,
|
||||
nav[aria-label="Main"] > ul > li:first-child > a {
|
||||
|
||||
Reference in New Issue
Block a user