dzarlax.dev Design System

Unified visual language for all personal projects. CSS-first, zero dependencies.

Colors

Warm ivory backgrounds with dark graphite accents. Swatches use CSS custom properties and update live when you toggle the theme.

Core

--bg
--surface
--surface-2
--surface-3
--accent
--accent-hover
--accent-foreground

Text

--text
--text-secondary
--text-tertiary

Borders

--border
--border-hover
--border-light

Status

--good
--good-bg
--warn
--warn-bg
--danger
--danger-bg

Category (dashboards)

--heart
--activity
--sleep
--cardio

Brand

--brand-dark
--brand-light

Typography

Georgia serif for headings, system fonts for body. Monospace for code.

Type scale

--text-2xl Display heading
--text-xl Section heading
--text-lg Subsection heading
--text-base Body text — the default for paragraphs and UI elements.
--text-sm Small text for descriptions and secondary content.
--text-xs Extra small for labels, timestamps, and metadata.

Font families

--font System font stack — SF Pro, system-ui, sans-serif.
--font-serif Georgia, Times New Roman — used for headings and brand.
--font-mono SF Mono, Fira Code — code blocks and tokens.

Semantic headings

Heading 1

Heading 2

Heading 3

Text utilities

Default paragraph text with normal color.

Secondary text — .text-secondary

Tertiary text — .text-tertiary

Serif text — .text-serif

Label text — .label

Code

Inline code uses the monospace stack.

// Pre-formatted code block
const theme = document.documentElement.hasAttribute('dark-mode');
console.log('Dark mode:', theme);

Tokens

Design tokens for spacing, radius, shadows, and layout.

Border radius

--radius-xs (2px)
--radius-sm (4px)
--radius (8px)
--radius-lg (12px)
--radius-full

Shadows

--shadow-sm
--shadow
--shadow-lg
--shadow-xl

Container widths

--container-sm 720px
--container-md 960px
--container-lg 1200px
--container-xl 1400px

Transition

--transition 0.18s ease
--navbar-height 56px

Buttons

Flat, borderless buttons with graphite accent. Five variants, three sizes.

Variants

Sizes

Hover (forced)

:hover
:hover
:hover
:hover
:hover

Disabled

Icon buttons

primary
secondary
ghost
danger

Block & link

Cards

Surface containers with subtle border and shadow. Supports hover lift, footer, and multiple grid layouts.

3-column grid with hover

Meeting Type
30-minute consultation session with calendar integration.
30 min Google Meet
Health Score
Daily readiness score based on sleep, HRV, and activity data.
Updated daily
News Digest
AI-summarized evening news from curated RSS feeds.
12 sources Auto

Card with footer

Project Setup
Configure your project with the design system tokens and components.

2-column grid

Left column
Cards without .card--hover have no lift effect.
Right column
Static cards for content display without interactivity.

Auto-fill grid

Auto card 1
Fills columns at 280px minimum width.
Auto card 2
Responsive grid without fixed column count.
Auto card 3
Adapts to available width automatically.
Auto card 4
Try resizing the browser window.

Badges

Status indicators with semantic colors. Pill-shaped, uppercase, small text.

Variants

Active .badge--success
Pending .badge--warning
Error .badge--danger
Draft .badge--neutral

In context

Service status: Running

Build #142: In progress

Deployment: Failed

Feature branch: Unreviewed

Forms

Clean inputs with uppercase labels. Supports hints, errors, and disabled states.

Default

Optional — helps prepare for the meeting.

Focus state

Error state

Please enter a valid email address.
Password must be at least 8 characters.

Disabled state

With hint

Find this in your account settings under Developer → API Keys.

Combobox

Searchable select with keyboard navigation. Requires dzarlax.js. Auto-initialized via data-ds-combobox.

Basic

  • Food & Groceries
  • Transport
  • Housing
  • Health
  • Entertainment
  • Shopping
  • Travel
  • Education
  • Utilities
  • Other
