mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-20 01:56:37 +03:00
Restyle the live picker to match the site kinpaku kit, persist pick mode in localStorage, fix DESIGN.md color swatches in the parser, and land the neo-kinpaku site refresh with new tokens, assets, palette script, and detector rules. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
6.0 KiB
Plaintext
164 lines
6.0 KiB
Plaintext
---
|
|
import Base from './Base.astro';
|
|
import '../styles/sub-pages.css';
|
|
import '../styles/docs-kinpaku.css';
|
|
import {
|
|
SKILL_CATEGORIES,
|
|
CATEGORY_ORDER,
|
|
CATEGORY_LABELS,
|
|
COMMAND_RELATIONSHIPS,
|
|
} from '../data/sub-pages-data';
|
|
import { getCommandDemo } from '../scripts/demos/commands/index.js';
|
|
|
|
interface Props {
|
|
title: string;
|
|
description: string;
|
|
tagline: string;
|
|
slug: string;
|
|
category: string;
|
|
allCommands: { slug: string; category: string }[];
|
|
}
|
|
|
|
const { title, description, tagline, slug, category, allCommands } = Astro.props;
|
|
|
|
// Decide hero layout at build time: when a command has a registered demo
|
|
// (in site/scripts/demos/commands/<id>.js), the hero is a two-column grid
|
|
// with the demo on the left and h1/lede on the right — same editorial
|
|
// before/after side-by-side that the old site used. When no demo exists,
|
|
// the hero stays single-column.
|
|
const hasDemo = !!getCommandDemo(slug);
|
|
|
|
const relationships = COMMAND_RELATIONSHIPS[slug] || {};
|
|
|
|
const sidebarGroups: Record<string, { slug: string }[]> = {};
|
|
for (const cat of CATEGORY_ORDER) {
|
|
sidebarGroups[cat] = allCommands
|
|
.filter(c => c.category === cat)
|
|
.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
}
|
|
---
|
|
|
|
<Base
|
|
title={`${title} | Impeccable`}
|
|
description={description}
|
|
activeNav="docs"
|
|
canonicalPath={`/docs/${slug}`}
|
|
bodyClass="sub-page skills-layout-page docs-kinpaku"
|
|
>
|
|
<div class="skills-layout">
|
|
<aside class="skills-sidebar" aria-label="Commands">
|
|
<button class="skills-sidebar-toggle" type="button" aria-expanded="false" aria-controls="skills-sidebar-inner">
|
|
<span class="skills-sidebar-toggle-label">Commands</span>
|
|
<svg class="skills-sidebar-toggle-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
|
|
</button>
|
|
<div class="skills-sidebar-inner" id="skills-sidebar-inner">
|
|
<p class="skills-sidebar-label">Commands</p>
|
|
{CATEGORY_ORDER.map(cat => (
|
|
<div class="skills-sidebar-group">
|
|
<span class="skills-sidebar-category">{CATEGORY_LABELS[cat]}</span>
|
|
<ul class="skills-sidebar-list">
|
|
{sidebarGroups[cat].map(cmd => (
|
|
<li>
|
|
<a href={`/docs/${cmd.slug}`} aria-current={cmd.slug === slug ? 'page' : undefined}>
|
|
<span>{cmd.slug}</span>
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
|
|
<div class="skills-main">
|
|
<div class="skills-detail">
|
|
{hasDemo ? (
|
|
/* Two-column editorial hero: demo on the left, h1 + lede on
|
|
the right. The demo content is populated client-side by the
|
|
script below from site/scripts/demos/commands/<id>.js, same
|
|
source the homepage uses. */
|
|
<div class="docs-hero docs-hero--with-demo">
|
|
<div class="docs-command-demo" data-command={slug}></div>
|
|
<header class="sub-page-header">
|
|
<h1 class="sub-page-title">/impeccable <em>{slug}</em></h1>
|
|
{tagline && <p class="sub-page-lede">{tagline}</p>}
|
|
</header>
|
|
</div>
|
|
) : (
|
|
<header class="sub-page-header">
|
|
<h1 class="sub-page-title">/impeccable <em>{slug}</em></h1>
|
|
{tagline && <p class="sub-page-lede">{tagline}</p>}
|
|
</header>
|
|
)}
|
|
|
|
<div class="skills-detail-body docs-body prose">
|
|
<slot />
|
|
</div>
|
|
|
|
{(relationships.leadsTo || relationships.pairs || relationships.combinesWith) && (
|
|
<footer class="skills-relationships">
|
|
<h2 class="skills-relationships-title">Related commands</h2>
|
|
<div class="skills-relationships-list">
|
|
{relationships.leadsTo?.map(cmd => (
|
|
<a href={`/docs/${cmd}`} class="skills-relationship-chip" data-relation="leads-to">
|
|
<span class="skills-relationship-label">leads to</span>
|
|
<span class="skills-relationship-name">{cmd}</span>
|
|
</a>
|
|
))}
|
|
{relationships.pairs && (
|
|
<a href={`/docs/${relationships.pairs}`} class="skills-relationship-chip" data-relation="pairs">
|
|
<span class="skills-relationship-label">pairs with</span>
|
|
<span class="skills-relationship-name">{relationships.pairs}</span>
|
|
</a>
|
|
)}
|
|
{relationships.combinesWith?.map(cmd => (
|
|
<a href={`/docs/${cmd}`} class="skills-relationship-chip" data-relation="combines">
|
|
<span class="skills-relationship-label">combines with</span>
|
|
<span class="skills-relationship-name">{cmd}</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</footer>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script is:inline>
|
|
document.addEventListener('click', (e) => {
|
|
const toggle = e.target.closest('.skills-sidebar-toggle');
|
|
if (!toggle) return;
|
|
const expanded = toggle.getAttribute('aria-expanded') === 'true';
|
|
toggle.setAttribute('aria-expanded', String(!expanded));
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
import { getCommandDemo } from '../scripts/demos/commands/index.js';
|
|
import { renderCommandDemo, initCommandDemo } from '../scripts/demo-renderer.js';
|
|
import { initSplitCompare } from '../scripts/effects/split-compare.js';
|
|
|
|
const slot = document.querySelector('.docs-command-demo');
|
|
if (slot) {
|
|
const command = slot.dataset.command;
|
|
const demo = getCommandDemo(command);
|
|
if (demo) {
|
|
slot.innerHTML = renderCommandDemo(command);
|
|
const split = slot.querySelector('.demo-split-comparison');
|
|
if (split) {
|
|
initSplitCompare(split, {
|
|
defaultPosition: 50,
|
|
skewAngle: 0,
|
|
minPosition: 10,
|
|
maxPosition: 90,
|
|
});
|
|
}
|
|
initCommandDemo(command, slot);
|
|
} else {
|
|
// No demo registered for this command — leave the layout flush.
|
|
slot.remove();
|
|
}
|
|
}
|
|
</script>
|
|
</Base>
|