Devsync
Team tooling
Everything this site is assembled from. Colour tokens and type specs are read out of src/styles/global.css at build time, so this page cannot drift from the stylesheet — if a utility is added there and left undocumented here, the build fails.
Declared once in @theme, from which Tailwind derives bg-*, text-* and border-* utilities. The site is dark-only: there is no light theme and no dark: variant anywhere.
Surfacesbg-*--color-bg#020202--color-surface#333333--color-surface-secondary#141414Page and panel backgrounds.
Texttext-*--color-text-primary#ffffff--color-text-secondary#ebebeb--color-text-third#a2a2a2--color-text-inactive#757575Foreground hierarchy, brightest to dimmest.
Bordersborder-*--color-border#232323--color-border-secondary#292929Hairlines and separators.
Page backgroundradial-gradient · --color-bgA warm brown glow at 8% opacity, anchored near the top-left and layered over --color-bg. It is painted once on the document body, so it spans the viewport rather than repeating per section.
CATEGORY_PALETTEcomponents/outbox/outbox.ts#f5c518#4d9de0#9b59b6#2ecc71#e67e22#1abc9c#e74c3c#f1c40f#1c1c1cempty cellHanded to outbox categories in first-seen order, wrapping modulo the palette length. These live outside @theme because they are data, not theme.
Inter for everything, Gochi Hand for accents. Style text with a type-* utility rather than ad-hoc size and weight classes. Specs below read family · size · weight · line-height.
Families@theme · :rootThe quick brown fox jumps over the lazy dog
--font-sansfont-sansThe quick brown fox jumps over the lazy dog
--font-accentfont-accentThe quick brown fox jumps over the lazy dog
--monospace-font-familyno utilitytype-heading-124px · 500 · 1.4The "Generic MIDI controller"
Article titles. Its only call site pulls the weight back down with font-normal.
type-heading-220px · 500 · 1.5Learn to manage large Vue.js components
Defined, but nothing uses it yet.
type-heading-318px · 500 · 1.6Using scoped BEM with Tailwind
Defined, but nothing uses it yet.
type-body16px · inherit · 1.7Product engineer at Deducta, previously Devsync.
type-body-small14px · inherit · 1.7Short notes, reports, and resources.
type-body-large18px · inherit · 1.7Hi there, I’m Will
type-label14px · 450 · 1.4Form labels, tabs and controls. Weight 450 needs a variable Inter; only static 400/500/600 are fetched, so it resolves to the nearest available weight.
type-title16px · 400 · 1.2Outbox
The workhorse. Section headings, page titles and card names all use this rather than type-heading-*.
type-monospacedJetBrains Mono · 14px · 500 · 1.4npm run validate
JetBrains Mono is named in :root but never loaded, so this falls back to the system monospace.
type-accentGochi Hand · 1.35em · 500 · 1.7Hi there, I’m Will
Sized in em, so it scales with whatever it sits in. Built to be dropped inline into body copy.
One .prose class styles every piece of rendered markdown — blog posts and Obsidian notes alike. It reaches descendants through :where(), so any utility you add always wins over it.
.prose14px · 1.7Body copy sits at 14px on a 1.7 line-height. Inline links pick up an underline offset by 3px, and inline code gets a chip in the secondary surface colour.
Blockquotes take a 2px leading rule and drop to the tertiary text colour.
export function excerpt(content: string, length = 160): string {
const stripped = content.replace(/\s+/g, ' ').trim();
return stripped.length <= length ? stripped : stripped.slice(0, length) + '…';
}Headings shift to the secondary text colour at weight 500, with generous space above and tight space below.
Code blocks are highlighted by Shiki with the min-dark theme, which brings its own background — .prose only contributes radius, padding and overflow.
Inline SVG components sharing one signature: { class?: string; size?: number }. They paint with currentColor, so colour them from the parent — the two badges are the exception and carry a fixed blue.
components/icons7 icons · currentColorArrowRightAtSignChevronDownExternalLinkGitHubLabExperimentVerifiedBadgesizedefault 16 · ChevronDown 13Height follows the intrinsic ratio, so non-square icons stay undistorted.
The reusable pieces. Everything here is server-rendered Astro with no client framework; interactivity is a handful of inline scripts.
Slidermin · max · value · stepWraps a native range input and keeps the filled track in sync through a --progress custom property, seeded server-side so there is no flash. It holds no logic — listen for input on the consumer.
ProjectCardname · description · href? · verified? · status? · icon?Wrapped in ProjectsGrid, which is a bare two-column grid. The badge appears for verified, the flask for status='experiment', and the external-link affordance only when href is set.
FilterTabstabs: { label, href, filter?, active? }[]Active state is server-rendered, then reconciled client-side against the ?filter= query. The outbox uses the same visual recipe on buttons instead of links.
Ghost controltype-label · hover:text-text-thirdThe only button recipe on the site: stripped to text, with hover lifting it one step up the text ramp.
Text linktext-text-third → text-text-primaryInline links inside prose underline instead; this recipe is for standalone actions.
Text fieldstype-label + type-body-smallDeliberately chrome-free: transparent background, no border, no focus ring. The label and placeholder carry the affordance.
Tooltipgroup + group-hoverCSS only. The trigger owns group and cursor-help; the bubble fades and slides in. Used on the wordmark in the header.
FormattedDatedate: DateEmits a <time> element with a machine-readable ISO datetime and an en-US short display date.
Recurring layout decisions that are not components. Nothing in Tailwind's spacing, radius or breakpoint scales is overridden — only the colours and fonts are.
Page shellheader · main · footer<main class="mx-auto max-w-5xl px-6 sm:px-8 py-6 sm:py-8">Every page shares this measure and gutter, so the header aligns to page content. Reading columns narrow further with max-w-2xl.
Spacing0.25rem step · shown at true sizegap-3An icon beside its labelgap-4A label above its fieldgap-6Card internals, tab rowsgap-8Between items in a sectiongap-10Between sectionsgap-12Between documented examplesVertical page rhythm is py-6 sm:py-8 per section, the same padding the header uses.
Divided rows[[data-outbox-row]+&]:border-tA build log for a hardware MIDI controller with no fixed purpose.
Naming things when the utilities have already named them.
The rule lives on the row and keys off a preceding sibling, so the first row is never capped — and filtering by detaching rows keeps that true without extra bookkeeping.
Horizontal railno-scrollbar · snap-xUsed by the home carousel: scrollbars hidden, scrolling untouched, slides snapping to the start edge.
Empty statetype-body-small · text-text-inactiveNo posts in this category yet.
Centred, dimmed, and generously padded so a filtered list never collapses.