Type to search · ↑↓ navigate · Enter select · Esc close

Pre-selected value

  • Food & Groceries
  • Transport
  • Housing
  • Health
  • Entertainment

JS API

// Auto-init all [data-ds-combobox] on the page
DS.init()

// Init a single element (e.g. dynamically added)
DS.Combobox(document.querySelector('#my-select'))

// Listen to selection changes
el.addEventListener('ds:change', e => {
  console.log(e.detail.value, e.detail.label)
})

Tables

Clean tables with hover rows. Wrap in .table-wrap for horizontal scroll on mobile.

Service Status Uptime Last deploy
Personal Assistant Running 99.9% 2 hours ago
Health Dashboard Running 99.8% Yesterday
Evening News Updating 98.5% Just now
Finance Tracker Down 95.2% 3 days ago
City Dashboard Draft

Toggles

Switch inputs for boolean settings. Uses native checkbox with custom appearance.

States

unchecked
checked
disabled off
disabled on

Theme toggle

Circular button used in the navbar for dark mode switching.

light mode
:hover
dark mode

Spinner

Loading indicators in two sizes. Integrates with HTMX via .htmx-indicator.

Sizes

default (16px)
large (32px)

With text

Loading data...

HTMX integration

<!-- Spinner hidden until HTMX request starts --> <button hx-get="/api/data" hx-target="#result"> Load <span class="spinner htmx-indicator"></span> </button>

Brand

Logo variants for light and dark backgrounds. Icon + wordmark + wordmark.dev.

Icon (light bg)
Icon (dark bg)
Wordmark (light bg)
Wordmark (dark bg)
Wordmark .dev (light bg)
Wordmark .dev (dark bg)

Usage

How to integrate the design system into your projects.

Quick start (single file)

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/dzarlax/design-system@v1.0.0/dist/dzarlax.css">

Docker (bake into image)

ADD https://github.com/dzarlax/design-system/releases/download/v1.0.0/dzarlax.css /app/static/dzarlax.css

Go (embed)

import _ "embed" //go:embed static/dzarlax.css var designSystemCSS string

Dark mode

<!-- Static: add attribute to html element --> <html dark-mode> // Toggle with JS — always set an explicit attribute so a // system-dark browser can't override the manual choice via // the prefers-color-scheme media query. var html = document.documentElement; var next = html.hasAttribute('dark-mode') ? 'light' : 'dark'; if (next === 'dark') { html.setAttribute('dark-mode', ''); html.removeAttribute('light-mode'); } else { html.removeAttribute('dark-mode'); html.setAttribute('light-mode', ''); } localStorage.setItem('theme', next);

Modular (pick what you need)

<link rel="stylesheet" href="tokens/colors.css"> <link rel="stylesheet" href="tokens/typography.css"> <link rel="stylesheet" href="tokens/spacing.css"> <link rel="stylesheet" href="themes/dark.css"> <link rel="stylesheet" href="base/reset.css"> <link rel="stylesheet" href="components/buttons.css"> ...

Hero

Full-screen intro section with avatar, badge, title, subtitle and action buttons. Classes: .hero, .hero__profile, .hero__avatar, .hero__badge, .hero__title, .hero__subtitle, .hero__description, .hero__actions.

🧑‍💻
💼

Alexey Panfilov

Product Manager · AI Enthusiast

Building AI-native products with 15 years of experience at Yandex, EdTech startups, and beyond.

Timeline

Center-column timeline for experience and history. Alternates items left/right. Collapses to left-aligned list on mobile. Modifier .timeline--list for a simple vertical list variant.

Center timeline

  • 2022 – present

    Head of Product

    Constructor.tech

    Leading product development for e-commerce search and discovery platform.

  • 2020 – 2022

    Senior Product Manager

    Yandex Cloud

    Managed data warehouse and analytics products serving millions of users.

  • 2018 – 2020

    Product Manager

    EdTech Startup

    Built AI/ML platform for university education, improving learning outcomes.

