diff --git a/apps/docs/components.ts b/apps/docs/components.ts new file mode 100644 index 0000000..4c6241c --- /dev/null +++ b/apps/docs/components.ts @@ -0,0 +1,13 @@ +/** + * Project MDX components, available on every page with no import. Static + * `.astro` files ship no framework JS (a custom element carries any + * behaviour); interactive React lives in `islands/` instead. + */ +import { defineComponents } from "blume"; + +export default defineComponents({ + mdx: { + Carousel: "./components/Carousel.astro", + CarouselImage: "./components/CarouselImage.astro", + }, +}); diff --git a/apps/docs/components/Carousel.astro b/apps/docs/components/Carousel.astro new file mode 100644 index 0000000..1b557ee --- /dev/null +++ b/apps/docs/components/Carousel.astro @@ -0,0 +1,170 @@ +--- +/** + * A horizontal carousel of slides, built the way Blume's own + * content components are: static markup, a custom element for the behaviour, + * no framework. The track is a CSS scroll-snap strip (so it also swipes on + * touch and the browser does the animation); prev/next sit outside it and + * the play toggle sits below with a "n / N" counter. + * + * `time` is seconds per slide while playing (default 1.25); `autoplay` starts + * playing on load. Play wraps around, pauses whenever the tab is hidden, and + * never starts on its own for a reader who prefers reduced motion — the + * button still works for them. Manual navigation (buttons, arrow keys, swipe) + * does not stop playback; it only restarts the interval so a step is never + * doubled. + */ +import Icon from "blume/components/Icon.astro"; + +interface Props { + /** Seconds each slide stays while playing. */ + time?: number; + autoplay?: boolean; +} + +const { time = 1.25, autoplay = false } = Astro.props; + +const buttonClass = + "inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-colors hover:border-foreground hover:text-foreground disabled:cursor-default disabled:opacity-40"; +--- + + +
+ +
+ +
+ +
+
+ + +
+
+ + diff --git a/apps/docs/components/CarouselImage.astro b/apps/docs/components/CarouselImage.astro new file mode 100644 index 0000000..883d946 --- /dev/null +++ b/apps/docs/components/CarouselImage.astro @@ -0,0 +1,45 @@ +--- +/** + * One slide of a : Blume's own around an , so a slide + * is pixel-identical to a framed image elsewhere on the site — `alt` doubles + * as the frame's hint line and `caption` is Markdown, exactly as in Frame. + * + * `src` accepts what MDX authors already write for images: a URL, a + * root-relative `/file.png` (served from public/, prefixed with the deploy + * base), or the page-relative `../../public/file.png` form. A relative path + * that does not pass through public/ has nothing to serve it, so it is a + * build error rather than a silent 404. + */ +import Frame from "blume/components/content/Frame.astro"; +import { withBase } from "blume/components/islands/base-path.ts"; + +interface Props { + src: string; + alt?: string; + caption?: string; +} + +const { src, alt, caption } = Astro.props; + +const resolveSrc = (value: string): string => { + if (URL.canParse(value)) return value; + if (value.startsWith("/")) return withBase(value); + const at = value.indexOf("public/"); + if (at === -1) { + throw new Error( + `: a relative src must point into public/ (or use /file.png)` + ); + } + return withBase(`/${value.slice(at + "public/".length)}`); +}; +--- + + + + {alt + + diff --git a/apps/docs/content/index.mdx b/apps/docs/content/index.mdx index 43b966b..265288a 100644 --- a/apps/docs/content/index.mdx +++ b/apps/docs/content/index.mdx @@ -3,7 +3,7 @@ title: Overview description: Live SRS campaign dashboard — phases, ladder, review queue, gates, and recent attempts, fetched from the SRS Worker. --- -![Dependency Map](../public/dependency-spine.svg) + Live view of the 56-day campaign. Everything below is fetched client-side from the SRS Worker, so it is always current — no rebuild required. diff --git a/apps/docs/content/topological-sort/index.mdx b/apps/docs/content/topological-sort/index.mdx index d4ef87a..64e9354 100644 --- a/apps/docs/content/topological-sort/index.mdx +++ b/apps/docs/content/topological-sort/index.mdx @@ -8,11 +8,11 @@ hidden: true Before we get started on topological order, let's talk about directed graphs first. A graph is directed when its edges have directions (these edges are also called arcs). Suppose that v, w are vertices in a directed graph. Let's get familiar with a few concepts. -- in-edge - an edge pointing to or coming into the node -- out-edge - an edge pointing away or going out from the node (we will refer those nodes on the other end of the out-edges of v to v's neighbors) -- in-degree - the number of in-edges coming into the node -- out-degree - the number of out-edges going out from the node -- dependency - "v depends on w" / "v is dependent on w" means that there is a directed path from w to v where v, w are vertices in a directed graph. With these new terms under our belt, it will make more sense when we talk about directed graphs. +- **in-edge** - an edge pointing to or coming into the node +- **out-edge** - an edge pointing away or going out from the node (we will refer those nodes on the other end of the out-edges of v to v's neighbors) +- **in-degree** - the number of in-edges coming into the node +- **out-degree** - the number of out-edges going out from the node +- **dependency** - "v depends on w" / "v is dependent on w" means that there is a directed path from w to v where v, w are vertices in a directed graph. With these new terms under our belt, it will make more sense when we talk about directed graphs. ## What is a topological order? @@ -33,10 +33,33 @@ Task 3 is completely independent of task 2 and 4, and it can be anywhere in the It should be obvious that if a graph with a cycle does not have a topological ordering. In the above example, 2 has to come before 5 which has come before 4 which has to come before 2 which has to come before 5... which is impossible. ::: +## Kahn's Algorithm + +To obtain a topological order, we can use Kahn's algorithm which is very similar to Breadth First Search. + +In order to understand why we must use this algorithm we first look at the problem it is trying to solve. + +Given a directed graph does there exist a way to remove the nodes such that each time we remove a node we guarantee that no other nodes point to that particular node? + +For this algorithm, we systematically remove one node at a time, each time removing a node such that no other nodes point to that node (in-degree is 0). If no such node exists, then there must be a cycle, and there is no way to order the nodes such that "every node appears in the ordering before all the nodes it points to" (no solution). After removing the current node, for each neighboring node the current node points to, we check whether any nodes point to this node. If there isn't any, we push this node into the queue. Notice it is important to keep track of the number of nodes pointing to a node (in-degree) in the question as we only push the node into the queue once all nodes the current node depended on have been removed. + +Here is a graphic to demonstrate the idea. + + + + + + + + + + + + ## Example Code -:::note +:::tip Notice the topological sort algorithm is very similar to BFS. The main difference is that we only push nodes with 0 in-degree into the queue in topological sort whereas in BFS we push all the neighboring nodes into the queue. ::: diff --git a/apps/docs/islands/DependencySpine.tsx b/apps/docs/islands/DependencySpine.tsx new file mode 100644 index 0000000..6f393ec --- /dev/null +++ b/apps/docs/islands/DependencySpine.tsx @@ -0,0 +1,377 @@ +/** + * Dependency spine island — the "how the 24 topics build on each other" map + * from the README, redrawn as data (nodes on a 7×6 grid, edges as SVG path + * strings) so it is themed, zoomable, and clickable. + * + * - Wheel over the figure zooms about the cursor (trackpad pinch arrives as a + * ctrl+wheel and behaves the same); dragging pans; the toolbar and +/-/0 + * keys do the same. Zooming OUT at the minimum scale is deliberately left + * to the page, so a reader scrolling past the map is never trapped in it. + * - A node is a link to its topic. Routes come from Blume's own navigation + * endpoint at mount: the topic's landing page when the folder has an + * `index.mdx`, else its first solution page. A topic with no pages yet is + * drawn inert rather than linked to a 404, so the map stays honest as the + * campaign fills in without a rebuild of this file. + * + * Invariants: the first frame is the figure at HOME_ZOOM, centred, with no + * links (server and client render identically); the geometry is the README + * SVG's, to the pixel, so `viewBox` maths below is in that 1472×648 space. + */ +import { useEffect, useRef, useState, type CSSProperties, type PointerEvent } from "react"; +import { withBase } from "blume/components/islands/base-path.ts"; + +export const client = "visible"; + +// ── figure data (README dependency-spine geometry) ─────────────── + +const W = 1472; +const H = 648; +const MAX_ZOOM = 6; +const NODE_W = 144; +const NODE_H = 48; +const COL_X = [56, 248, 472, 664, 888, 1080, 1272] as const; +const ROW_Y = [72, 144, 216, 288, 416, 488] as const; + +type Kind = "entry" | "topic" | "focal"; + +interface Topic { + n: number; + name: string; + sub?: string; + /** Sync's group slug for the README topic name — the docs folder. */ + slug: string; + kind: Kind; + col: number; + row: number; +} + +const TOPICS: Topic[] = [ + { n: 5, name: "Binary Search", slug: "binary-search", kind: "entry", col: 0, row: 0 }, + { n: 3, name: "Two Pointers", slug: "two-pointers", kind: "entry", col: 0, row: 1 }, + { n: 4, name: "Sliding Window", slug: "sliding-window", kind: "topic", col: 1, row: 1 }, + { n: 6, name: "Stack", slug: "stack", kind: "entry", col: 0, row: 2 }, + { n: 7, name: "Monotonic Stack", slug: "monotonic-stack", kind: "topic", col: 1, row: 2 }, + { n: 1, name: "Hash Lookup", slug: "hash-table", kind: "entry", col: 0, row: 3 }, + { n: 2, name: "Prefix Sum", slug: "prefix-sum", kind: "topic", col: 1, row: 3 }, + { n: 11, name: "Tree DFS", slug: "tree-dfs", kind: "focal", col: 2, row: 0 }, + { n: 13, name: "Binary Search Tree", slug: "binary-search-tree", kind: "topic", col: 2, row: 1 }, + { n: 14, name: "Heap", sub: "priority queue", slug: "heap-priority-queue", kind: "topic", col: 3, row: 1 }, + { n: 15, name: "Trie", slug: "trie", kind: "topic", col: 3, row: 2 }, + { n: 12, name: "Tree BFS", slug: "tree-bfs", kind: "entry", col: 2, row: 3 }, + { n: 8, name: "Linked List", slug: "linked-list", kind: "entry", col: 2, row: 4 }, + { n: 9, name: "Hybrid Structures", slug: "hybrid-structures", kind: "topic", col: 3, row: 4 }, + { n: 10, name: "Matrix Index Math", slug: "matrix-index-math", kind: "topic", col: 2, row: 5 }, + { n: 16, name: "Graph DFS", slug: "graph-dfs", kind: "topic", col: 4, row: 0 }, + { n: 18, name: "Topological Sort", slug: "topological-sort", kind: "topic", col: 5, row: 0 }, + { n: 19, name: "Union-Find", slug: "union-find", kind: "topic", col: 4, row: 1 }, + { n: 20, name: "Backtracking", slug: "backtracking", kind: "topic", col: 5, row: 1 }, + { n: 21, name: "1-D DP", sub: "dynamic programming", slug: "1-d-dynamic-programming", kind: "focal", col: 5, row: 2 }, + { n: 22, name: "Multi-D / Grid DP", slug: "multi-d-grid-dp", kind: "topic", col: 6, row: 2 }, + { n: 17, name: "Graph BFS", slug: "graph-bfs", kind: "topic", col: 4, row: 3 }, + { n: 23, name: "Greedy", slug: "greedy", kind: "topic", col: 5, row: 3 }, + { n: 24, name: "Intervals", slug: "intervals", kind: "topic", col: 6, row: 3 }, +]; + +/** "builds on" arrows, source → target, in the figure's coordinate space. */ +const EDGES: [string, string][] = [ + ["3 → 4", "M200,168 H248"], + ["6 → 7", "M200,240 H248"], + ["1 → 2", "M200,312 H248"], + ["8 → 9", "M616,440 H664"], + ["16 → 18", "M1032,96 H1080"], + ["21 → 22", "M1224,240 H1272"], + ["23 → 24", "M1224,312 H1272"], + ["11 → 16", "M616,84 H888"], + ["12 → 17", "M616,312 H888"], + ["11 → 13", "M544,120 V144"], + ["16 → 19", "M960,120 V144"], + ["18 → 20", "M1152,120 V144"], + ["20 → 21", "M1152,192 V216"], + ["21 → 23", "M1152,264 V288"], + ["5 → 13", "M200,96 H416 Q424,96 424,104 V160 Q424,168 432,168 H472"], + ["11 → 14", "M616,96 H640 Q648,96 648,104 V160 Q648,168 656,168 H664"], + ["11 → 15", "M616,108 H624 Q632,108 632,116 V232 Q632,240 640,240 H664"], + ["4 → 10", "M392,184 H432 Q440,184 440,192 V504 Q440,512 448,512 H472"], + ["1 → 9", "M128,336 V560 Q128,568 136,568 H728 Q736,568 736,560 V464"], +]; + +const ZONES: [string, number, number, number, number, number][] = [ + // label, x, y, w, h, label plate width + ["PHASE I — LINEAR STRUCTURES", 40, 40, 368, 312, 168], + ["PHASE III — HIERARCHICAL", 456, 40, 368, 312, 152], + ["PHASE II — NODAL & GRID", 456, 384, 368, 168, 144], + ["PHASES IV–V — RELATIONAL & DECISION-SPACE", 872, 40, 560, 312, 264], +]; + +// ── theme ──────────────────────────────────────────────────────── +// Everything is a Blume token so the map follows the site's light/dark +// scheme instead of the README's fixed black; tints go through color-mix. + +const fg = "var(--blume-foreground)"; +const muted = "var(--blume-muted-foreground)"; +const accent = "var(--blume-accent)"; +const tint = (color: string, pct: number) => `color-mix(in oklab, ${color} ${pct}%, transparent)`; + +const NODE_STYLE: Record = { + entry: { fill: tint(muted, 10), stroke: tint(muted, 70), strokeWidth: 1, tag: muted }, + topic: { fill: tint(fg, 4), stroke: fg, strokeWidth: 1, tag: muted }, + focal: { fill: tint(accent, 12), stroke: accent, strokeWidth: 1.2, tag: accent }, +}; + +const mono: CSSProperties = { fontFamily: "var(--blume-font-mono, ui-monospace, monospace)" }; +const sans: CSSProperties = { fontFamily: "var(--blume-font-sans, system-ui, sans-serif)" }; + +const toolButton: CSSProperties = { + width: "1.75rem", + height: "1.75rem", + border: "1px solid var(--blume-border)", + borderRadius: "9999px", + background: "var(--blume-background)", + color: muted, + font: "inherit", + lineHeight: 1, + cursor: "pointer", +}; + +// ── navigation lookup ──────────────────────────────────────────── + +interface NavGroup { + kind: "group"; + path: string; + route?: string | null; + children: unknown[]; +} + +const isGroup = (v: unknown): v is NavGroup => + typeof v === "object" && v !== null && (v as NavGroup).kind === "group" && + typeof (v as NavGroup).path === "string" && Array.isArray((v as NavGroup).children); + +/** The first page route under a sidebar entry, depth-first. */ +const firstRoute = (items: unknown[]): string | null => { + for (const item of items) { + if (typeof item !== "object" || item === null) continue; + const v = item as { route?: unknown; children?: unknown }; + if (typeof v.route === "string") return v.route; + if (Array.isArray(v.children)) { + const inner = firstRoute(v.children); + if (inner) return inner; + } + } + return null; +}; + +/** folder path (`/tree-dfs`) → the route a node should open. */ +const topicRoutes = (nav: unknown): Record => { + const out: Record = {}; + const sidebar = (nav as { sidebar?: unknown })?.sidebar; + if (!Array.isArray(sidebar)) return out; + for (const entry of sidebar) { + if (!isGroup(entry)) continue; + const route = entry.route ?? firstRoute(entry.children); + if (route) out[entry.path] = route; + } + return out; +}; + +// ── component ──────────────────────────────────────────────────── + +interface ViewBox { x: number; y: number; w: number; h: number } +/** The resting view — 2× on the centre so node labels read at the content width; 1× stays reachable by zooming out. */ +const HOME_ZOOM = 2; +const HOME: ViewBox = { x: (W - W / HOME_ZOOM) / 2, y: (H - H / HOME_ZOOM) / 2, w: W / HOME_ZOOM, h: H / HOME_ZOOM }; +const clamp = (v: number, lo: number, hi: number) => Math.min(Math.max(v, lo), hi); + +export default function DependencySpine() { + const svgRef = useRef(null); + const [vb, setVbState] = useState(HOME); + // The native wheel listener and back-to-back events read the latest view + // from the ref, which is written synchronously — state alone lags a render. + const vbRef = useRef(vb); + const setVb = (next: ViewBox) => { + vbRef.current = next; + setVbState(next); + }; + const [routes, setRoutes] = useState | null>(null); + const drag = useRef<{ x: number; y: number; moved: boolean } | null>(null); + + useEffect(() => { + let live = true; + fetch(withBase("/api/docs/navigation.json")) + .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status))))) + .then((nav) => live && setRoutes(topicRoutes(nav))) + .catch(() => live && setRoutes({})); + return () => { live = false; }; + }, []); + + /** Zoom by `factor` (>1 in) about a client-space point; omitted = centre. */ + const zoomAt = (factor: number, clientX?: number, clientY?: number) => { + const rect = svgRef.current?.getBoundingClientRect(); + const cur = vbRef.current; + if (!rect) return; + const px = clientX === undefined ? cur.x + cur.w / 2 : cur.x + ((clientX - rect.left) / rect.width) * cur.w; + const py = clientY === undefined ? cur.y + cur.h / 2 : cur.y + ((clientY - rect.top) / rect.height) * cur.h; + const w = clamp(cur.w / factor, W / MAX_ZOOM, W); + const f = cur.w / w; + const h = H * (w / W); + setVb({ x: clamp(px - (px - cur.x) / f, 0, W - w), y: clamp(py - (py - cur.y) / f, 0, H - h), w, h }); + }; + + // React registers `wheel` as passive, so preventDefault has to go native. + useEffect(() => { + const svg = svgRef.current; + if (!svg) return; + const onWheel = (e: WheelEvent) => { + if (e.deltaY > 0 && vbRef.current.w >= W) return; // fully zoomed out: let the page scroll + e.preventDefault(); + zoomAt(Math.exp(-e.deltaY * 0.0015), e.clientX, e.clientY); + }; + svg.addEventListener("wheel", onWheel, { passive: false }); + return () => svg.removeEventListener("wheel", onWheel); + }, []); + + const onPointerDown = (e: PointerEvent) => { + if (e.button !== 0) return; + drag.current = { x: e.clientX, y: e.clientY, moved: false }; + }; + const onPointerMove = (e: PointerEvent) => { + const d = drag.current; + const rect = svgRef.current?.getBoundingClientRect(); + if (!d || !rect) return; + const dx = e.clientX - d.x; + const dy = e.clientY - d.y; + if (!d.moved && Math.hypot(dx, dy) < 4) return; // a click, not a drag + if (!d.moved) { + // Capture only once it is a drag: capturing on pointerdown would retarget + // pointerup (and so the click) to the , and a node's link would never fire. + d.moved = true; + e.currentTarget.setPointerCapture(e.pointerId); + } + d.x = e.clientX; + d.y = e.clientY; + const cur = vbRef.current; + const scale = cur.w / rect.width; + setVb({ ...cur, x: clamp(cur.x - dx * scale, 0, W - cur.w), y: clamp(cur.y - dy * scale, 0, H - cur.h) }); + }; + const onPointerUp = (e: PointerEvent) => { + if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId); + // Cleared on the next tick so a node's click handler can still see `moved`. + setTimeout(() => { drag.current = null; }, 0); + }; + + const zoom = W / vb.w; + const atHome = vb.x === HOME.x && vb.y === HOME.y && vb.w === HOME.w; + + return ( +
{ + if (e.key === "+" || e.key === "=") zoomAt(1.25); + else if (e.key === "-") zoomAt(0.8); + else if (e.key === "0") setVb(HOME); + else return; + e.preventDefault(); + }} + tabIndex={0} + > +
+ + scroll to zoom · drag to pan · click a topic + + + {zoom.toFixed(1)}× + + + + +
+ + 1.001 ? "grab" : "default", userSelect: "none" }} + onPointerDown={onPointerDown} + onPointerMove={onPointerMove} + onPointerUp={onPointerUp} + onPointerCancel={onPointerUp} + onDoubleClick={() => setVb(HOME)} + > + LeetCode topic dependency spine + + Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs. + + + + + + + + {ZONES.map(([label, x, y, w, h, plate]) => ( + + + + {label} + + ))} + + {EDGES.map(([id, d]) => ( + + {id} + + ))} + + {TOPICS.map((t) => { + const x = COL_X[t.col]!; + const y = ROW_Y[t.row]!; + const s = NODE_STYLE[t.kind]; + const route = routes?.[`/${t.slug}`]; + const tag = String(t.n).padStart(2, "0"); + const body = ( + <> + {route ? `${tag} · ${t.name} — open topic` : `${tag} · ${t.name} — no pages yet`} + + + + {tag} + {t.name} + {t.sub && {t.sub}} + + ); + return route ? ( + { if (drag.current?.moved) e.preventDefault(); }} + > + {body} + + ) : ( + {body} + ); + })} + + + LEGEND + + ENTRY — NO PREREQS + + TOPIC + + FOCAL HUB + + BUILDS ON + + PHASE ZONE + + NO PAGES YET + + + + ); +} diff --git a/apps/docs/public/dependency-spine.html b/apps/docs/public/dependency-spine.html deleted file mode 100644 index fab07ab..0000000 --- a/apps/docs/public/dependency-spine.html +++ /dev/null @@ -1,305 +0,0 @@ - - - - - - LeetCode Dependency Spine - - - - -
-

