Files
pbakaus_impeccable/public/js/components/art-gallery.js
T
Paul BakausandClaude Opus 4.5 0b9c1846a6 Add teach-impeccable and review commands, dynamic placeholders
New commands:
- /teach-impeccable: One-time setup that gathers UX-focused design
  context (explores codebase first, then asks targeted questions)
- /review: UX design review evaluating hierarchy, clarity, emotional
  resonance (complements technical /audit)

Build system improvements:
- Dynamic {{model}}, {{ask_instruction}}, {{config_file}} placeholders
  per provider (Claude/Gemini/GPT/the model)
- context: fork support for Claude Code sub-agents (audit, extract)
- Skill reference auto-added to design commands

Skill refinements:
- Merged Design Thinking and Context Awareness sections
- Removed redundant patterns and technical questions
- More direct feedback guidance (radical candor)

Website updates:
- 17 commands (was 16)
- Review demo showing UX issues (HIERARCHY, NO PRIMARY, DEAD END)
- teach-impeccable in System group of periodic table

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 16:56:08 -08:00

143 lines
4.4 KiB
JavaScript

import { readySkills, skillFocusAreas } from "../data.js";
import { renderSkillDemo, setupDemoTabs } from "../demo-renderer.js";
import { setupDemoToggles } from "../demo-toggles.js";
export function initArtGallery() {
// Initial setup if needed
}
export function renderGallery(skills) {
const container = document.querySelector(".skills-gallery");
if (!container) return;
// Filter skills (hide frontend-design as per original)
const filteredSkills = skills.filter((s) => s.id !== "frontend-design");
container.innerHTML = `
<div class="gallery-track">
${filteredSkills.map((skill, index) => renderFrame(skill, index)).join("")}
</div>
<div class="gallery-map" role="tablist" aria-label="Skill gallery navigation">
${filteredSkills.map((skill, index) => `<button class="gallery-dot ${index === 0 ? "active" : ""}" data-index="${index}" role="tab" aria-selected="${index === 0 ? "true" : "false"}" aria-label="View ${formatName(skill.id)} skill"></button>`).join("")}
</div>
`;
setupInteractions();
setupDemoTabs();
setupDemoToggles();
}
function renderFrame(skill, index) {
const isReady = readySkills.includes(skill.id);
const focusAreas = skillFocusAreas[skill.id] || [];
const displayName = formatName(skill.id);
return `
<article class="gallery-frame ${index === 0 ? "active" : ""}" data-index="${index}" id="skill-${skill.id}">
<div class="gallery-content">
<div class="gallery-visual">
${isReady ? renderSkillDemo(skill.id) : renderComingSoonVisual(skill.id)}
</div>
<div class="gallery-info">
<div class="gallery-header">
<h3 class="gallery-title">${displayName}</h3>
<div class="gallery-meta">
Skill · ${isReady ? "Available" : "Coming Soon"}
</div>
</div>
<p class="gallery-desc">${skill.description}</p>
${
focusAreas.length > 0
? `
<div class="gallery-tags">
${focusAreas
.slice(0, 4)
.map(
(area) => `
<span class="gallery-tag">${area.area}</span>
`,
)
.join("")}
</div>
`
: ""
}
</div>
</div>
</article>
`;
}
function renderComingSoonVisual(id) {
return `
<div class="coming-soon-placeholder" style="text-align: center;">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" style="opacity: 0.3">
<path d="M12 6v6l4 2M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
</svg>
<p style="margin-top: 1rem; color: var(--color-ash); font-size: 0.875rem;">Coming Soon</p>
</div>
`;
}
function formatName(id) {
return id
.split("-")
.map((word) =>
word === "ux" ? "UX" : word.charAt(0).toUpperCase() + word.slice(1),
)
.join(" ");
}
function setupInteractions() {
const track = document.querySelector(".gallery-track");
const frames = document.querySelectorAll(".gallery-frame");
const dots = document.querySelectorAll(".gallery-dot");
if (!track) return;
// Intersection Observer for Active State
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const index = entry.target.dataset.index;
// Update frames
frames.forEach((f) => f.classList.remove("active"));
entry.target.classList.add("active");
// Update dots
dots.forEach((d) => {
d.classList.remove("active");
d.setAttribute("aria-selected", "false");
});
if (dots[index]) {
dots[index].classList.add("active");
dots[index].setAttribute("aria-selected", "true");
}
}
});
},
{
root: track,
threshold: 0.6,
},
);
frames.forEach((frame) => observer.observe(frame));
// Dot navigation
dots.forEach((dot, index) => {
dot.addEventListener("click", () => {
const frame = frames[index];
if (frame) {
frame.scrollIntoView({ behavior: "smooth", inline: "center" });
}
});
});
}