List variant (.timeline--list)

  • 2018

    MSc Computer Science

    Moscow State University

  • 2014

    BSc Applied Mathematics

    HSE University

Cards — extended

New card variants: .card--project for project showcases (tags + link), .card--icon for education/achievement cards (icon + body).

Project cards (.card.card--project)

Health Dashboard

Personal health metrics aggregator with Go backend, ClickHouse, and real-time charts.

Go ClickHouse Docker
View project →

Design System

CSS-first design system used across all dzarlax.dev projects. Zero dependencies.

CSS GitHub Pages
View project →

Evening News

RSS aggregator with AI summaries, delivered as a personal daily digest.

Python LLM FastAPI
View project →

Icon cards (.card.card--icon)

🎓

MSc Computer Science

Moscow State University, 2018

🏢

Yandex Alumni

Data Infrastructure & Cloud Products

Icon card with soft icon (.card__icon--soft)

🚀

15+ Years Experience

Across product management, engineering, and AI product development.

🌍

Global Reach

Products used by millions of users in 30+ countries.

Editorial variants

Modifiers tuned for marketing pages, portfolios, and blogs. Available on existing components: .card--editorial, .hero--editorial, .navbar--pill, .footer--editorial. New helpers: .card__eyebrow, .card--link, .hero__lede, .hero__meta, .hero__eyebrow, .navbar-controls.

Editorial hero (.hero.hero--editorial)

Drops the min-height: 100vh from base .hero. Adds .hero__eyebrow (topic crumbs), .hero__lede (article subtitle), .hero__meta (date / reading time). Title uses a serif clamp scale.

Product & AI · Claude Code

How a sleeping terminal cost me $1,000 overnight

A Claude Code post-mortem on background daemons, prompt caching, and infinite agent loops — and how a strict spending limit failed to save my bill.

· 3 min read

Pill navbar (.navbar.navbar--pill)

Floating glassy pill at the top of the viewport — for marketing / blog pages where the navbar is decorative. Goes alongside .navbar-controls on the right side for theme toggle / language switcher. Add .active to the current page's link. At ≤ 768px it collapses into a slide-out drawer — pair with a .hamburger button carrying data-ds-nav-toggle="#yourNavId" (auto-wired by js/nav-drawer.js).

Mobile drawer (≤ 768px)

When the viewport is ≤ 768px, the pill collapses into a 280px-wide drawer triggered by .active. Below is a fixed-size replica forced into the open state via inline CSS so you can see it at any viewport. Real usage: include js/nav-drawer.js in the bundle (auto-init) and add data-ds-nav-toggle="#mainNav" to your hamburger button.

Editorial footer (.footer.footer--editorial)

Dark background, two-column grid: .footer__main (copyright) + .footer__social (Connect heading + .social-icons). .footer__bottom row at the end. Mobile collapses to single column.

Tag chip

Standalone clickable pill — for article footers, blog tag rows, tag clouds. Class: .tag-chip. Cloud wrapper: .tag-cloud + size modifier .tag-chip--cloud (font size scales with inline --tag-weight 0..1).

Prose

Typography utility for long-form content. Apply .prose to a wrapper around article markup; descendants get reading-comfortable defaults — serif h2/h3, 68ch max column, inline code, code blocks, blockquote, lists, links. Companion table-of-contents card: .prose-toc + .prose-toc__title.

Article body (.prose)

Picture this: you're working on a project, using Claude Code to refactor a massive monorepo. Evening comes, you close your laptop lid, and in the morning you wake up to a $1,025 API bill — even though you had strict spending limits set up in your billing dashboard.

The setup

Every request to Claude pulled in roughly 250,000 tokens of context. As a wrapper, I was using AgentDeck — a handy tool that creates its own PTY terminal managed by a background daemon. That daemon is where this story begins.

What the logs showed

  • input_tokens: 1
  • cache_read_input_tokens: 242,304
  • durationMs: 929,873 (~15.5 minutes)
