mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
more fixes
This commit is contained in:
+59
-17
@@ -30,30 +30,72 @@ async function loadContent() {
|
||||
// Render commands (Glass Terminal)
|
||||
renderTerminalLayout(allCommands);
|
||||
|
||||
// Render patterns and antipatterns
|
||||
renderPatterns(patternsData.patterns, "patterns-grid", "pattern");
|
||||
renderPatterns(patternsData.antipatterns, "antipatterns-grid", "antipattern");
|
||||
// Render patterns with tabbed navigation
|
||||
renderPatternsWithTabs(patternsData.patterns, patternsData.antipatterns);
|
||||
} catch (error) {
|
||||
console.error("Failed to load content:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderPatterns(categories, containerId, classPrefix) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container || !categories) return;
|
||||
function renderPatternsWithTabs(patterns, antipatterns) {
|
||||
const container = document.getElementById("patterns-categories");
|
||||
if (!container || !patterns || !antipatterns) return;
|
||||
|
||||
container.innerHTML = categories
|
||||
.map(
|
||||
(category) => `
|
||||
<div class="${classPrefix}-category">
|
||||
<h3 class="${classPrefix}-category-title">${category.name}</h3>
|
||||
<ul class="${classPrefix}-list">
|
||||
${category.items.map((item) => `<li>${item}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
// Create a map of antipatterns by category name
|
||||
const antipatternMap = {};
|
||||
antipatterns.forEach(cat => {
|
||||
antipatternMap[cat.name] = cat.items;
|
||||
});
|
||||
|
||||
// Build tabs
|
||||
const tabsHTML = patterns
|
||||
.map((category, i) => `<button class="pattern-tab${i === 0 ? ' active' : ''}" data-tab="${category.name}">${category.name}</button>`)
|
||||
.join("");
|
||||
|
||||
// Build panels
|
||||
const panelsHTML = patterns
|
||||
.map((category, i) => {
|
||||
const antiItems = antipatternMap[category.name] || [];
|
||||
return `
|
||||
<div class="pattern-panel${i === 0 ? ' active' : ''}" data-panel="${category.name}">
|
||||
<div class="pattern-columns">
|
||||
<div class="pattern-column pattern-column--anti">
|
||||
<span class="pattern-column-label">Don't</span>
|
||||
<ul class="pattern-list">
|
||||
${antiItems.map((item) => `<li class="pattern-item pattern-item--anti">${item}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pattern-column pattern-column--do">
|
||||
<span class="pattern-column-label">Do</span>
|
||||
<ul class="pattern-list">
|
||||
${category.items.map((item) => `<li class="pattern-item pattern-item--do">${item}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="pattern-tabs">${tabsHTML}</div>
|
||||
<div class="pattern-panels">${panelsHTML}</div>
|
||||
`;
|
||||
|
||||
// Tab click handling
|
||||
container.querySelectorAll('.pattern-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
const tabName = tab.dataset.tab;
|
||||
|
||||
// Update active tab
|
||||
container.querySelectorAll('.pattern-tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
// Update active panel
|
||||
container.querySelectorAll('.pattern-panel').forEach(p => p.classList.remove('active'));
|
||||
container.querySelector(`[data-panel="${tabName}"]`).classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
+125
-158
@@ -371,14 +371,15 @@ code {
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.lens-comparison {
|
||||
/* Split Screen Comparison */
|
||||
.split-comparison {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.lens-container {
|
||||
.split-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
@@ -388,10 +389,12 @@ code {
|
||||
overflow: hidden;
|
||||
background: var(--color-cream);
|
||||
border: 1px solid var(--color-mist);
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.lens-before,
|
||||
.lens-after {
|
||||
.split-before,
|
||||
.split-after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
@@ -399,46 +402,45 @@ code {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.lens-before {
|
||||
.split-before {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lens-after {
|
||||
clip-path: circle(120px at var(--lens-x, 70%) var(--lens-y, 50%));
|
||||
transition: clip-path 0.08s ease-out;
|
||||
.split-after {
|
||||
/* Angled clip-path matching divider's skewX(-10deg): top-right, bottom-left */
|
||||
clip-path: polygon(78% 0%, 100% 0%, 100% 100%, 62% 100%);
|
||||
z-index: 2;
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.lens-circle {
|
||||
.split-divider {
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
border: 2px solid var(--color-accent);
|
||||
border-radius: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 70%;
|
||||
width: 3px;
|
||||
background: var(--color-accent);
|
||||
transform: translateX(-50%) skewX(-10deg);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
left: var(--lens-x, 70%);
|
||||
top: var(--lens-y, 50%);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: left 0.08s ease-out, top 0.08s ease-out;
|
||||
box-shadow: 0 0 0 4px var(--color-accent-dim), 0 20px 60px rgba(0,0,0,0.15);
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.lens-label {
|
||||
.split-label {
|
||||
position: absolute;
|
||||
top: -28px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
transform: translate(-50%, -50%) skewX(10deg);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
background: var(--color-paper);
|
||||
padding: 4px 12px;
|
||||
color: var(--color-paper);
|
||||
background: var(--color-accent);
|
||||
padding: 6px 14px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Slop Card - Generic AI output */
|
||||
@@ -607,15 +609,15 @@ code {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Lens Labels */
|
||||
.lens-labels {
|
||||
/* Split Labels */
|
||||
.split-labels {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-lg);
|
||||
gap: var(--spacing-xl);
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.lens-label-item {
|
||||
.split-label-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
@@ -623,14 +625,14 @@ code {
|
||||
color: var(--color-ash);
|
||||
}
|
||||
|
||||
.lens-label-dot {
|
||||
.split-label-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-mist);
|
||||
}
|
||||
|
||||
.lens-label-dot--accent {
|
||||
.split-label-dot--accent {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
@@ -1322,168 +1324,133 @@ code {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.antidote-intro {
|
||||
text-align: center;
|
||||
/* Patterns - tabbed layout */
|
||||
.patterns-categories {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.antidote-intro p {
|
||||
font-size: 1.25rem;
|
||||
/* Tab bar */
|
||||
.pattern-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-mist);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.pattern-tab {
|
||||
background: none;
|
||||
border: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-ash);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
cursor: pointer;
|
||||
transition: color var(--duration-fast) var(--ease-out);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pattern-tab:hover {
|
||||
color: var(--color-charcoal);
|
||||
max-width: 50ch;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.antidote-intro strong {
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Section titles for patterns/antipatterns */
|
||||
.patterns-section-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
.pattern-tab.active {
|
||||
color: var(--color-ink);
|
||||
margin: var(--spacing-lg) 0 var(--spacing-md);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.patterns-section-title::before {
|
||||
content: '✓ ';
|
||||
.pattern-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: calc(-1 * var(--spacing-sm) - 1px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Panels */
|
||||
.pattern-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pattern-panel.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pattern-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pattern-columns {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
.pattern-column-label {
|
||||
display: block;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
color: var(--color-ash);
|
||||
}
|
||||
|
||||
.pattern-column--anti .pattern-column-label {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.pattern-column--do .pattern-column-label {
|
||||
color: var(--color-success, #22c55e);
|
||||
}
|
||||
|
||||
.patterns-section-title--anti::before {
|
||||
content: '× ';
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Patterns grid (what TO do) */
|
||||
.patterns-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.pattern-category {
|
||||
background: var(--color-paper);
|
||||
border: 1px solid var(--color-mist);
|
||||
padding: var(--spacing-lg);
|
||||
transition: all var(--duration-base) var(--ease-out);
|
||||
}
|
||||
|
||||
.pattern-category:hover {
|
||||
border-color: var(--color-success, #22c55e);
|
||||
box-shadow: 0 10px 40px rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.pattern-category-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-ink);
|
||||
margin: 0 0 var(--spacing-md);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--color-mist);
|
||||
}
|
||||
|
||||
/* Pattern list styles */
|
||||
.pattern-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.pattern-list li {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-charcoal);
|
||||
.pattern-item {
|
||||
font-size: 0.8125rem;
|
||||
padding-left: var(--spacing-md);
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pattern-list li::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-success, #22c55e);
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
/* Anti-pattern items - muted */
|
||||
.pattern-item--anti {
|
||||
color: var(--color-ash);
|
||||
}
|
||||
|
||||
/* Anti-patterns grid */
|
||||
.antipatterns-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.antipattern-category {
|
||||
background: var(--color-paper);
|
||||
border: 1px solid var(--color-mist);
|
||||
padding: var(--spacing-lg);
|
||||
transition: all var(--duration-base) var(--ease-out);
|
||||
}
|
||||
|
||||
.antipattern-category:hover {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 10px 40px var(--color-accent-dim);
|
||||
}
|
||||
|
||||
.antipattern-category-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-ink);
|
||||
margin: 0 0 var(--spacing-md);
|
||||
padding-bottom: var(--spacing-sm);
|
||||
border-bottom: 1px solid var(--color-mist);
|
||||
}
|
||||
|
||||
.antipattern-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.antipattern-list li {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-charcoal);
|
||||
padding-left: var(--spacing-md);
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.antipattern-list li::before {
|
||||
.pattern-item--anti::before {
|
||||
content: '×';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Antidote CTA */
|
||||
.antidote-cta {
|
||||
text-align: center;
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-mist);
|
||||
}
|
||||
|
||||
.antidote-cta p {
|
||||
font-size: 1.125rem;
|
||||
/* Pattern items - prominent */
|
||||
.pattern-item--do {
|
||||
color: var(--color-charcoal);
|
||||
max-width: 50ch;
|
||||
margin: 0 auto;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.pattern-item--do::before {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--color-success, #22c55e);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
||||
@@ -1,130 +1,94 @@
|
||||
/**
|
||||
* Problem Section - The Clarity Lens
|
||||
* Interactive before/after comparison
|
||||
* Problem Section - Split Screen Comparison
|
||||
* Interactive before/after comparison with angled divider
|
||||
*/
|
||||
|
||||
/* Additional lens styles that need complex CSS */
|
||||
|
||||
.lens-container {
|
||||
cursor: crosshair;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* Subtle grid background for the lens container */
|
||||
.lens-container::before {
|
||||
/* Subtle grid background for the container */
|
||||
.split-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
background-image:
|
||||
linear-gradient(var(--color-mist) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--color-mist) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
opacity: 0.5;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Lens glow effect */
|
||||
.lens-circle::before {
|
||||
content: '';
|
||||
/* Hover hint */
|
||||
.split-container::after {
|
||||
content: '← Drag →';
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
var(--color-accent-dim) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
bottom: 12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ash);
|
||||
background: var(--color-paper);
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Pulsing animation for the lens when idle */
|
||||
@keyframes lensGlow {
|
||||
0%, 100% {
|
||||
box-shadow:
|
||||
0 0 0 4px var(--color-accent-dim),
|
||||
0 20px 60px rgba(0,0,0,0.15);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0 0 0 8px var(--color-accent-dim),
|
||||
0 20px 80px rgba(0,0,0,0.2);
|
||||
}
|
||||
.split-container:hover::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.lens-container:not(:hover) .lens-circle {
|
||||
animation: lensGlow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Smooth transitions when hovering */
|
||||
.lens-container:hover .lens-circle {
|
||||
transition: left 0.05s linear, top 0.05s linear;
|
||||
}
|
||||
|
||||
.lens-container:hover .lens-after {
|
||||
transition: clip-path 0.05s linear;
|
||||
}
|
||||
|
||||
/* Mobile: tap to reveal */
|
||||
@media (hover: none) {
|
||||
.lens-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lens-circle::after {
|
||||
content: 'Tap & Drag';
|
||||
position: absolute;
|
||||
bottom: -36px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
background: var(--color-paper);
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover state for impeccable card inside lens */
|
||||
.lens-after .impeccable-card {
|
||||
/* Hover state for impeccable card */
|
||||
.split-after .impeccable-card {
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
/* Entry animation for the lens */
|
||||
@keyframes lensEntry {
|
||||
/* Entry animation for the divider */
|
||||
@keyframes splitEntry {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.8);
|
||||
transform: translateX(-50%) skewX(-10deg) scaleY(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
transform: translateX(-50%) skewX(-10deg) scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
.lens-circle {
|
||||
animation: lensEntry 0.6s var(--ease-out) 0.3s backwards;
|
||||
.split-divider {
|
||||
animation: splitEntry 0.6s var(--ease-out) 0.3s backwards;
|
||||
}
|
||||
|
||||
/* Labels hover states */
|
||||
.lens-label-item {
|
||||
.split-label-item {
|
||||
transition: color var(--duration-fast) var(--ease-out);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.lens-label-item:hover {
|
||||
.split-label-item:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.lens-label-item[data-point="after"]:hover .lens-label-dot--accent {
|
||||
.split-label-item[data-point="after"]:hover .split-label-dot--accent {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
.lens-label-dot {
|
||||
.split-label-dot {
|
||||
transition: transform var(--duration-fast) var(--ease-spring);
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (hover: none) {
|
||||
.split-container::after {
|
||||
content: '← Swipe →';
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.split-label {
|
||||
font-size: 0.5625rem;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
+64
-64
@@ -243,89 +243,89 @@
|
||||
margin-top: var(--spacing-sm) !important;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-container {
|
||||
/* Demo Split Comparison in Terminal */
|
||||
.terminal-preview .demo-split-comparison {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-header {
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
.terminal-preview .demo-split-comparison .split-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
cursor: ew-resize;
|
||||
user-select: none;
|
||||
background: var(--color-cream);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-viewport {
|
||||
flex: 1;
|
||||
.terminal-preview .demo-split-comparison .split-before,
|
||||
.terminal-preview .demo-split-comparison .split-after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-md);
|
||||
min-height: 0;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-viewport > * {
|
||||
margin-block: auto; /* Center vertically when content is shorter than container */
|
||||
.terminal-preview .demo-split-comparison .split-before {
|
||||
z-index: 1;
|
||||
background: var(--color-cream);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-caption {
|
||||
.terminal-preview .demo-split-comparison .split-after {
|
||||
z-index: 2;
|
||||
background: var(--color-paper);
|
||||
clip-path: polygon(58% 0%, 100% 0%, 100% 100%, 42% 100%);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-split-comparison .split-content {
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-split-comparison .split-divider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 2px;
|
||||
background: var(--color-accent);
|
||||
transform: translateX(-50%) skewX(-10deg);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
box-shadow: 0 0 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-split-comparison .split-label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) skewX(10deg);
|
||||
font-size: 0.5625rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-paper);
|
||||
background: var(--color-accent);
|
||||
padding: 4px 10px;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-split-comparison .demo-caption {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-ash);
|
||||
text-align: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Demo toggle in terminal */
|
||||
.terminal-preview .demo-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-switch {
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
background: var(--color-mist);
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-switch::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-switch.active {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-switch.active::after {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-ash);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.terminal-preview .demo-toggle-label.active {
|
||||
color: var(--color-ink);
|
||||
font-weight: 500;
|
||||
border-top: 1px solid var(--color-mist);
|
||||
}
|
||||
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
|
||||
+17
-32
@@ -34,7 +34,7 @@
|
||||
<span class="hero-title-line" data-split-text>Impeccable</span>
|
||||
<span class="hero-title-line hero-title-accent" data-split-text>Style</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle" data-reveal>One design skill and 15 commands that work across Cursor, Claude Code, Gemini CLI, and Codex CLI. Stop getting generic AI output. Start getting considered design.</p>
|
||||
<p class="hero-subtitle" data-reveal>One design skill and 15 commands that work across Cursor, Claude Code, Gemini CLI, and Codex CLI. Stop getting generic AI output. Start getting intentional design.</p>
|
||||
<div class="hero-cta" data-reveal>
|
||||
<a href="#downloads" class="btn btn-primary">Get the Framework</a>
|
||||
</div>
|
||||
@@ -104,10 +104,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lens-comparison" id="lens-comparison">
|
||||
<div class="lens-container">
|
||||
<div class="split-comparison" id="lens-comparison">
|
||||
<div class="split-container">
|
||||
<!-- Before: AI Slop -->
|
||||
<div class="lens-before">
|
||||
<div class="split-before">
|
||||
<div class="slop-card">
|
||||
<div class="slop-header">
|
||||
<div class="slop-avatar"></div>
|
||||
@@ -127,8 +127,8 @@
|
||||
<span class="slop-callout" data-point="rounded">Cards on cards</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- After: Impeccable (shown through lens) -->
|
||||
<div class="lens-after">
|
||||
<!-- After: Impeccable (shown through split) -->
|
||||
<div class="split-after">
|
||||
<div class="impeccable-card">
|
||||
<p class="impeccable-eyebrow">Introducing</p>
|
||||
<h3 class="impeccable-title">Thoughtful Design</h3>
|
||||
@@ -136,18 +136,18 @@
|
||||
<button class="impeccable-button">Explore</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- The Lens -->
|
||||
<div class="lens-circle" id="lens-circle">
|
||||
<span class="lens-label">With Skills</span>
|
||||
<!-- The Divider -->
|
||||
<div class="split-divider">
|
||||
<span class="split-label">With Skills</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lens-labels">
|
||||
<div class="lens-label-item" data-point="before">
|
||||
<span class="lens-label-dot"></span>
|
||||
<div class="split-labels">
|
||||
<div class="split-label-item" data-point="before">
|
||||
<span class="split-label-dot"></span>
|
||||
<span>Generic AI Output</span>
|
||||
</div>
|
||||
<div class="lens-label-item" data-point="after">
|
||||
<span class="lens-label-dot lens-label-dot--accent"></span>
|
||||
<div class="split-label-item" data-point="after">
|
||||
<span class="split-label-dot split-label-dot--accent"></span>
|
||||
<span>With Design Skills</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,25 +164,10 @@
|
||||
<div class="antidote-content">
|
||||
<p class="section-lead" data-reveal>The original Claude frontend-design skill says to avoid Arial and Inter. We've expanded it with 60+ specific patterns that fight model bias across every design domain.</p>
|
||||
|
||||
<div class="antidote-intro" data-reveal>
|
||||
<p>Your AI learns <strong>what TO do</strong> and <strong>what NOT to do</strong>. These rules are baked into the skill.</p>
|
||||
</div>
|
||||
|
||||
<!-- Patterns (what TO do) -->
|
||||
<h3 class="patterns-section-title" data-reveal>What TO Do</h3>
|
||||
<div class="patterns-grid" id="patterns-grid" data-reveal>
|
||||
<!-- Patterns by category with tabs -->
|
||||
<div class="patterns-categories" id="patterns-categories">
|
||||
<!-- Rendered by JS -->
|
||||
</div>
|
||||
|
||||
<!-- Anti-patterns (what NOT to do) -->
|
||||
<h3 class="patterns-section-title patterns-section-title--anti" data-reveal>What NOT to Do</h3>
|
||||
<div class="antipatterns-grid" id="antipatterns-grid" data-reveal>
|
||||
<!-- Rendered by JS -->
|
||||
</div>
|
||||
|
||||
<div class="antidote-cta" data-reveal>
|
||||
<p>These rules are embedded in the skill. Your AI internalizes them before writing a single line of code.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -295,7 +280,7 @@
|
||||
<section class="platforms-section" id="downloads">
|
||||
<div class="section-header" data-reveal>
|
||||
<span class="section-number">05</span>
|
||||
<h2 class="section-title">One Framework, Every AI Tool</h2>
|
||||
<h2 class="section-title">One Framework, Every Agent Harness</h2>
|
||||
<p class="section-subtitle">We wrote it once, then compiled it for each platform. Same design intelligence, native to your workflow.</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { renderCommandDemo } from "../demo-renderer.js";
|
||||
import { setupCommandDemoToggles } from "../demo-toggles.js";
|
||||
import { initSplitCompare } from "../effects/split-compare.js";
|
||||
import { commandProcessSteps, commandCategories, commandRelationships } from "../data.js";
|
||||
|
||||
export function initGlassTerminal() {
|
||||
@@ -141,9 +141,23 @@ function setupScrollSpy(commands) {
|
||||
});
|
||||
}
|
||||
|
||||
// Track current split instance and command for cleanup
|
||||
let currentSplitInstance = null;
|
||||
let currentCommandId = null;
|
||||
|
||||
function updateTerminal(cmd, container, allCommands) {
|
||||
if (!cmd || !container) return;
|
||||
|
||||
// Skip if already showing this command
|
||||
if (currentCommandId === cmd.id) return;
|
||||
currentCommandId = cmd.id;
|
||||
|
||||
// Cleanup previous split instance
|
||||
if (currentSplitInstance) {
|
||||
currentSplitInstance.destroy();
|
||||
currentSplitInstance = null;
|
||||
}
|
||||
|
||||
// Get process steps for this command (or use generic fallback)
|
||||
const steps = commandProcessSteps[cmd.id] || ['Analyze', 'Transform', 'Verify'];
|
||||
const stepsOutput = steps.map((step, i) =>
|
||||
@@ -156,6 +170,14 @@ function updateTerminal(cmd, container, allCommands) {
|
||||
<div class="terminal-preview command-demo-area">${renderCommandDemo(cmd.id)}</div>
|
||||
<div class="terminal-line terminal-cursor-line"><span class="terminal-prompt">➜</span><span class="terminal-cursor"></span></div>`;
|
||||
|
||||
// Re-bind toggles
|
||||
setupCommandDemoToggles(allCommands, () => {});
|
||||
// Initialize split-compare effect for the demo
|
||||
const splitComparison = container.querySelector('.demo-split-comparison');
|
||||
if (splitComparison) {
|
||||
currentSplitInstance = initSplitCompare(splitComparison, {
|
||||
defaultPosition: 50,
|
||||
skewOffset: 8,
|
||||
minPosition: 5,
|
||||
maxPosition: 95
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,13 @@
|
||||
import { initSplitCompare } from "../effects/split-compare.js";
|
||||
|
||||
export function initLensEffect() {
|
||||
const container = document.getElementById("lens-comparison");
|
||||
if (!container) return;
|
||||
|
||||
const lensContainer = container.querySelector(".lens-container");
|
||||
const lensAfter = container.querySelector(".lens-after");
|
||||
const lensCircle = container.querySelector(".lens-circle");
|
||||
|
||||
if (!lensContainer || !lensAfter || !lensCircle) return;
|
||||
|
||||
let isHovering = false;
|
||||
|
||||
function updateLens(x, y) {
|
||||
const rect = lensContainer.getBoundingClientRect();
|
||||
const relX = ((x - rect.left) / rect.width) * 100;
|
||||
const relY = ((y - rect.top) / rect.height) * 100;
|
||||
|
||||
const clampedX = Math.max(25, Math.min(75, relX));
|
||||
const clampedY = Math.max(30, Math.min(70, relY));
|
||||
|
||||
lensAfter.style.clipPath = `circle(120px at ${clampedX}% ${clampedY}%)`;
|
||||
lensCircle.style.setProperty("--lens-x", `${clampedX}%`);
|
||||
lensCircle.style.setProperty("--lens-y", `${clampedY}%`);
|
||||
lensCircle.style.left = `${clampedX}%`;
|
||||
lensCircle.style.top = `${clampedY}%`;
|
||||
}
|
||||
|
||||
lensContainer.addEventListener("mouseenter", () => {
|
||||
isHovering = true;
|
||||
initSplitCompare(container, {
|
||||
defaultPosition: 70,
|
||||
skewOffset: 8,
|
||||
minPosition: 5,
|
||||
maxPosition: 95
|
||||
});
|
||||
|
||||
lensContainer.addEventListener("mouseleave", () => {
|
||||
isHovering = false;
|
||||
// Reset to default position
|
||||
updateLens(
|
||||
lensContainer.getBoundingClientRect().left +
|
||||
lensContainer.offsetWidth * 0.7,
|
||||
lensContainer.getBoundingClientRect().top +
|
||||
lensContainer.offsetHeight * 0.5,
|
||||
);
|
||||
});
|
||||
|
||||
lensContainer.addEventListener("mousemove", (e) => {
|
||||
if (isHovering) {
|
||||
updateLens(e.clientX, e.clientY);
|
||||
}
|
||||
});
|
||||
|
||||
// Touch support
|
||||
lensContainer.addEventListener("touchmove", (e) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
updateLens(touch.clientX, touch.clientY);
|
||||
});
|
||||
|
||||
// Initialize at default position
|
||||
updateLens(
|
||||
lensContainer.getBoundingClientRect().left +
|
||||
lensContainer.offsetWidth * 0.7,
|
||||
lensContainer.getBoundingClientRect().top +
|
||||
lensContainer.offsetHeight * 0.5,
|
||||
);
|
||||
}
|
||||
|
||||
+20
-22
@@ -6,11 +6,11 @@ import { getCommandDemo } from './demos/commands/index.js';
|
||||
import { getSkillDemo } from './demos/skills/index.js';
|
||||
|
||||
/**
|
||||
* Render a command demo
|
||||
* Render a command demo with split-screen comparison
|
||||
*/
|
||||
export function renderCommandDemo(commandId) {
|
||||
const demo = getCommandDemo(commandId);
|
||||
|
||||
|
||||
if (!demo) {
|
||||
return `
|
||||
<div class="demo-container">
|
||||
@@ -22,20 +22,18 @@ export function renderCommandDemo(commandId) {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const viewportClass = demo.viewportClass ? ` ${demo.viewportClass}` : '';
|
||||
|
||||
|
||||
// Use split-screen comparison
|
||||
return `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
<div class="demo-toggle">
|
||||
<span class="demo-toggle-label active">Before</span>
|
||||
<div class="demo-toggle-switch" data-demo="command-${demo.id}"></div>
|
||||
<span class="demo-toggle-label">After</span>
|
||||
<div class="demo-split-comparison" data-demo="command-${demo.id}">
|
||||
<div class="split-container">
|
||||
<div class="split-before">
|
||||
<div class="split-content">${demo.before}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-viewport${viewportClass}" data-state="before" id="command-${demo.id}-viewport">
|
||||
${demo.before}
|
||||
<div class="split-after">
|
||||
<div class="split-content">${demo.after || demo.before}</div>
|
||||
</div>
|
||||
<div class="split-divider"></div>
|
||||
</div>
|
||||
<div class="demo-caption">${demo.caption}</div>
|
||||
</div>
|
||||
@@ -47,7 +45,7 @@ export function renderCommandDemo(commandId) {
|
||||
*/
|
||||
export function renderSkillDemo(skillId) {
|
||||
const skill = getSkillDemo(skillId);
|
||||
|
||||
|
||||
if (!skill || !skill.tabs || skill.tabs.length === 0) {
|
||||
return `
|
||||
<div class="demo-container">
|
||||
@@ -59,21 +57,21 @@ export function renderSkillDemo(skillId) {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
const showTabs = skill.tabs.length > 1;
|
||||
|
||||
|
||||
const tabs = showTabs ? skill.tabs.map((tab, i) => `
|
||||
<button class="demo-tab ${i === 0 ? 'active' : ''}" data-demo-tab="${tab.id}" data-skill="${skillId}">
|
||||
${tab.label}
|
||||
</button>
|
||||
`).join('') : '';
|
||||
|
||||
|
||||
const panels = skill.tabs.map((tab, i) => `
|
||||
<div class="demo-panel ${i === 0 ? 'active' : ''}" data-demo-panel="${tab.id}">
|
||||
${renderSkillTabDemo(skillId, tab)}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
|
||||
return `
|
||||
<div class="demo-tabbed-container">
|
||||
${showTabs ? `<div class="demo-tabs">${tabs}</div>` : ''}
|
||||
@@ -90,7 +88,7 @@ export function renderSkillDemo(skillId) {
|
||||
function renderSkillTabDemo(skillId, tab) {
|
||||
const hasToggle = tab.hasToggle !== false;
|
||||
const demoId = `${skillId}-${tab.id}`;
|
||||
|
||||
|
||||
return `
|
||||
<div class="demo-container">
|
||||
<div class="demo-header">
|
||||
@@ -118,10 +116,10 @@ export function setupDemoTabs() {
|
||||
tab.addEventListener('click', () => {
|
||||
const tabId = tab.dataset.demoTab;
|
||||
const container = tab.closest('.demo-tabbed-container');
|
||||
|
||||
|
||||
container.querySelectorAll('.demo-tab').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
|
||||
container.querySelectorAll('.demo-panel').forEach(p => p.classList.remove('active'));
|
||||
container.querySelector(`[data-demo-panel="${tabId}"]`)?.classList.add('active');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// ============================================
|
||||
// SPLIT COMPARE - Reusable before/after split-screen effect
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Initialize split comparison effect on a container
|
||||
* @param {HTMLElement} container - The container element with .split-container inside
|
||||
* @param {Object} options - Configuration options
|
||||
*/
|
||||
export function initSplitCompare(container, options = {}) {
|
||||
const {
|
||||
defaultPosition = 70,
|
||||
skewOffset = 8,
|
||||
minPosition = 5,
|
||||
maxPosition = 95,
|
||||
lerpSpeed = 0.15,
|
||||
animationThreshold = 40, // Re-trigger animations when crossing this threshold
|
||||
onCrossThreshold = null // Callback when crossing threshold toward "after" side
|
||||
} = options;
|
||||
|
||||
const splitContainer = container.querySelector('.split-container');
|
||||
const splitAfter = container.querySelector('.split-after');
|
||||
const splitDivider = container.querySelector('.split-divider');
|
||||
|
||||
if (!splitContainer || !splitAfter || !splitDivider) return null;
|
||||
|
||||
let isHovering = false;
|
||||
let currentX = defaultPosition;
|
||||
let targetX = defaultPosition;
|
||||
let animationId = null;
|
||||
let wasAboveThreshold = defaultPosition > animationThreshold;
|
||||
|
||||
function updateSplit(percent) {
|
||||
const clampedX = Math.max(minPosition, Math.min(maxPosition, percent));
|
||||
|
||||
// Check if we crossed the threshold toward the "after" side (moving left)
|
||||
const isAboveThreshold = clampedX > animationThreshold;
|
||||
if (wasAboveThreshold && !isAboveThreshold) {
|
||||
// Crossed threshold - re-trigger animations
|
||||
retriggerAnimations();
|
||||
if (onCrossThreshold) onCrossThreshold(clampedX);
|
||||
}
|
||||
wasAboveThreshold = isAboveThreshold;
|
||||
|
||||
// Angled clip-path matching divider's skewX(-10deg)
|
||||
splitAfter.style.clipPath = `polygon(${clampedX + skewOffset}% 0%, 100% 0%, 100% 100%, ${clampedX - skewOffset}% 100%)`;
|
||||
splitDivider.style.left = `${clampedX}%`;
|
||||
}
|
||||
|
||||
function retriggerAnimations() {
|
||||
// Find all elements with animations in the "after" content and re-trigger them
|
||||
const afterContent = splitAfter.querySelector('.split-content');
|
||||
if (afterContent) {
|
||||
// Clone and replace to restart all CSS animations
|
||||
const clone = afterContent.cloneNode(true);
|
||||
afterContent.parentNode.replaceChild(clone, afterContent);
|
||||
}
|
||||
}
|
||||
|
||||
function animate() {
|
||||
const diff = targetX - currentX;
|
||||
if (Math.abs(diff) > 0.1) {
|
||||
currentX += diff * lerpSpeed;
|
||||
updateSplit(currentX);
|
||||
animationId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
currentX = targetX;
|
||||
updateSplit(currentX);
|
||||
animationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startAnimation() {
|
||||
if (!animationId) {
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseEnter() {
|
||||
isHovering = true;
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
isHovering = false;
|
||||
targetX = defaultPosition;
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
function handleMouseMove(e) {
|
||||
if (isHovering) {
|
||||
const rect = splitContainer.getBoundingClientRect();
|
||||
targetX = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
function handleTouchStart() {
|
||||
isHovering = true;
|
||||
}
|
||||
|
||||
function handleTouchEnd() {
|
||||
isHovering = false;
|
||||
targetX = defaultPosition;
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
function handleTouchMove(e) {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
const rect = splitContainer.getBoundingClientRect();
|
||||
targetX = ((touch.clientX - rect.left) / rect.width) * 100;
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
// Attach listeners
|
||||
splitContainer.addEventListener('mouseenter', handleMouseEnter);
|
||||
splitContainer.addEventListener('mouseleave', handleMouseLeave);
|
||||
splitContainer.addEventListener('mousemove', handleMouseMove);
|
||||
splitContainer.addEventListener('touchstart', handleTouchStart);
|
||||
splitContainer.addEventListener('touchend', handleTouchEnd);
|
||||
splitContainer.addEventListener('touchmove', handleTouchMove, { passive: false });
|
||||
|
||||
// Initialize
|
||||
updateSplit(defaultPosition);
|
||||
|
||||
// Return cleanup function
|
||||
return {
|
||||
destroy() {
|
||||
splitContainer.removeEventListener('mouseenter', handleMouseEnter);
|
||||
splitContainer.removeEventListener('mouseleave', handleMouseLeave);
|
||||
splitContainer.removeEventListener('mousemove', handleMouseMove);
|
||||
splitContainer.removeEventListener('touchstart', handleTouchStart);
|
||||
splitContainer.removeEventListener('touchend', handleTouchEnd);
|
||||
splitContainer.removeEventListener('touchmove', handleTouchMove);
|
||||
if (animationId) cancelAnimationFrame(animationId);
|
||||
},
|
||||
setPosition(percent) {
|
||||
targetX = percent;
|
||||
startAnimation();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all split comparisons on the page
|
||||
*/
|
||||
export function initAllSplitCompare(selector = '.split-comparison', options = {}) {
|
||||
const containers = document.querySelectorAll(selector);
|
||||
const instances = [];
|
||||
|
||||
containers.forEach(container => {
|
||||
const instance = initSplitCompare(container, options);
|
||||
if (instance) instances.push(instance);
|
||||
});
|
||||
|
||||
return instances;
|
||||
}
|
||||
Reference in New Issue
Block a user