Design System · Project DNA

K13 DNA

The studio's own site is the argument, so its DNA is documented to the same standard as any client's. Every value below is transcribed byte-exact from index.html, beta_v7, "THE DRAFTING TABLE."

k13projects.com  •  static, single-file, no build step  •  prepared by K13 Software Studio

Foundations

Color Palette

Straight from index.html's :root (lines 30-42). One paper-light ground, one ink for structure, one burnt-orange accent, four soft washes for atmosphere. Worth saying plainly: --blue and --blue-2 are historical misnomers, holding the studio's real accent, burnt orange, not blue. The name survived a rebrand; the hex didn't. Left as-is on purpose, renaming a live token is a bigger risk than the confusion it causes a reader of this file.

Paper / ground, light surfaces
#F3F5FA
Paper--paperBody background, the site's one ground
#FFFFFF
Paper-2--paper-2Cards, sent-state panel, work-index thumbnail fallback
#E9EDF7
Paper-3--paper-3Capabilities band, portrait back-panel fill
Ink / structure
#141D35
Ink--inkHeadings, brand "K", hero gradient start, skip-link bg
#2A3554
Ink-2--ink-2Hero gradient end, body-adjacent text
Neutral text
#515C78
Muted--mutedBody copy default, card/row description text
#646B7E
Faint--faintMeta labels, tag text, brand tagline
Accent, the one interactive hue (misnamed "blue")
#B94612
Blue--blueLinks, CTAs, eyebrow color, brand "13", every interactive accent
#EA5E14
Blue-2--blue-2Hero radial wash, italic emphasis on dark, hover lift
Washes, soft atmosphere blobs
#FCE0CC
Wash-blue--wash-bluePeach-toned, not blue-toned; another naming holdover. Fixed blob, top-right
#E8E1FB
Wash-lilac--wash-lilacFixed blob, top-left
#D6F0E6
Wash-mint--wash-mintFixed blob, mid-page; also approach-card 02 tint
#FBE6DD
Wash-peach--wash-peachApproach-card 03 tint
Lines & glass
rgba(20,29,53,.12)
Line--lineThe default hairline, cards, section dividers
rgba(20,29,53,.22)
Line-2--line-2Slightly stronger, peek-preview frame, portrait frame
rgba(20,29,53,.52)
Line-3--line-3Strongest, ghost-button border, stepper field underline
rgba(255,255,255,.62)
Glass--glassHero status card + contact stepper background, paired with blur(16px) saturate(1.3)
Semantic (outside the named :root block, used once each)
#27B584
Success(unnamed token)"Open for projects" status dot, the beat pulse
#229F74
Success-2(unnamed token)Contact-form success ring, the pop keyframe
Retired, not canon
#CB4D14
Legacy orangergba(203,77,20,…)Pre-rebrand accent. Retired, but not fully swept: still tints the draft-grid dots and the about-portrait back-panel, and is baked into the primary button's own drop shadow

Found while building this guide, worth a fix, not fixed here: .btn's box-shadow is literally rgba(203,77,20,.7), the old accent, not var(--blue). It reads fine (both are burnt-orange family) but it's a real drift between the token and the pixel. See Tokens for the full list of legacy-orange sightings.

Foundations

Typography

Fraunces (500-700, plus italic 400-500) is the display face: warm, editorial, a little hand-set. It is rendered at weight 600 with tight tracking (-.015em to -.02em) so headings feel considered rather than heavy. Inter (400-700) carries body copy and UI chrome at 18px/1.65, unusually generous for a marketing site, chosen because the whole pitch is "clear." JetBrains Mono (400-700) is reserved for anything that reads as system information: eyebrows, nav links, tags, stat labels, meta lines, never for a sentence a visitor is meant to read as prose.

Type Scale

index.html :root, h1/h2/.hero-sub

Headings, body and eyebrow, set exactly as the hero renders them.

A studio for software that feels obvious.
K13 Software Studio · Kazim K.

Body copy, Inter 400, 18px, line-height 1.65, --muted on light surfaces. Plain, confident, no jargon, first person.

Display
'Fraunces', italic optical-size axis, weights 500/600/700 upright, 400/500 italic
Sans
'Inter', weights 400/500/600/700
Mono
'JetBrains Mono', weights 400/500/700
H1 (.hero h1)
clamp(40px,6vw,82px) · weight 600 · letter-spacing -.02em · line-height 1.02
H2 (.h2)
clamp(32px,4.6vw,56px) · weight 600 · letter-spacing -.015em
Body
18px / 1.65 · weight 400 · muted
Eyebrow
12px · mono · uppercase · .12em tracking · blue, with a 24px leading rule
Base weight
body defaults to 400 site-wide (not light, not thin)

