Skip to content
kc@kumarChandrachooda.com:~$ cd /design-system/chrome && read --section="01 brand" 0%
KC Design System — Classic / Sketch

Flow with it.
One system, every component.

The classic skin: Inter typography, ink-black 2px borders and the periwinkle accent, expressed as CSS custom properties in design-system.css. The resume and the blog are both built from these parts.

Specimen

One card, re-inked and re-shaped by whatever the two columns say.

Primary chip

Set — Classic / Sketch

Chrome

Brand marks

Four lockups the name can wear, all one block. The base .ds-brand is what the site ships — the bitmap logo beside the full name — and the three modifiers are the alternatives. --monogram sets the initials in the signature 2px box beside the name; --plate is the postmark, the name stacked over a mono role kicker on a filled slab; and --dot is the compact mark, lowercase mono initials closed by the single full stop that carries the accent. None of them fork for dark: every colour resolves through a --ds-brand-* property, so an ink surface declares .ds-on-dark once and the marks follow — the same eight tokens the scrolled header already flips, which is why it is named in that block rather than restating them.
The base lockup (.ds-brand, no modifier). The mark the site actually wears: the 40px bitmap logo and the full name, set at --ds-text-md bold with a touch of tracking. It is the only lockup whose glyph is an image rather than type, so on ink it cannot recolour through a token — it inverts, exactly as the scrolled band inverts it. The name is an unclassed span, which is why the dark rule has to come from the surface rather than from the element.
The monogram (.ds-brand--monogram). Initials in the signature 2px box, name alongside — the smallest lockup that still says both things. The box stays an outline on either surface and only its stroke colour moves, so the mark never turns into a different shape in the dark; hover fills it periwinkle, the one move it borrows from the button. Square corners: it is a surface, not a control.
The plate (.ds-brand--plate). The postmark: the name over a mono role kicker in a drawn 2px frame — the one place the brand is allowed two lines. It used to be a filled slab, which made it the only lockup that inverted against its surface rather than with it. It no longer does: the frame stays an outline on both surfaces and only its stroke colour moves, exactly as the monogram's box does, so the lockup never becomes a different shape in the dark. That costs one token (--ds-brand-plate-edge) and buys back a whole category of surprise.
The frame is a pseudo-element, not a border on the box, and that is not a flourish: a real border occupies layout, so it could not be absent from a folded plate without shifting every row by 2px. Being a pseudo lets it be drawn — which is the last beat of the fold below. The three rows are three elements, each leading with a .ds-brand__seed: the glyph of the dot mark that row grew from.
The dot (.ds-brand--dot). Lowercase mono initials closed by a full stop — the compact mark, for the places a full name will not fit. The stop is the only place the accent is spent, and it is the only thing that moves in the dark: periwinkle on paper, violet on ink, where the stronger tone would vibrate. --ds-brand-dot-size carries the same mark from this header scale to the 3rem loader splash without a second rule. It reads as initials, not a word, so the link carries the name in its aria-label.
Plate to dot, and back (.ds-brand-fold). The last two lockups are not two marks — the plate is the dot, unfolded. Each of the plate's three rows carries the glyph it grew from, so folding does not swap one lockup for another, it stands the same three glyphs back down into a row. That is what lets a column narrow to 84px without the identity at the top of it blinking into something else.
Four beats, in order: kc. stands up from a row into a column and shrinks to the plate's own size as it goes; the frame is drawn round the column it now encloses; each glyph hands off; each word writes out, one row after another. The glyph is gone before its word arrives — lowercase mono cannot be interpolated into bold sans, and two typefaces caught overlapping read as a glitch rather than as a handoff.
A fold only means anything at two widths, so each frame below is a column that narrows, built from the rail's own tokens. Hover one, or tab into it. On the live rail the same two states are the collapse and the breather. Both surfaces, as everywhere else on this page — and nothing in the pair is restated for the dark one. The frame, the glyphs and the kicker all read the plate's own tokens, so the fold inverts by standing on a different surface rather than by being told to.
Chrome

Icons

One canvas, one stroke, one place they live (_Icon): 24×24, bare geometry carrying nothing but coordinates, and a 2.4 stroke supplied by whatever surface the mark lands on. Nothing here names a colour, which is the only reason the same twenty marks can sit on paper in one row and inside an ink plate in the next without a second copy of themselves — the two panels below are that claim, run twice on identical markup. Square caps and miter joins because there is not a rounded corner anywhere in this system; angles of 0, 45 and 90 and nothing else. The set is also honest about itself. The fifteen destination marks and the five chrome marks meet the contract; the twelve below the fold do not, and each one says which rule it breaks and where it ships. They are drawn at the weight they actually render, not corrected on the way to the page. The full contract — counter minimums, the element budget, the five-question review test — is in docs/guidelines/icon-style-guide.md.
The whole governed set at 20px — the size the rail renders, which is the only size the review test is run at. The two panels are the same twenty <partial> calls; the ink one declares a colour and changes nothing else.
On paper
On ink