A 90% discount on 250,000 tokens still adds up fast when something is hitting the model on a loop.

Code blocks render with monospace inside a surface-2 background:

function setupSmoothScrolling() {
    const navLinks = document.querySelectorAll('nav a');
    navLinks.forEach(link => {
        link.addEventListener('click', (e) => {
            const targetId = link.getAttribute('href');
            if (!targetId || !targetId.startsWith('#')) return;
            e.preventDefault();
        });
    });
}

Links inside prose are underlined with a subtle thickness — like this one — staying readable without screaming "I am a link".

Empty State

Centered confirmation and empty-state layout. Classes: .empty-state, .empty-state__icon + modifiers --success --error --warning --info, .empty-state__title, .empty-state__text. Compact variant: .empty-state--sm.

Icon variants

success
error
!
warning
i
info
📭
default

Full confirmation

Booking confirmed!

You'll receive a calendar invite and reminder email shortly.

Back to home

Empty list state (.empty-state--sm)

📭

No bookings yet

When someone books a meeting it will appear here.

Utilities — Scroll

Scroll progress bar and scroll-to-top button. JS sets style.width on .scroll-progress and toggles .visible on .scroll-top.

Scroll progress bar

In production: position:fixed; top:0; left:0; z-index:9999

Scroll-to-top button

visible

Utilities — Skeleton loader

Shimmer placeholder for async content. Add .skeleton + a size modifier. Respects prefers-reduced-motion.

In a card

Utilities — Animations

.reveal — fade in on scroll (JS adds .visible). .stagger on container — staggers children with --i CSS variable delay. Both respect prefers-reduced-motion.

Reveal

<!-- HTML --> <section class="reveal">...</section> <!-- JS (Intersection Observer) --> const observer = new IntersectionObserver(entries => { entries.forEach(e => e.target.classList.toggle('visible', e.isIntersecting)); }); document.querySelectorAll('.reveal').forEach(el => observer.observe(el));

Stagger children

<!-- CSS var --i sets per-child delay --> <ul class="stagger"> <li style="--i:0">Item 1</li> <li style="--i:1">Item 2</li> <li style="--i:2">Item 3</li> </ul>

Utilities — Language switcher

Simple button group for internationalization. Classes: .lang-switcher, .lang-btn, .lang-btn.active.

Flag buttons

Text labels

Popover

This is a popover. Use position:absolute and JS to position dynamically relative to the trigger element.

Alerts

Inline status messages. Classes: .alert + modifiers --success --warning --danger --info. Optional elements: .alert__icon, .alert__text, .alert__title.

All variants

Import completed — 1 420 records added successfully.
No data for 3 days
Connect your device to sync recent activity.
Connection failed — check your API key and try again.
i Health data is updated every 6 hours via background sync.

Stat chip

Compact label + big-number display for inline metrics. Classes: .stat-chip, .stat-chip__label, .stat-chip__value, .stat-chip__note. Group with .stat-chips.

Chip row

Avg heart rate
68
bpm · last 7 days
Sleep score
82
pts · yesterday
Active minutes
47
min · today
Readiness
91
pts · today

KPI bar

Horizontal row of key metrics with vertical dividers. Classes: .kpi-bar, .kpi-item, .kpi-item__label, .kpi-item__value, .kpi-item__note. Trend dot: .kpi-indicator + --up --down --stable.

Dashboard overview

Total sources
24
3 inactive
Articles today
138
+12 vs yesterday
Avg summary time
4.2s
was 3.8s
Uptime
99.8%
last 30 days

Divider

Horizontal separator with a plain variant (.divider) and a labeled variant (.divider--labeled + .divider__label) that splits a line with a centered pill. Good for session breaks in chat logs or phase markers in timelines.

Plain

Content above the rule.


Content below.

Labeled (session break)

Last message of session 1.

2026-04-21 09:34 · Cleared

First message of session 2.