prdlk / leetcode · dependency spine

-

How the 24 topics build on each other

- - - LeetCode topic dependency spine - Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs. - - - - - - - - - - - - PHASE I — LINEAR STRUCTURES - - - - PHASE III — HIERARCHICAL - - - - PHASE II — NODAL & GRID - - - - PHASES IV–V — RELATIONAL & DECISION-SPACE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 05 - Binary Search - - - - - - 03 - Two Pointers - - - - - - 04 - Sliding Window - - - - - - 06 - Stack - - - - - - 07 - Monotonic Stack - - - - - - 01 - Hash Lookup - - - - - - 02 - Prefix Sum - - - - - - 11 - Tree DFS - - - - - - 13 - Binary Search Tree - - - - - - 14 - Heap - priority queue - - - - - - 15 - Trie - - - - - - 12 - Tree BFS - - - - - - 08 - Linked List - - - - - - 09 - Hybrid Structures - - - - - - 10 - Matrix Index Math - - - - - - 16 - Graph DFS - - - - - - 18 - Topological Sort - - - - - - 19 - Union-Find - - - - - - 20 - Backtracking - - - - - - 21 - 1-D DP - dynamic programming - - - - - - 22 - Multi-D / Grid DP - - - - - - 17 - Graph BFS - - - - - - 23 - Greedy - - - - - - 24 - Intervals - - - - LEGEND - - - ENTRY — NO PREREQS - - - TOPIC - - - FOCAL HUB - - - BUILDS ON - - - PHASE ZONE - -
- - diff --git a/apps/docs/public/dependency-spine.svg b/apps/docs/public/dependency-spine.svg deleted file mode 100644 index 1eb30df..0000000 --- a/apps/docs/public/dependency-spine.svg +++ /dev/null @@ -1,244 +0,0 @@ - - - LeetCode topic dependency spine - Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs. - - - - - - - - - - - - - PHASE I — LINEAR STRUCTURES - - - - PHASE III — HIERARCHICAL - - - - PHASE II — NODAL & GRID - - - - PHASES IV–V — RELATIONAL & DECISION-SPACE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 05 - Binary Search - - - - - - 03 - Two Pointers - - - - - - 04 - Sliding Window - - - - - - 06 - Stack - - - - - - 07 - Monotonic Stack - - - - - - 01 - Hash Lookup - - - - - - 02 - Prefix Sum - - - - - - 11 - Tree DFS - - - - - - 13 - Binary Search Tree - - - - - - 14 - Heap - priority queue - - - - - - 15 - Trie - - - - - - 12 - Tree BFS - - - - - - 08 - Linked List - - - - - - 09 - Hybrid Structures - - - - - - 10 - Matrix Index Math - - - - - - 16 - Graph DFS - - - - - - 18 - Topological Sort - - - - - - 19 - Union-Find - - - - - - 20 - Backtracking - - - - - - 21 - 1-D DP - dynamic programming - - - - - - 22 - Multi-D / Grid DP - - - - - - 17 - Graph BFS - - - - - - 23 - Greedy - - - - - - 24 - Intervals - - - - LEGEND - - - ENTRY — NO PREREQS - - - TOPIC - - - FOCAL HUB - - - BUILDS ON - - - PHASE ZONE - diff --git a/apps/docs/public/topo_kahn.001.png b/apps/docs/public/topo_kahn.001.png new file mode 100644 index 0000000..7c103cd Binary files /dev/null and b/apps/docs/public/topo_kahn.001.png differ diff --git a/apps/docs/public/topo_kahn.002.png b/apps/docs/public/topo_kahn.002.png new file mode 100644 index 0000000..65604a6 Binary files /dev/null and b/apps/docs/public/topo_kahn.002.png differ diff --git a/apps/docs/public/topo_kahn.003.png b/apps/docs/public/topo_kahn.003.png new file mode 100644 index 0000000..9c86ba1 Binary files /dev/null and b/apps/docs/public/topo_kahn.003.png differ diff --git a/apps/docs/public/topo_kahn.004.png b/apps/docs/public/topo_kahn.004.png new file mode 100644 index 0000000..8d85d29 Binary files /dev/null and b/apps/docs/public/topo_kahn.004.png differ diff --git a/apps/docs/public/topo_kahn.005.png b/apps/docs/public/topo_kahn.005.png new file mode 100644 index 0000000..1ef4863 Binary files /dev/null and b/apps/docs/public/topo_kahn.005.png differ diff --git a/apps/docs/public/topo_kahn.006.png b/apps/docs/public/topo_kahn.006.png new file mode 100644 index 0000000..f9f4b4d Binary files /dev/null and b/apps/docs/public/topo_kahn.006.png differ diff --git a/apps/docs/public/topo_kahn.007.png b/apps/docs/public/topo_kahn.007.png new file mode 100644 index 0000000..88b5dbc Binary files /dev/null and b/apps/docs/public/topo_kahn.007.png differ diff --git a/apps/docs/public/topo_kahn.009.png b/apps/docs/public/topo_kahn.009.png new file mode 100644 index 0000000..ef2320d Binary files /dev/null and b/apps/docs/public/topo_kahn.009.png differ diff --git a/apps/docs/public/topo_kahn.010.png b/apps/docs/public/topo_kahn.010.png new file mode 100644 index 0000000..da9f88b Binary files /dev/null and b/apps/docs/public/topo_kahn.010.png differ