Destinations

Fifteen marks, three of them level one and the rest level two. Collapsed, the rail is 84px and the icon is the row — so these carry the least detail in the system, and the three app marks carry the least of all.
app-resume frame + three pips
app-blog page, turned corner
app-design four brackets
home house
about bust
skills bars on a floor
experience a climb
projects a hung selection
certifications medal and ribbon
overview front page as layout
topics four equal cells
series stacked sheets
articles marked rows
classic square cut down
flow square cut across
sketch square drawn freehand

Chrome

The marks on controls. Same canvas, same stroke, same silence about colour. close was inline in nine places before this section existed — three in the drawer, three in the modal, two in the toast, one in this page's own chrome — and every copy agreed, which is the kind of agreement that holds until someone edits one of them.
close drawer, modal, toast
menu drawer tab
chevron-left carousel, rail latch
chevron-right carousel, cta
download rail cv button

Off the contract

Twelve marks that ship and do not meet the rules. They are drawn here exactly as they render on their own pages — the 1.6-weight ones at 1.6, the round caps round, the solid ones solid — because a specimen redrawn to the contract would quietly hide the thing worth seeing. Set them beside the panels at the top of this section: the feature and counter marks read as a lighter, softer family, and they are on the same screen as the rail. Nothing here is a bug. It is a list of decisions that were made one at a time, before there was a set to make them against.
download-document resume cv buttons Filled, and bezier arcs in the corners. Draws the same action as the hollow download above it.
linkedin site footer Filled, bezier throughout. A third-party brand mark — kept whole on purpose, since a redrawn LinkedIn is still theirs.
lightbulb skills · feature card Bezier curves; drawn at stroke-width 1.6 rather than 2.4.
monitor skills · feature card rx="2" corner radius; stroke-width 1.6.
flag skills · feature card Half-unit coordinates, and the angles they make are neither 45 nor 90; stroke-width 1.6.
code-brackets skills · feature card The slash is six degrees off vertical where only 0, 45 and 90 are permitted; stroke-width 1.6.
compass skills · counter Half-unit coordinates and free angles; stroke-width 1.6 with round caps and joins.
pencil skills · counter Bezier arc in the nib, half-unit coordinates; 1.6 with round caps.
monitor-stat skills · counter rx="2"; 1.6 with round caps. A second monitor, one unit shorter than the first — two marks for one object.
award skills · counter Decimal coordinates; 1.6 with round caps. The same award as certifications, drawn again to different rules.
caret-right deck indicator A closed triangle, and the only mark in the system carrying fill and stroke on the path itself.
caret-down deck indicator Filled. It marks the current slide, and solid is how the indicator says so.

Injected by the behaviour layer

Three marks go into elements that do not exist until JavaScript runs — the code block's copy button, its copied state, and the anchor on every prose heading — so a Razor partial cannot reach them. The geometry has a key here and the strings that actually ship live in wwwroot/js/ds/. That is the one real seam in the set: two copies of one drawing, which have to be changed together. Saying so is cheaper than a build step that would prove it.
copy util.js · COPY_ICON Filled, bezier corners.
check util.js · CHECK_ICON Filled; the tick is an outline, so its weight cannot follow the surface.
link prose.js · headings Bezier arcs throughout; stroke-width 2 with round caps.

Using one

<partial name="_Icon" model='@(new IconSpec("close"))' />

<!-- the rail is the one surface that needs a class on the element,
     because it hangs per-glyph modifiers off the same hook -->
<partial name="_Icon" model='@(new IconSpec(app.Icon, "ds-rail__icon"))' />
/* everything else styles the glyph through its container, and the mark
   supplies nothing but coordinates */
.ds-drawer__close svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: square;
  stroke-linejoin: miter;
}
An unrecognised key renders nothing — not a placeholder, not a dashed box. A row that lost its icon should look like a row without one, so the fault reads as a gap rather than as a glyph someone has to learn to recognise as an error. The full contract, including the counter minimums that decide what can be drawn at all and the five-question review test, is in docs/guidelines/icon-style-guide.md.
Chrome

Spine rail & two-level navigation