Wordmark: the typographic lockup

.brand .k / .foot .fb · CLAUDE.md 2026-08-18

The only official K13 mark. Fraunces "K" set bold in ink, "13" set bold in the accent (rendered upright here, italic where it doubles as emphasis elsewhere), a two-line JetBrains Mono tag underneath. A rounded-square glyph mark existed once; Kazim ordered it destroyed on 2026-08-18 after it surfaced in a client deliverable he didn't recognize as his own. It is not canon and must never be regenerated.

K13Software
Studio
K
Fraunces 700 · 27px · ink
13
Fraunces 700 · 27px · blue (italic when used as inline emphasis, e.g. hero's "13" is upright here but the emphasis word in prose leans italic 500)
Tag
JetBrains Mono 10px · .16em tracking · uppercase · faint, two lines
Canon since
2026-08-18, reconfirmed in the 2026-08-19 brand triage
Foundations

Motion

One easing curve does almost everything here: --ease: cubic-bezier(.22,1,.36,1), the house "premium" reveal curve, fast to start and soft to land. Interactions run .3-.5s on it; scroll reveals run .8s; the two ambient loops (the status-dot pulse and, before it, nothing else) run 2s. There is no shine sweep and no idle "breathing" card anywhere on this site, restraint is the point. Every effect below is real, pulled from the live inline script and CSS, not the house's general motion vocabulary.

Signature reveal (.rv)

.rv / .rv.in · IntersectionObserver

Every above-the-fold block starts at opacity 0, translateY(26px). An IntersectionObserver (threshold 0.16, rootMargin "0px 0px -8% 0px") adds .in once, then unobserves, so it never re-fires on scroll-back. Hero elements are force-revealed after a 160ms timeout instead of waiting on the observer, so the first paint isn't empty.

.rv → .in
card
card
From
opacity 0, translateY(26px)
Duration
.8s both opacity and transform, var(--ease)
Trigger
IntersectionObserver, threshold .16, once
Stagger
none coded explicitly, the cascade you see is natural: each block crosses the trigger line at a different scroll moment

Ambient status pulse

@keyframes beat

The only truly looping animation on the page. A ring of success-green box-shadow expands and fades around the "Open for projects" dot, forever, on a 2s cycle. It is the one place the site allows itself to feel "alive" while sitting still.

Open for projects
Keyframes
0%/100% box-shadow 0 0 0 0 rgba(39,181,132,.5) → 60% 0 0 0 8px rgba(39,181,132,0)
Duration
2s infinite, default ease

Success pop-in

@keyframes pop · .sent .ring

The moment the contact stepper hands off to mailto:, a checkmark ring scales in from 40% with a fade. Runs once in production; looped here alternate for the demo.

✓
From
scale(.4), opacity 0
To
scale(1), opacity 1
Duration
.5s var(--ease), runs once

Header scroll condense

#hdr.scrolled · onScroll

Past 30px of scroll, the header tightens its padding and its translucent, blurred background fades in. Both are plain CSS transitions triggered by a class toggle, no scroll-linked animation.

K13at top · padding 18px
K13scrolled · padding 12px · blur(14px) bg
Trigger
window.scrollY > 30
Transition
padding .4s var(--ease); bg opacity .4s
Backdrop
rgba(243,245,250,.8), blur(14px) saturate default

Rotating status feed

#feed · inline script

The hero's "Currently…" line cross-fades through a shuffled list of ~40 one-liners every 3.8s (420ms fade). A visible, focusable Pause toggle sits as a DOM sibling of the card, not inside it, because the card itself is aria-hidden="true" (a decorative ticker) and aria-hidden on a descendant can't undo an ancestor's. Under reduced motion the whole feed script bails early, and the CSS hides the Pause button, a control with no listener attached is worse than no control.

Launching a public market site for UC San DiegoPutting every bottle on a scale, so free pours have nowhere left to hide
Pause
Interval
3800ms, opacity fade 420ms
Toggle
aria-pressed, swaps label Pause ↔ Play and the aria-label
Reduced motion
script exits before wiring the toggle; .feed-toggle{display:none} so no dead control ships

Portrait scroll depth

#portrait [data-depth] · IntersectionObserver + rAF

Three layers (a paper back-panel, the photo frame, the caption chip) sit at different data-depth multipliers (-.45, .08/.16, .16) and translate on the scroll position relative to viewport center, transform-only, GPU-friendly. An IntersectionObserver gates the scroll listener so it only runs while the portrait is actually visible.

photo
data-depth=.16
Formula
translate3d(0, p*depth*90, 0) where p = (top+height/2-vh/2)/vh
Depths
back-panel -.45 · frame .08 · image .16 (scale 1.06) · caption .16
Reveal
frame also clip-path insets from 9% to 0% over 1.1s var(--ease), delayed .1s, on .rv.in
Gate
skips entirely if prefers-reduced-motion, or while off-screen

Contact stepper transition

.screen-s / .screen-s.on/.past

Each question screen slides in from translateX(34px) with a fade, and slides the opposite way (-34px) when stepping backward past it, so going forward and going back read as different directions, not a mirrored animation.

Step 1: What are we building?
→
Step 2: Who are you?
Enter
opacity 0→1, translateX(34px)→0, .45s var(--ease)
Exit (back)
translateX(-34px), same duration
Focus
the new screen's first field gets focus after a 360ms delay, timed to land after the slide

Count-up stats

[data-count] · countUp()

The hero stat numbers (16 shipped, 10 live) count up from 0 once they scroll within 95% of the viewport, cubic ease-out over 1300ms via requestAnimationFrame. Reduced motion skips straight to the final number.

16shipped
10live now
1builder
Duration
1300ms · ease 1-(1-p)³ (cubic-out)
Trigger
first el.top < innerHeight*0.95, fires once

Easing & reduced motion

index.html :root · @media(prefers-reduced-motion)

--ease cubic-bezier(.22,1,.36,1), the only named easing token, used for reveals, hovers, the header, the stepper and the portrait clip-path

Interactions
.3–.5s (buttons, links, header, options)
Reveals
.8s scroll-triggered, once (.rv), 1.1s for the portrait clip-path
Ambient
2s loop, the status dot only
Reduced motion
global rule collapses every animation/transition duration to .001s and forces .rv{opacity:1;transform:none}; the feed-toggle button is also force-hidden since its script never runs
Components

Buttons

Two CTA weights (solid, ghost), one underline-link device reused for both nav and the mailto link, and a small pill control for the feed's Pause toggle. No third accent, no icon-only buttons.

Solid CTA

.btn

The primary action everywhere: "Start a project," "Send it." Accent fill, white text, lifts 2px and swaps to ink on hover.

Radius
10px
Padding
11px 20px
Fill
var(--blue) → hover var(--ink)
Shadow
0 10px 24px -12px rgba(203,77,20,.7), the legacy-orange drift noted in Colors
Hover
translateY(-2px), .3s var(--ease)

Ghost CTA

.btn.ghost

The quieter second action, "See the work." Transparent, hairline border, no shadow, no lift, only a color swap on hover.

Border
1.5px solid var(--line-3)
Hover
border → blue, text → blue, no shadow, no translate

Underline link

.nl · .contact .mail

Every plain in-line link, nav items and the mailto line, shares one device: an accent underline that scales in from the left on hover, never a color change alone.

Rule
2px, transform:scaleX(0)→1, transform-origin left, .35s var(--ease)
Mail variant
also nudges its arrow SVG +3px/-3px on hover

Pill toggle

.feed-toggle

The one toggle control on the page, Pause/Play for the rotating status feed. Small, quiet, sits visually on the glass card without being trapped inside its aria-hidden scope.

Pause
Shape
100px radius, min 24px tap target
Hover
border → blue, text → blue
Components

Forms

The only data-collecting surface is the four-step contact stepper. It has no backend and needs none, the final step composes a pre-filled mailto: and hands it to the visitor's own mail app, so there's no honeypot, no server validation, and no email address shipped as a plain link until the very last step.

Underline field

.s-field

Name and email steps, minimal underline input, no box, no visible native outline, the underline itself becomes the focus indicator.

Border
1.5px solid var(--line-3), no box
Native outline
removed on the input; :focus-within on the wrapper swaps the border to blue instead, the substitute indicator
Font size
16px (name/email), 19px in the live stepper to avoid iOS zoom-on-focus

Option buttons

.opt

Step 1's "what are we building" choice, four stacked buttons instead of a select, each with a trailing arrow that nudges right and brightens on hover.

Radius
13px
Hover
border → blue, bg → solid white, translateY(-2px)

Stepper chrome

.step-prog · .step-back · .step-sum .sc

Progress dots along the top fill in as you advance; a Back link appears only past step 1; answered steps collapse into clickable summary chips ("Building: A brand site") so you can jump back without losing later answers.

BuildingA brand site
Dot
22×3px, fills var(--blue) as steps complete
Chip
rounded-100px, clicking one jumps idx straight back to that step

Success state

.sent · sendIt handler

The whole step body becomes genuinely inert, not just visually hidden, before the success panel takes over, and the panel's copy is honest about uncertainty: the code cannot know a mail client actually opened, so it offers a plain-text fallback address if nothing takes focus within 1.2s.

✓
"Opening your mail app…" → fallback after 1.2s if unconfirmed
Isolation
stepBody.inert = true before reveal, so nothing underneath stays tabbable
Never claims
a message "was sent," only that the mail app was handed the draft

Accessibility: skip link + real inert

.skip-link · CLAUDE.md

A hidden skip-to-content link reveals on focus. The house rule behind it is written into this repo's own CLAUDE.md: opacity:0; pointer-events:none does not remove an element from the tab order, and that exact mistake once shipped a stepper that told a visitor their message was sent when nothing had happened. Every inactive step now carries the real HTML inert attribute instead.

Skip to main content
Behavior
top:-60px off-screen, :focus brings it to top:12px
Status
page passed axe-core with zero violations as of 2026-08-11
Components

Site Components

Every page-level building block, pulled straight from the live sections of index.html.

Header / nav

header#hdr

Fixed, transparent over the hero until scrolled (see Motion), brand lockup left, three mono nav links plus a solid CTA right. Collapses its links on narrow screens (nav .nl hidden below 600px), the CTA remains the one path to Contact.

Trigger
scrollY > 30
Mobile
<600px: nav links hidden, CTA stays

Hero status card

.status · .status-wrap

A glass panel presenting the studio as a live, working thing: an "Open for projects" pulse, a rotating "Currently…" line, then count-up stats. The card itself is decorative (aria-hidden); the Pause control lives outside it in the DOM, positioned to sit visually on its corner. See Motion for the feed and count-up timing.

Open for projects2026
Launching a public market site for UC San Diego
Surface
rgba(255,255,255,.62), blur(16px) saturate(1.3), border rgba(255,255,255,.8)
Shadow
0 26px 60px -34px rgba(20,29,53,.5)

Approach cards

.approach .cards

The "three rules" section, three numbered cards, each closing with a short tint bar drawn from a different wash color, the only place the four washes get named individually rather than used as background atmosphere.

02

Slow where it counts

Fast on the easy parts, careful on the ones that bite later.

Hover
translateY(-5px), shadow 0 24px 50px -34px rgba(20,29,53,.5), .4s var(--ease)
Tints in order
01 wash-blue · 02 wash-mint · 03 wash-peach

Work index row + cursor peek

.row · #peek

Ten client rows, each a number, a Fraunces name, a description, a tag and an "Open" arrow. On hover the row nudges 12-18px right, gains an accent underline, and the name turns accent-colored. On hover-capable devices a floating screenshot preview (#peek) follows the cursor with rAF lerp easing and a -2deg tilt; on touch or narrow screens the same screenshot renders inline above the row instead, no hover to fake.

02La Vida San DiegoOpen ↗
Row hover
padding-left 0→18px desktop / 6px touch, underline scaleX .5s
Peek
340px wide, rotate(-2deg), lerp 0.18 per frame, opacity/scale in on mouseenter

About portrait

#portrait · .p-frame · .p-cap

Layered scroll-depth photo (see Motion) with a dotted paper-3 back-panel offset behind the frame, and a small floating caption chip (name, role, studio) anchored to the bottom-right corner of the image.

Kazim K.
Frame radius
18px, clip-path reveal on scroll-in
Caption
Fraunces name 20px, mono role 9px uppercase blue

Brand mark: favicon

favicon.svg · CLAUDE.md 2026-08-19

The only system icon is the typographic favicon.svg, Kazim-approved in the 2026-08-19 brand triage alongside the ten live Work screenshots and assets/kazim.jpg. There is no separate app-icon composite, no square glyph anywhere in the tree, that family was purged for good.

K13
Source
root favicon.svg, typographic, referenced directly in <head>
Patterns

Recurring Devices

Small, repeated motifs that hold the editorial-but-technical register together across every section.

Eyebrow label

.eyebrow

Uppercase mono kicker with a 24px accent rule leading into it, ahead of nearly every section title.

How I work
Case
uppercase, .12em tracking
Rule
24×1.5px, same accent color, ::before

Italic accent word

h1 .ci · .h2 i · .big i

The single recurring emphasis device: one word per heading set in Fraunces italic 500 and the accent color, "feels obvious," "real products," "worth building." Never bold, never all-caps, for emphasis inside a heading.

Real products, on real domains.
Style
italic, weight 500 (vs. 600 upright), var(--blue)

Hairline section dividers

.approach / .work / .cap / .about / .contact { border-top }

Every major band after the hero opens with a single 1px --line rule instead of a background color change or a shadow, the page reads as one continuous sheet of paper, not stacked cards.

Approach
Work
Capabilities
Rule
border-top:1px solid var(--line)

Draft-grid & wash atmosphere

.draft-grid · .wash

A fixed, page-wide layer of faint dotted "plan paper" (30px grid, legacy-orange dots at 14% opacity, radially masked so it fades toward the bottom) sits under three large, heavily blurred wash blobs. Both layers are position:fixed, z-index:0, and never animate, the atmosphere is static texture, not motion.

Grid
30px dots, rgba(203,77,20,.14), radial mask 120vw 90vh at 70% -10%
Blobs
blur(60px), opacity .5, three fixed positions (lilac top-left, blue-wash right, mint mid-page)

Numbered device

.card .num · .row .rnum

Mono two-digit numbers (01, 02…) label both the Approach cards and the Work index rows, the site's one recurring "ordered list" motif, always mono, always accent or faint, never a bullet or an icon.

03
Approach
11px, blue, .1em tracking
Work index
13px, faint

Single-accent discipline, no left accent bars

house canon, K13 brand rules (2026-08-13)

One hue carries every interactive and emphasis surface on this page, links, CTAs, eyebrows, the wordmark's "13," hovers, focus states. Success green is the sole sanctioned exception, reserved for confirmation only (the status dot, the sent-form ring). This also follows a K13-wide rule from the studio's own brand canon: never a colored strip down a card's left edge, it reads as generic template AI work. Where this site wants a card to carry color, it tints the whole tag (approach card's tint bar) or the whole page (the wash blobs), never a left border.

Rule
one accent hue for every interactive state; color as a full tint or wash, never a left accent bar
Reference

Tokens & Specs

The numbers behind the look, plus the operational rules that govern this repo specifically.

Core tokens

index.html, inline <style>
radius 810111213161820100 (pill)
bp 600bp 880bp 920
Radii in use
8 skip-link · 10 buttons/portrait cap · 11 row thumbnail · 12 peek frame · 13 stepper options · 16 cards · 18 status card/portrait frame · 20 stepper/portrait back-panel · 100 pills (tags, feed toggle, "send it" button)
Breakpoints
600px (nav links hide, hero-meta gap tightens) · 880px (contact grid stacks) · 920px (hero, cards, capabilities, about, work rows all stack)
Shadow family
large-blur, negative-spread, ink-tinted: card-hover 0 24px 50px -34px, portrait frame 0 36px 80px -44px, status/stepper glass 0 26-30px 60-70px -34/-38px, all rgba(20,29,53, .4 to .65); the one outlier is the primary button's rgba(203,77,20,.7), legacy-orange, see Colors
Container
.wrap max-width 1200px, hero-grid 1.55fr/1fr, contact-grid 1fr / minmax(380px,460px)
Safe areas
no explicit env(safe-area-inset) usage found; header padding is fixed clamp values, not safe-area aware
Focus
no custom global focus-ring override; native browser outline holds everywhere except the stepper's underline inputs, which remove outline:none and rely on :focus-within border-color instead, and the skip-link's own translate-into-view

Governance & legal posture

CLAUDE.md · vercel.json

This repo carries its own operating rules on top of the visual system, worth knowing before touching it.

"Clear software, built end to end."
Voice
first person (Kazim, not "the studio"), plain language, no hype, honest about uncertainty ("your mail app opened," not "message sent")
Copy rule
no em dash anywhere in visitor-facing copy, including OG/Twitter meta; commas, colons and periods only
Security
CSP + 5 headers in vercel.json, allowlist derived from captured network requests (fonts.googleapis.com, fonts.gstatic.com, cdn.jsdelivr.net); script-src 'unsafe-inline' is a known, tracked exception since the JS is inline with no build step
Accessibility
marketed as a paid capability ("WCAG · 508"), so a11y failures are a credibility problem, not just a defect; zero axe-core violations as of 2026-08-11
Brand governance
the typographic wordmark is the only canon mark (2026-08-18); the square glyph mark is destroyed and must never be regenerated
No build step
one index.html is the whole application; edits deserve the same care as a source tree, and concurrent agents should partition by section