diff --git a/frontend/src/components/examples/ExamplesGallery.css b/frontend/src/components/examples/ExamplesGallery.css index bd87d95c..54735a36 100644 --- a/frontend/src/components/examples/ExamplesGallery.css +++ b/frontend/src/components/examples/ExamplesGallery.css @@ -2,37 +2,139 @@ * Examples Gallery — Apple Dark Design System */ +/* Shared content width — wide enough to show many example columns on big + screens while staying readable. Was 1200px (capped the grid at ~3 cols). */ .examples-gallery { + --examples-max: 1680px; min-height: 100vh; background: var(--bg); color: var(--text); - padding: 52px 40px; + padding: 28px 32px 48px; font-family: var(--font); -webkit-font-smoothing: antialiased; } .examples-header { text-align: center; - margin-bottom: 52px; - max-width: 1200px; - margin-left: auto; - margin-right: auto; + margin: 0 auto 20px auto; + max-width: var(--examples-max); } .examples-header h1 { - font-size: clamp(32px, 5vw, 52px); - margin: 0 0 14px 0; + font-size: clamp(26px, 3.4vw, 40px); + margin: 0 0 6px 0; font-weight: 700; - letter-spacing: -1.5px; + letter-spacing: -1px; color: var(--text); } .examples-header p { - font-size: 17px; + font-size: 15px; color: var(--text-muted); margin: 0; font-weight: 400; - line-height: 1.65; + line-height: 1.5; +} + +/* ── Compact toolbar (search + dropdowns) ─────────────────────────────── */ +.examples-toolbar { + max-width: var(--examples-max); + margin: 0 auto 12px auto; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.examples-select { + appearance: none; + -webkit-appearance: none; + padding: 9px 30px 9px 14px; + background: var(--bg-card); + color: var(--text); + border: 1px solid var(--border); + border-radius: var(--radius-pill); + font-family: var(--font); + font-size: 13px; + cursor: pointer; + outline: none; + transition: background 0.15s, border-color 0.15s; + /* chevron */ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; +} + +.examples-select:hover { + background-color: var(--bg-elevated); +} + +.examples-select:focus { + border-color: var(--accent, var(--color-action-primary)); +} + +.examples-count { + color: var(--text-muted); + font-size: 13px; + white-space: nowrap; + margin-left: auto; +} + +/* ── Active-filter chips (removable badges) ───────────────────────────── */ +.examples-chips { + max-width: var(--examples-max); + margin: 0 auto 18px auto; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.filter-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 6px 4px 12px; + background: color-mix(in srgb, var(--accent, #007acc) 18%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, #007acc) 45%, transparent); + color: var(--text); + border-radius: var(--radius-pill); + font-size: 12px; + font-weight: 500; +} + +.filter-chip-x { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border: none; + background: rgba(255, 255, 255, 0.12); + color: var(--text); + border-radius: 50%; + font-size: 14px; + line-height: 1; + cursor: pointer; + transition: background 0.15s; +} + +.filter-chip-x:hover { + background: rgba(255, 255, 255, 0.28); +} + +.filter-chip-clear-all { + background: none; + border: none; + color: var(--text-muted); + font-size: 12px; + cursor: pointer; + padding: 4px 6px; + text-decoration: underline; +} + +.filter-chip-clear-all:hover { + color: var(--text); } /* Board tabs */ @@ -143,46 +245,50 @@ font-weight: 500; } -/* Examples Grid */ +/* Examples Grid — denser: smaller min column so more cards fit per row + (was minmax(300px) capped at 1200px → ~3 cols). */ .examples-grid { - max-width: 1200px; + max-width: var(--examples-max); margin: 0 auto; display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 16px; + grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); + gap: 14px; } .example-card { background: var(--bg-card); - border-radius: 20px; + border-radius: 14px; overflow: hidden; cursor: pointer; transition: - transform 0.3s cubic-bezier(0.34, 1.46, 0.64, 1), - box-shadow 0.3s ease; + transform 0.2s cubic-bezier(0.34, 1.46, 0.64, 1), + box-shadow 0.2s ease; display: flex; flex-direction: column; } .example-card:hover { - transform: scale(1.025); - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); + transform: translateY(-3px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); } .example-thumbnail { width: 100%; - height: 180px; - background: #0a0a0c; + aspect-ratio: 5 / 3; + background: var(--bg-elevated, #17171a); display: flex; align-items: center; justify-content: center; overflow: hidden; } +/* Fill the whole thumbnail — `contain` left black bars on the sides of the + card (the 300×180 preview is wider than the denser cards). `cover` crops a + little instead, which reads far cleaner. */ .example-thumbnail img { width: 100%; height: 100%; - object-fit: contain; + object-fit: cover; } .example-preview-image { @@ -221,19 +327,20 @@ } .example-info { - padding: 20px 20px 18px; + padding: 12px 14px 12px; display: flex; flex-direction: column; - gap: 8px; + gap: 6px; flex: 1; } .example-title { - font-size: 16px; + font-size: 14px; font-weight: 600; margin: 0; color: var(--text); - letter-spacing: -0.3px; + letter-spacing: -0.2px; + line-height: 1.3; } .example-description { @@ -292,8 +399,8 @@ } .examples-empty { - max-width: 1200px; - margin: 80px auto; + max-width: var(--examples-max); + margin: 60px auto; text-align: center; color: var(--text-dim); font-size: 15px; @@ -329,7 +436,8 @@ } .examples-search-wrap { - flex: 1; + flex: 1 1 260px; + min-width: 220px; position: relative; display: flex; align-items: center; @@ -423,19 +531,23 @@ /* Responsive */ @media (max-width: 768px) { .examples-gallery { - padding: 32px 20px; + padding: 20px 16px 32px; } .examples-grid { - grid-template-columns: 1fr; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 10px; } - .filter-group { - flex-direction: column; - align-items: flex-start; + .examples-toolbar { + gap: 8px; } - .filter-group label { - min-width: auto; + .examples-search-wrap { + flex-basis: 100%; + } + + .examples-count { + margin-left: 0; } } diff --git a/frontend/src/components/examples/ExamplesGallery.tsx b/frontend/src/components/examples/ExamplesGallery.tsx index a45d27c3..595e6ca7 100644 --- a/frontend/src/components/examples/ExamplesGallery.tsx +++ b/frontend/src/components/examples/ExamplesGallery.tsx @@ -220,6 +220,58 @@ export const ExamplesGallery: React.FC = ({ onLoadExample return { label: tab.label, color: tab.color, bg: tab.bg }; }; + const CATEGORIES = [ + 'all', + 'basics', + 'sensors', + 'displays', + 'communication', + 'games', + 'robotics', + 'circuits', + ] as const; + const DIFFICULTIES = ['all', 'beginner', 'intermediate', 'advanced'] as const; + + // Active filters rendered as removable chips (the compact replacement for + // the old three rows of always-visible filter buttons). Each chip clears + // its own filter; "Clear all" resets everything. + const boardTabForChip = BOARD_TABS.find((tb) => tb.id === selectedBoard); + const activeChips: { key: string; label: string; onClear: () => void }[] = []; + if (selectedBoard !== 'all' && boardTabForChip) { + activeChips.push({ + key: 'board', + label: boardTabForChip.label, + onClear: () => setSelectedBoard('all'), + }); + } + if (selectedCategory !== 'all') { + activeChips.push({ + key: 'cat', + label: t(`examples.filters.category.${selectedCategory}`), + onClear: () => setSelectedCategory('all'), + }); + } + if (selectedDifficulty !== 'all') { + activeChips.push({ + key: 'diff', + label: t(`examples.filters.difficulty.${selectedDifficulty}`), + onClear: () => setSelectedDifficulty('all'), + }); + } + if (search.trim()) { + activeChips.push({ + key: 'search', + label: `“${search.trim()}”`, + onClear: () => setSearch(''), + }); + } + const resetAll = () => { + setSearch(''); + setSelectedBoard('all'); + setSelectedCategory('all'); + setSelectedDifficulty('all'); + }; + return (
@@ -227,8 +279,9 @@ export const ExamplesGallery: React.FC = ({ onLoadExample

{t('examples.subtitle')}

- {/* Search */} -
+ {/* Compact toolbar: search + three dropdowns + live count. + Replaces the old search row + 16 board tabs + two filter rows. */} +
= ({ onLoadExample )}
- {searchTokens.length > 0 && ( - - {t('examples.matchCount', { count: filteredExamples.length })} - - )} + + + + + + + + + {t('examples.matchCount', { count: filteredExamples.length })} +
- {/* Board tabs */} -
- {BOARD_TABS.map((tab) => ( - - ))} -
- - {/* Category + Difficulty filters */} -
-
- -
- {( - [ - 'all', - 'basics', - 'sensors', - 'displays', - 'communication', - 'games', - 'robotics', - 'circuits', - ] as const - ).map((cat) => ( + {/* Active-filter chips (removable badges). */} + {activeChips.length > 0 && ( +
+ {activeChips.map((chip) => ( + + {chip.label} - ))} -
+ + ))} + {activeChips.length > 1 && ( + + )}
- -
- -
- {(['all', 'beginner', 'intermediate', 'advanced'] as const).map((diff) => ( - - ))} -
-
-
+ )} {/* Examples Grid */}