The header turned through ninety degrees (--rail): chrome leaves the top edge for the left margin, and the room that buys pays for the second level of navigation the band never had space for. The three apps stack as one bordered plate; beneath them the 2px rule turns inward into a timeline carrying the current app's sections. Switch app and the spine re-populates — slides in the Resume, archive surfaces in the Blog, the two skins here — while the geometry never moves. Primary wears the ink, secondary wears the accent, so a place you are and a position that moves can sit 40px apart and still never be confused. The latch parks it at one cell wide, and from there hover or focus opens the full column over the page rather than pushing it: horizontal padding is the same at both widths, so the cell lands on the same pixel and collapsing changes what is written beside a row, never where the row is. Below 992px it becomes a slide-over, which keeps the two levels stacked instead of flattening them into one menu.
The rail. The header turned through ninety degrees: chrome leaves the top edge for the left margin, and in the room that buys it grows the second level of navigation the band never had space for. Brand, then the three apps as one bordered plate divided by the 2px rule, then that rule turned inward into a timeline carrying the current app’s sections, then the CV button on the floor. Primary wears the ink, secondary wears the accent — the current app is a filled plate, a place you are; the current section is a periwinkle marker, a position that moves. Two levels, two colours, and no way to confuse them.
Switch app, and the spine re-populates. The geometry never moves — only the label and the rows under it. In the Portfolio the spine reads slides and lists the deck’s panels; in the Blog it reads sections and lists the four surfaces of the archive. Both come from one ISiteNavigation, so a destination cannot exist in the rail and nowhere else.
The shortest spine on the site. This page’s own second level is not its forty-one sections — those are a table of contents for one page, and they stay in the drawer. It is which skin you are reading in: Classic or Flow. Two rows, and the spine still reads as the same component, because a level that can be short without looking broken is a level that can be honest about how much is under it.
Collapsed, and the breather. Hover the rail below. The latch parks it at one cell wide; from there hover or keyboard focus opens the full column over the page rather than pushing it, on the pop shadow that anything floating free of an edge earns. The page’s margin follows the resting width and never the hovered one, so content holds still while the column passes across it — the reason to overlay rather than reflow is that a reader glancing at the nav should not lose their place in the text. Closing carries a --ds-rail-close-delay so a cursor travelling diagonally back to the page does not snap the column shut as it clips the corner; opening has none.
Nothing moves when the rail breathes. Every row is a --ds-rail-cell square plus a label that fades, and the horizontal padding is identical in both states, so the collapsed width is derived — cell plus two paddings — rather than picked. The cell therefore lands on the same pixel either way: collapsing changes what is written beside a row, never where the row is. That is what stops it reading as two components swapping. The same discipline decides the marks: the plate is the lockup drawn for a corner and tops the open column, but 84-odd pixels have room for initials and nothing else, so collapsed always falls back to the dot — and it is swapped by display, not opacity, so only one of the two is ever in the accessibility tree. The icons are load-bearing here, not decoration: collapsed, the cell is the entire row, and a mono number that read fine beside a word does not survive being the only thing left. They follow their own style guide (docs/guidelines/icon-style-guide.md) — 24px canvas, 2.4 stroke, square caps, no curves, because a set that drifts in weight stops reading as one row.
Below 992px, a band and a full-page menu. A phone cannot spare a permanent 264px column, so the rail hands the page its width back and keeps only a band — the mark and the toggle, in both states. What the toggle opens is the page: a 264px drawer is a shape borrowed from the column it came from, and at this width the column is not the point. The map gets the screen, carrying the same two levels in the same order — order kept intact rather than two levels reflowed into one flat menu is the whole reason the component exists. There is no scrim, because a full-page panel has no outside to dim; the toggle stays put above it and becomes the way out. 992px is deliberate: it is the same threshold the deck disengages at, so the Portfolio never has a rail without slides to track. Shown here at desktop width, declared open rather than tapped open.
Two class choices carry the component. The panel is .ds-rail__nav, not .ds-nav: the phone overlay restyles every .ds-nav descendant into the full-screen ink menu — down to a counter on ::before, which is precisely where the spine draws its marker — and that block sorts after this one, so taking a different class is cheaper and steadier than out-specifying it. The section links, though, keep .ds-nav__link, because that is the hook the deck reads: any such link whose fragment names a panel is already wired to page the deck and to receive .is-active as it moves. The spine gets slide tracking for nothing, and pays only by overriding the underline wipe it would otherwise inherit.
Chrome

Reading progress strip

