feat: breadcrumbs, related tools

This commit is contained in:
2026-10-12 16:37:35 +05:00
parent 5184878589
commit 399c3fb282
10 changed files with 182 additions and 17 deletions
+48
View File
@@ -0,0 +1,48 @@
<script lang="ts">
import { resolve } from "$app/paths";
import { pageTitle } from "$lib/i18n/schema-tool-strings";
import { t } from "$lib/i18n/t";
import type { Page } from "$lib/registry";
import { ChevronRight } from "@lucide/svelte";
interface Props {
page: Page;
}
let { page }: Props = $props();
const categoryLabel = $derived(t(`categories.${page.category}`));
const title = $derived(pageTitle(page));
</script>
<!-- Middle crumb is plain text: a category has no address of its own until the
category hubs land (docs/plan-seo.md S3c), then it becomes a link here. -->
<nav class="crumbs" aria-label={t("toolPage.breadcrumbAria")}>
<a href={resolve("/list-tools")}>{t("toolPage.breadcrumbCatalog")}</a>
<ChevronRight size={13} aria-hidden="true" />
<span>{categoryLabel}</span>
<ChevronRight size={13} aria-hidden="true" />
<span class="current" aria-current="page">{title}</span>
</nav>
<style>
.crumbs {
display: flex;
align-items: center;
gap: var(--space-s);
flex-wrap: wrap;
max-width: var(--size-content-max-wide);
padding: var(--space-xxxl) clamp(var(--space-m), 4vw, var(--space-xxxl)) 0;
font: var(--font-size-s) var(--font-mono);
color: var(--color-text-muted);
}
a {
color: var(--color-text-muted);
text-decoration: none;
}
a:hover {
color: var(--color-main);
}
.current {
color: var(--color-text);
}
</style>
+32 -1
View File
@@ -1,7 +1,14 @@
<script lang="ts">
import { pageSeo } from "$lib/i18n/schema-tool-strings";
import { relatedPages } from "$lib/catalog";
import ToolCard from "$lib/components/catalog/ToolCard.svelte";
import {
pageDescription,
pageSeo,
pageTitle,
} from "$lib/i18n/schema-tool-strings";
import { t } from "$lib/i18n/t";
import type { Page } from "$lib/registry";
import { toolIcon } from "$lib/tool-icons";
interface Props {
page: Page;
@@ -9,6 +16,7 @@
let { page }: Props = $props();
const seo = $derived(pageSeo(page));
const related = $derived(relatedPages(page, 6));
</script>
<!-- Below the tool, not above it: the workspace is what the visitor came for,
@@ -33,6 +41,21 @@
</details>
{/each}
</div>
<!-- Internal links: the only way a crawler reaches the neighbours of
this page, so the block stays derived from the category, never a
hand-picked list. -->
<h2>{t("toolPage.relatedHeading")}</h2>
<div class="related">
{#each related as item (item.slug)}
<ToolCard
title={pageTitle(item)}
slug={item.slug}
description={pageDescription(item)}
icon={toolIcon(item.slug)}
/>
{/each}
</div>
</section>
{/if}
@@ -91,9 +114,17 @@
font-size: var(--font-size-m);
line-height: 1.6;
}
.related {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-l);
}
@media (--bp-tablet) {
.long {
font-size: var(--font-size-m);
}
.related {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,12 +1,12 @@
<script lang="ts">
import Button from "$lib/components/ui/Button.svelte";
import ChipButton from "$lib/components/ui/ChipButton.svelte";
import Toggle from "$lib/components/ui/Toggle.svelte";
import { ButtonVariantDefine } from "$lib/components/ui/define";
import { isFav, toggleFav } from "$lib/favorites.svelte";
import { Star } from "@lucide/svelte";
import { DragDropProvider } from "@dnd-kit/svelte";
import { debounce } from "$lib/core/debounce";
import { cachedDataUrl } from "$lib/core/io";
import type { PixelImage } from "$lib/core/types";
import { isFav, toggleFav } from "$lib/favorites.svelte";
import {
chainStepTitle,
chainWarningNotice,
@@ -17,8 +17,8 @@
import { t } from "$lib/i18n/t";
import { moveStep, type ChainStep } from "$lib/pipeline.svelte";
import { getTool, type Page, type Tool } from "$lib/registry";
import ChipButton from "$lib/components/ui/ChipButton.svelte";
import Toggle from "$lib/components/ui/Toggle.svelte";
import { DragDropProvider } from "@dnd-kit/svelte";
import { Star } from "@lucide/svelte";
import AddStepButton from "./AddStepButton.svelte";
import PartsGrid from "./PartsGrid.svelte";
import PreviewTile from "./PreviewTile.svelte";
@@ -32,8 +32,8 @@
import { createChainHost } from "./chain-host.svelte";
import { createOutputControls } from "./output-controls.svelte";
import { hasPreviewResult } from "./schema-preview-model";
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
import type { DisplayError } from "./schema-tool-runner.svelte";
import { createSchemaToolRunner } from "./schema-tool-runner.svelte";
import {
createSchemaToolState,
toolSchemaOf,
@@ -483,8 +483,8 @@
<style>
.schema-tool {
padding: calc(var(--space-xxxl) + var(--space-l))
clamp(var(--space-m), 4vw, var(--space-xxxl));
padding: var(--space-l) clamp(var(--space-m), 4vw, var(--space-xxxl))
calc(var(--space-xxxl) + var(--space-l));
flex: 1;
}
.header {