One line of chrome doing four jobs (.ds-progress): identity, app, the section actually being read, and how far through — written as a single shell invocation, because the mono voice is the brand’s own. The prompt holds the left end; the middle is a cd into the page whose --section flag rewrites itself by scroll-spy, caret still blinking; the right side composes up to three optional readouts — section counter, percentage, and the bar itself as either a 2px accent rule along the bottom edge or a ten-glyph ASCII meter drawn in text. Light-mode native on a hairline rather than an ink plate, so it can sit under the white header band without reading as a second header; --pinned sticks it below whatever chrome the page wears via --ds-chrome-top. The markup is passive; progressStrips() drives the flag, the counters and the fill, and speaks the state through role="progressbar". It ships live on all three apps, composed per app through ViewData["DsProgress"]: blog posts read --section over their own h2s with the percentage and the rule; this page reads its catalog into the ASCII meter — look up — and the resume deck presents a --slide flag with a slide counter, fed by the deck itself, because a stage that pages by transform has no scroll to spy on.
The command line — the default. The whole state as a single shell invocation: the prompt, a cd into the page, and a live --section flag that rewrites itself as you scroll, the caret still blinking at the end. The right side carries the section counter and the percentage; the progress bar is the 2px accent rule filling along the strip’s bottom edge — present, never competing with the text. Scroll inside the frame.
kc@kumarChandrachooda.com:~$ cd /blog/an-event-bus-in-seven-files && read --section="01 the-contract" section 1/5 0%

01The contract comes first

02Commands and events on the wire

03A shadow registry of every message

04Swapping the transport underneath

05Seven files, one idea

The ASCII meter — the same strip with the progress drawn in block glyphs instead of a rule: ████░░░░░░, ten characters filling in periwinkle beside the percentage. No track element, no div — the meter is text, which is exactly the joke and exactly the credibility. A strip carries the meter or the fill, never both; two progress readings a rounding error apart read as a bug.
kc@kumarChandrachooda.com:~$ cd /blog/an-event-bus-in-seven-files && read --section="01 the-contract" 0%

01The contract comes first

02Commands and events on the wire

03A shadow registry of every message

04Swapping the transport underneath

05Seven files, one idea

The readouts, composed — the right side is three optional spans, and a page keeps only what it needs: the section counter alone for a piece where the count is the point, the percentage for a long single read, and one of the two bars where the glance should not need numbers at all. Held at sixty-two percent so the four can be compared.
kc@kumarChandrachooda.com:~$ read --section="03 the-registry" section 3/5
kc@kumarChandrachooda.com:~$ read --section="03 the-registry" section 3/5 62%
kc@kumarChandrachooda.com:~$ read --section="03 the-registry" 62%
kc@kumarChandrachooda.com:~$ read --section="03 the-registry" 62%
PieceHookNotes
Strip.ds-progress[data-ds-progress]Wired by progressStrips(). Add --pinned to stick it below the page’s chrome via --ds-chrome-top.
Sections[data-ds-progress-mark="01 name"]On the content, in order. The last mark scrolled past the strip is “current”.
Flag value[data-ds-progress-section]Rewritten, quoted, as the reader scrolls.
Counter[data-ds-progress-index]1-based; the total is server-rendered.
Percentage[data-ds-progress-pct]Whole numbers; __pct reserves 4ch so 9→10 cannot nudge the row.
Rule fill.ds-progress__fillReads --ds-progress off the strip; 2px accent along the bottom edge.
ASCII meter[data-ds-progress-meter-on/-off]Ten glyphs, lit / spent. Carry one bar per strip, never both.
AppThe commandThe readouts
Blog postcd /blog/<slug> && read --section=…, spied off the article’s own h2sPercentage + rule fill — a read is one continuous measure.
Design systemcd /design-system && read --section=…, one mark per catalog sectionASCII meter + percentage — the audience that gets the joke.
Resume deckpresent --slide=… — no cd; the prompt is already homeSlide counter + rule fill, fed by ds-deck-state from the deck — six discrete slides make a percentage fake precision.
A page opts in by putting a ProgressStrip in ViewData["DsProgress"]; the layout renders it at the top of main through _ProgressStrip, pinned under the page’s chrome — or fixed over the deck, which has no scroll to pin in. The strip exposes role="progressbar" with aria-valuenow and a spoken aria-valuetext (“section 2 of 5, 34% read”); the meter glyphs and the caret are decoration and say so. Under prefers-reduced-motion the caret holds solid instead of blinking — still live, no longer motion. Below 768px the path drops from the command whole, then the prompt folds to kc:~$ — the constant and the answer survive, the piece already committed to goes first. The whole strip stays one line, under 30px, so it can never become a second header.