<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged fscss</title>
<link>https://coderlegion.com/tag/fscss</link>
<description>Powered by Question2Answer</description>
<item>
<title>Build an AI Ops Dashboard with FSCSS modules (ai-ui, st-core, circle-progress, overlapui)</title>
<link>https://coderlegion.com/32863/build-an-ai-ops-dashboard-with-fscss-modules-ai-ui-st-core-circle-progress-overlapui</link>
<description>&lt;blockquote&gt;&lt;p&gt;A step by step tutorial for building a responsive AI dashboard with compiled FSCSS, pure CSS charts, progress rings, stacked avatars, and plain JavaScript. No framework, no build tool beyond the FSCSS CLI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you have ever built an AI product UI, you know the pattern. A sidebar, a chat column, a metrics panel, a streaming caret, some usage cards. It is a lot of CSS before you write a single line of product logic.&lt;/p&gt;
&lt;p&gt;In this tutorial we build an &lt;strong&gt;AI Ops Console&lt;/strong&gt; using four FSCSS modules, compile everything to one plain CSS file, and wire it up with a small vanilla JavaScript layer. No frameworks. No runtime preprocessor in the browser.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codepen.io/editor/David-Hux/pen/01a181f1-e94a-7029-9f5f-894ea531b2fc&quot;&gt;CodePen preview&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-we-are-building&quot;&gt;What we are building&lt;/h3&gt;
&lt;p&gt;An AI Ops Console with a desktop layout (sidebar, chat, metrics panel) and a mobile layout (hamburger sidebar drawer, bottom sheet metrics panel at 960px and below).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Surface &lt;/th&gt;&lt;th&gt;Module&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;App shell, sidebar, messages, prompt, stream caret, thinking dots, usage cards, workflow nodes, tool chips, citations &lt;/td&gt;&lt;td&gt;&lt;strong&gt;ai-ui.fscss&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Token burn area and line chart (clip-path, no SVG or canvas) &lt;/td&gt;&lt;td&gt;&lt;strong&gt;st-core@v2&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPU, Queue and Quota rings &lt;/td&gt;&lt;td&gt;&lt;strong&gt;circle-progress&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Team avatar stack &lt;/td&gt;&lt;td&gt;&lt;strong&gt;overlapui&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mock agent replies, live chart nudges, mobile drawers &lt;/td&gt;&lt;td&gt;&lt;strong&gt;dashboard.js&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The project is split into four files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;ai-dashboard/
├── dashboard.html      # markup only
├── dashboard.fscss     # all FSCSS + layout
├── dashboard.css       # compiled output (generated)
└── dashboard.js        # chat, drawers, chart updates
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You compile once and ship plain CSS.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 18 or newer (for the FSCSS CLI)&lt;/li&gt;
&lt;li&gt;A modern browser&lt;/li&gt;
&lt;li&gt;Optional: VS Code with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS Support&lt;/a&gt; extension&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install the compiler globally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g fscss@1.2.6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need &lt;strong&gt;FSCSS 1.2.6 or newer&lt;/strong&gt;. ai-ui requires it, and st-core needs 1.2.3 or newer.&lt;/p&gt;
&lt;h3 id=&quot;1-project-setup&quot;&gt;1. Project setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir ai-dashboard &amp;amp;&amp;amp; cd ai-dashboard
touch dashboard.html dashboard.fscss dashboard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dashboard.css&lt;/code&gt; will be generated by the CLI in step 3.&lt;/p&gt;
&lt;h3 id=&quot;2-dashboard-fscss-the-styles-source&quot;&gt;2. dashboard.fscss: the styles source&lt;/h3&gt;
&lt;p&gt;This file imports the four modules, calls their helpers against your own class names, defines the chart series, and holds all layout and responsive rules.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* dashboard.fscss
   AI Ops Dashboard: FSCSS source
   Compile:  fscss dashboard.fscss dashboard.css
*/

/* Modules (registry names; CLI + runtime resolve these) */
@import((*) from ai-ui)
@import((*) from st-core@v2)
@import((*) from circle-progress)
@import((*) from overlapui)

/* Theme + design tokens */
@ai-compose-dashboard()   /* ai-ui tokens + dashboard theme */
@st-root()                /* st-core chart tokens */
@progress-root()          /* circle-progress tokens */
@ou-root()                /* overlapui tokens */

/* ai-ui structure helpers */
@ai-shell(.ai-shell)
@ai-sidebar(.ai-sidebar)
@ai-main(.ai-main)
@ai-messages(.ai-messages)
@ai-message(.ai-message)
@ai-message-user(.ai-message-user)
@ai-message-assistant(.ai-message-assistant)
@ai-bubble(.ai-bubble)
@ai-avatar(.ai-avatar)
@ai-prompt(.ai-prompt)
@ai-prompt-box(.ai-prompt-box)
@ai-prompt-send(.ai-prompt-send)
@ai-stream(.ai-stream)
@ai-thinking(.ai-thinking)
@ai-usage(.ai-usage)
@ai-stat(.ai-stat)
@ai-workflow(.ai-workflow)
@ai-node(.ai-node)
@ai-edge(.ai-edge)
@ai-tools(.ai-tools)
@ai-tool-chip(.ai-tool-chip)
@ai-card(.ai-card)
@ai-divider(.ai-divider)
@ai-citations(.ai-citations)
@ai-citation(.ai-citation)

/* st-core linear chart */
@arr tokenSeries[32, 48, 55, 42, 68, 75, 61, 88, 72, 91, 84, 96]

@st-chart-fill(.chart-fill, tokenSeries)
@st-chart-line(.chart-line, tokenSeries)

.token-chart {
  @st-chart-points(tokenSeries)
  position: relative;
  height: 160px;
  width: 100%;
  background: var(--st-surface, #161422);
  border-radius: 12px;
  overflow: hidden;
}

/* circle-progress */
@circle-progress(.cp)
.cp-72 { @progress-range(72) }
.cp-48 { @progress-range(48) }
.cp-91 { @progress-range(91) }

.cp {
  --progress-size: 110px;
  --progress-stroke: 10px;
  --progress-color-bg: transparent;
  --progress-color-track: rgba(255, 255, 255, 0.08);
  --progress-color-arc: var(--ai-accent, #e879f9);
  --progress-color-glow: color-mix(in srgb, var(--ai-accent, #e879f9) 40%, transparent);
  --progress-color-value: var(--ai-text, #f5f3ff);
  --progress-color-label: var(--ai-text-muted, #a78bfa);
  font-size: 0.95rem;
  font-weight: 600;
}

/* overlapui avatars */
@avatar-overlap(.team-faces)
.team-faces {
  --ou-avatar-size: 36px;
  --ou-overlap: -12px;
  margin-block: 8px;
}

/* Base layout */
* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  font-family: var(--ai-font, system-ui, -apple-system, sans-serif);
  background: var(--ai-bg, #0c0a12);
  color: var(--ai-text, #f5f3ff);
  -webkit-text-size-adjust: 100%;
}

.ai-shell {
  min-height: 100vh;
  min-height: 100dvh;
  height: 100vh;
  height: 100dvh;
  display: grid;
  grid-template-columns: var(--ai-sidebar-w, 240px) 1fr;
}

.ai-sidebar {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px 16px;
  overflow-y: auto;
  border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08));
  background: var(--ai-bg, #0c0a12);
  z-index: 40;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: -0.02em;
}
.brand-mark {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--ai-accent, #e879f9), #818cf8);
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 800;
  color: #0c0a12;
  flex-shrink: 0;
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 8px;
  color: var(--ai-text-muted, #a78bfa);
  text-decoration: none;
  font-size: 0.875rem;
  transition: background 0.15s, color 0.15s;
}
.nav a:hover,
.nav a.active {
  background: color-mix(in srgb, var(--ai-accent, #e879f9) 12%, transparent);
  color: var(--ai-text, #f5f3ff);
}

.side-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.side-section h3 {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ai-text-muted, #a78bfa);
  font-weight: 600;
}

.cp-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.cp-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-size: 0.7rem;
  color: var(--ai-text-muted, #a78bfa);
}

.ai-main {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08));
  gap: 10px;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.topbar h1 {
  font-size: 1rem;
  font-weight: 600;
}
.topbar-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
.chip {
  font-size: 0.75rem;
  padding: 4px 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ai-accent, #e879f9) 15%, transparent);
  color: var(--ai-accent, #e879f9);
  border: 1px solid color-mix(in srgb, var(--ai-accent, #e879f9) 30%, transparent);
  white-space: nowrap;
}

.workspace {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr minmax(260px, 320px);
  min-height: 0;
  overflow: hidden;
}

.chat-col {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.06));
}

.ai-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  -webkit-overflow-scrolling: touch;
}

.ai-prompt {
  padding: 12px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--ai-border, rgba(255, 255, 255, 0.06));
  flex-shrink: 0;
}

.right-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 16px;
  overflow-y: auto;
  background: color-mix(in srgb, var(--ai-surface, #12101a) 80%, transparent);
  -webkit-overflow-scrolling: touch;
}

.panel-card {
  background: var(--ai-card, #1a1625);
  border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08));
  border-radius: 12px;
  padding: 14px;
}
.panel-card h3 {
  font-size: 0.8rem;
  font-weight: 600;
  margin-bottom: 10px;
  color: var(--ai-text-muted, #a78bfa);
}

.ai-workflow {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 0.75rem;
}
.ai-node {
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--ai-surface, #161422);
  border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.1));
}
.ai-node.is-active {
  border-color: var(--ai-accent, #e879f9);
  color: var(--ai-accent, #e879f9);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ai-accent, #e879f9) 40%, transparent);
}
.ai-edge {
  width: 16px;
  height: 2px;
  background: var(--ai-border, rgba(255, 255, 255, 0.2));
}

.ai-usage {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.ai-stat {
  background: var(--ai-surface, #161422);
  border-radius: 10px;
  padding: 10px 12px;
}
.ai-stat-label {
  display: block;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ai-text-muted, #a78bfa);
  margin-bottom: 4px;
}
.ai-stat-value {
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.ai-stat-delta {
  font-size: 0.7rem;
  margin-left: 6px;
}
.ai-stat-delta.up { color: #4fffb0; }
.ai-stat-delta.down { color: #ff5e7d; }

.ai-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.ai-tool-chip {
  font-size: 0.7rem;
  padding: 4px 8px;
  border-radius: 6px;
  background: var(--ai-surface, #161422);
  border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.1));
}
.ai-tool-chip.is-done { border-color: #4fffb0; color: #4fffb0; }
.ai-tool-chip.is-running { border-color: var(--ai-accent, #e879f9); color: var(--ai-accent, #e879f9); }

.muted {
  color: var(--ai-text-muted, #a78bfa);
  font-size: 0.8rem;
}

/* Mobile controls (hidden on desktop) */
.menu-btn,
.panel-btn,
.sidebar-backdrop {
  display: none;
}

/* TABLET &amp;lt;= 960px */
@media (max-width: 960px) {
  .ai-shell {
    grid-template-columns: 1fr;
  }

  .ai-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(280px, 86vw);
    transform: translateX(-105%);
    transition: transform 0.25s ease;
    box-shadow: none;
    border-right: 1px solid var(--ai-border, rgba(255, 255, 255, 0.08));
  }
  body.sidebar-open .ai-sidebar {
    transform: translateX(0);
    box-shadow: 8px 0 32px rgba(0, 0, 0, 0.45);
  }

  .sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 35;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }
  body.sidebar-open .sidebar-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  .menu-btn,
  .panel-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 8px;
    border: 1px solid var(--ai-border, rgba(255, 255, 255, 0.12));
    background: var(--ai-surface, #161422);
    color: var(--ai-text, #f5f3ff);
    cursor: pointer;
    font-size: 1.1rem;
    flex-shrink: 0;
    line-height: 1;
  }

  .topbar {
    padding: 10px 12px;
    gap: 8px;
  }
  .topbar h1 {
    font-size: 0.95rem;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .topbar-actions { gap: 6px; }
  .chip {
    font-size: 0.7rem;
    padding: 3px 8px;
  }
  #model-chip { display: none; }

  .workspace {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    position: relative;
  }
  .chat-col {
    border-right: none;
    min-height: 0;
  }

  .right-panel {
    position: fixed;
    inset: auto 0 0 0;
    max-height: min(72vh, 560px);
    border-radius: 16px 16px 0 0;
    border-top: 1px solid var(--ai-border, rgba(255, 255, 255, 0.1));
    transform: translateY(110%);
    transition: transform 0.28s ease;
    z-index: 30;
    box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.4);
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  body.panel-open .right-panel {
    transform: translateY(0);
  }
  .right-panel::before {
    content: &quot;&quot;;
    display: block;
    width: 36px;
    height: 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.2);
    margin: 0 auto 10px;
  }

  .token-chart { height: 140px; }
  .cp {
    --progress-size: 88px;
    --progress-stroke: 8px;
    font-size: 0.85rem;
  }
}

/* PHONE &amp;lt;= 600px */
@media (max-width: 600px) {
  .ai-messages {
    padding: 12px;
    gap: 12px;
  }
  .ai-prompt {
    padding: 10px 12px calc(12px + env(safe-area-inset-bottom, 0px));
  }
  .ai-usage {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }
  .ai-stat { padding: 8px 10px; }
  .ai-stat-value { font-size: 1rem; }

  .cp-row {
    justify-content: center;
    gap: 12px;
  }
  .cp {
    --progress-size: 76px;
    --progress-stroke: 7px;
    font-size: 0.8rem;
  }

  .panel-card { padding: 12px; }
  .ai-workflow { gap: 4px; }
  .ai-node {
    padding: 5px 8px;
    font-size: 0.7rem;
  }
  .ai-edge { width: 10px; }

  .team-faces {
    --ou-avatar-size: 32px;
    --ou-overlap: -10px;
  }
  .ai-bubble { font-size: 0.9rem; }
}

/* VERY NARROW &amp;lt;= 380px */
@media (max-width: 380px) {
  .ai-usage { grid-template-columns: 1fr; }
  .chip#btn-refresh {
    padding: 3px 6px;
    font-size: 0.65rem;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-structure-works&quot;&gt;Why this structure works&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Import&lt;/strong&gt; only registers &lt;code&gt;@define&lt;/code&gt; helpers. Nothing paints until you call them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compose and root helpers&lt;/strong&gt; write design tokens on &lt;code&gt;:root&lt;/code&gt; (or a scope).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selector-parameterized helpers&lt;/strong&gt; such as &lt;code&gt;@ai-shell(.ai-shell)&lt;/code&gt; and &lt;code&gt;@st-chart-fill(.chart-fill, tokenSeries)&lt;/code&gt; attach rules to your own class names.&lt;/li&gt;
&lt;li&gt;Plain CSS after that is normal layout, overrides and media queries.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Can also use remote URLs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from &quot;https://cdn.jsdelivr.net/gh/fscss-ttr/ai-ui.fscss@main/ai-ui.fscss&quot;)
@import((*) from &quot;https://cdn.jsdelivr.net/gh/fscss-ttr/st-core.fscss@main/st-core%40v2.fscss&quot;)
@import((*) from &quot;https://cdn.jsdelivr.net/gh/fscss-ttr/FSCSS@main/xf/styles/circle-progress.fscss&quot;)
@import((*) from &quot;https://cdn.jsdelivr.net/gh/fscss-ttr/FSCSS@main/xf/styles/overlapui.fscss&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-compile-fscss-to-css&quot;&gt;3. Compile FSCSS to CSS&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# one-shot
fscss dashboard.fscss dashboard.css

# watch (recompile on save), if your fscss build supports it
# fscss dashboard.fscss dashboard.css --watch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expands every &lt;code&gt;@import&lt;/code&gt;, &lt;code&gt;@define&lt;/code&gt; and &lt;code&gt;@arr&lt;/code&gt; into standard CSS&lt;/li&gt;
&lt;li&gt;Emits custom properties (&lt;code&gt;--st-p1&lt;/code&gt; to &lt;code&gt;--st-p12&lt;/code&gt;, &lt;code&gt;--ai-*&lt;/code&gt;, &lt;code&gt;--progress-*&lt;/code&gt; and so on)&lt;/li&gt;
&lt;li&gt;Emits &lt;code&gt;clip-path: polygon(...)&lt;/code&gt; for the chart fill and line&lt;/li&gt;
&lt;li&gt;Leaves your media queries and plain CSS intact&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ship only &lt;code&gt;dashboard.css&lt;/code&gt; to production. No &lt;code&gt;runtime.min.js&lt;/code&gt; is needed on this path.&lt;/p&gt;
&lt;h3 id=&quot;4-dashboard-html-the-markup&quot;&gt;4. dashboard.html: the markup&lt;/h3&gt;
&lt;p&gt;Link the compiled stylesheet. There is no FSCSS runtime script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See complete tutorial here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/fscss/build-an-ai-ops-dashboard-with-fscss-ai-ui-st-core-circle-progress-overlapui-59m5&quot;&gt;https://dev.to/fscss/build-an-ai-ops-dashboard-with-fscss-ai-ui-st-core-circle-progress-overlapui-59m5&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/32863/build-an-ai-ops-dashboard-with-fscss-modules-ai-ui-st-core-circle-progress-overlapui</guid>
<pubDate>Wed, 28 Oct 2026 15:40:56 +0000</pubDate>
</item>
<item>
<title>devtemDesign: one config object paints a whole landing page</title>
<link>https://coderlegion.com/32379/devtemdesign-one-config-object-paints-a-whole-landing-page</link>
<description>&lt;blockquote&gt;&lt;p&gt;A zero-dependency page engine in plain HTML, CSS and JS. 7 templates, live theming, pricing, FAQ, stats and a mobile drawer from a single config. Full reference included&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every landing page I built started the same way: nav, hero, feature cards, pricing, FAQ, footer, then a day of making it responsive. So I wrote an engine that does that part for me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;devtemDesign&lt;/strong&gt; takes one config object and renders the whole page. No framework, no build step, no dependencies. Two tags and one function call.&lt;/p&gt;
&lt;p&gt;This post is both an introduction and a reference for &lt;strong&gt;v0.1.0&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;try-it-in-30-seconds&quot;&gt;Try it in 30 seconds&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.jsdelivr.net/npm/devtemdesign@0.1.0/style.min.css&quot;&amp;gt;
&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/devtemdesign@0.1.0/script.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;div id=&quot;app-root&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
  devtemDesign(&quot;app-root&quot;, {
    template: &quot;cyber&quot;,
    logoName: &quot;MYAPP&quot;,
    heroTitleLine1: &quot;Declarative UI,&quot;,
    heroTitleLine2: &quot;Zero-Config Motion&quot;,
    heroSubtitle: &quot;One config object. Complete animated landing.&quot;,
    primaryBtnText: &quot;Get started&quot;,
    primaryBtnHref: &quot;/docs&quot;
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That gives you a sticky glass navbar, an animated hero, ambient background orbs, reveal-on-scroll, a mobile drawer and a footer. Every section except the hero is optional, and a section only renders when its config key is present.&lt;/p&gt;
&lt;p&gt;If you want to skip the code entirely, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/builder&quot;&gt;builder&lt;/a&gt; generates a config from a description and lets you edit it live.&lt;/p&gt;
&lt;h3 id=&quot;what-s-in-0-1-0&quot;&gt;What's in 0.1.0&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;7 templates:&lt;/strong&gt; &lt;code&gt;figsh&lt;/code&gt;, &lt;code&gt;cyber&lt;/code&gt;, &lt;code&gt;aurora&lt;/code&gt;, &lt;code&gt;monochrome&lt;/code&gt;, &lt;code&gt;sunset&lt;/code&gt;, &lt;code&gt;ocean&lt;/code&gt;, and &lt;code&gt;paper&lt;/code&gt; (light).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live theming:&lt;/strong&gt; switch at runtime, add a built-in theme button, or override any design token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New sections:&lt;/strong&gt; stats with count-up numbers, FAQ accordion, closing CTA, and pricing with a Monthly/Yearly toggle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactions:&lt;/strong&gt; scroll progress bar, active nav link, card spotlight, seamless brand marquee, back-to-top button.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A real lifecycle:&lt;/strong&gt; &lt;code&gt;devtemDesign()&lt;/code&gt; returns &lt;code&gt;{ setTemplate, destroy }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accessibility:&lt;/strong&gt; skip link, focus rings, &lt;code&gt;aria-expanded&lt;/code&gt; on the menu, native &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; for FAQ, full &lt;code&gt;prefers-reduced-motion&lt;/code&gt; support.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-fuller-example&quot;&gt;A fuller example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const page = devtemDesign(&quot;app-root&quot;, {
  template: &quot;ocean&quot;,
  themeSwitcher: true,
  logoName: &quot;ORBIT&quot;,
  navLinks: [
    { label: &quot;Features&quot;, href: &quot;#features&quot; },
    { label: &quot;Pricing&quot;, href: &quot;#pricing&quot; },
    { label: &quot;FAQ&quot;, href: &quot;#faq&quot; }
  ],
  ctaText: &quot;Start free&quot;,
  ctaHref: &quot;#pricing&quot;,

  heroBadge: &quot;New: team workspaces&quot;,
  heroTitleLine1: &quot;Ship analytics,&quot;,
  heroTitleLine2: &quot;not dashboards&quot;,
  heroSubtitle: &quot;Turn raw events into answers your whole team can read.&quot;,
  primaryBtnText: &quot;Start free&quot;,
  primaryBtnHref: &quot;#pricing&quot;,

  stats: [
    { value: &quot;12k+&quot;, label: &quot;Teams&quot; },
    { value: &quot;99.9%&quot;, label: &quot;Uptime&quot; }
  ],

  features: [
    { title: &quot;Live metrics&quot;, desc: &quot;Streams update in under a second.&quot;, icon: &quot;⚡&quot; },
    { title: &quot;Shared boards&quot;, desc: &quot;One link for the whole team.&quot;, icon: &quot;📊&quot; }
  ],

  yearlyBadge: &quot;Save 20%&quot;,
  pricing: [
    { tier: &quot;Starter&quot;, amount: &quot;$0&quot;, features: [&quot;3 boards&quot;], cta: &quot;Start free&quot;, href: &quot;#&quot; },
    { tier: &quot;Team&quot;, amount: &quot;$19&quot;, period: &quot;/mo&quot;,
      yearly: { amount: &quot;$182&quot;, period: &quot;/yr&quot; },
      featured: true, badge: &quot;Most popular&quot;,
      features: [&quot;Unlimited boards&quot;, &quot;Priority support&quot;], cta: &quot;Go Team&quot;, href: &quot;#&quot; }
  ],

  faq: [{ q: &quot;Is there a free plan?&quot;, a: &quot;Yes. Starter is free forever.&quot; }],
  cta: { title: &quot;See your data differently&quot;, text: &quot;Create account&quot;, href: &quot;#&quot; },
  footer: { tagline: &quot;Built with devtemDesign.&quot;, copy: &quot;© 2026 Orbit&quot; }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see this running, with a template picker and a mobile preview, on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/examples&quot;&gt;examples page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;templates&quot;&gt;Templates&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Template &lt;/th&gt;&lt;th&gt;Vibe &lt;/th&gt;&lt;th&gt;Accents&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;figsh&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Futuristic glass &lt;/td&gt;&lt;td&gt;Cyan, purple, pink&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;cyber&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Neon cyberpunk &lt;/td&gt;&lt;td&gt;Cyan, magenta, red&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;aurora&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Organic nature &lt;/td&gt;&lt;td&gt;Emerald, violet, blue&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;monochrome&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Minimal grayscale &lt;/td&gt;&lt;td&gt;White, silver&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;sunset&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Warm dusk &lt;/td&gt;&lt;td&gt;Orange, rose, gold&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ocean&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Cool depth &lt;/td&gt;&lt;td&gt;Sky, teal, indigo&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;paper&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Light mode &lt;/td&gt;&lt;td&gt;Indigo, pink, cyan&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every color in the stylesheet comes from a token, which is why the light template works without a separate stylesheet.&lt;/p&gt;
&lt;h3 id=&quot;runtime-control&quot;&gt;Runtime control&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const page = devtemDesign(&quot;app-root&quot;, config);

page.setTemplate(&quot;sunset&quot;);   // switch theme, no re-render
page.destroy();               // remove listeners, observers and markup

devtemDesign.version;         // &quot;0.1.0&quot;
devtemDesign.templates;       // [&quot;figsh&quot;, &quot;cyber&quot;, &quot;aurora&quot;, ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calling &lt;code&gt;devtemDesign()&lt;/code&gt; again on the same container destroys the previous instance first, so there are no leaked listeners.&lt;/p&gt;
&lt;h3 id=&quot;theme-tokens&quot;&gt;Theme tokens&lt;/h3&gt;
&lt;p&gt;Override any token with the &lt;code&gt;theme&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;theme: { accent: &quot;#ff4d6d&quot;, accent2: &quot;#ffb703&quot;, &quot;bg-deep&quot;: &quot;#0b0710&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Token &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--accent&lt;/code&gt;, &lt;code&gt;--accent2&lt;/code&gt;, &lt;code&gt;--accent3&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Brand colors and gradients&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--text&lt;/code&gt;, &lt;code&gt;--muted&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Primary and secondary text&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--bg-deep&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Page background, and text on filled buttons&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--glass-bg&lt;/code&gt;, &lt;code&gt;--glass-border&lt;/code&gt;, &lt;code&gt;--glass-blur&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Card surfaces&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--surface&lt;/code&gt;, &lt;code&gt;--line&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Subtle fills and dividers&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;--orb&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Ambient orb opacity&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;config-reference&quot;&gt;Config reference&lt;/h3&gt;
&lt;p&gt;All text fields accept HTML, so icons and inline markup work. Only pass content you trust.&lt;/p&gt;
&lt;h4 id=&quot;core-and-navigation&quot;&gt;Core and navigation&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Option &lt;/th&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Default &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;template&lt;/code&gt; &lt;/td&gt;&lt;td&gt;string &lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;figsh&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;One of the 7 templates&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;theme&lt;/code&gt; &lt;/td&gt;&lt;td&gt;object &lt;/td&gt;&lt;td&gt;&lt;code&gt;{}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Token overrides&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;themeSwitcher&lt;/code&gt; &lt;/td&gt;&lt;td&gt;boolean or string[] &lt;/td&gt;&lt;td&gt;&lt;code&gt;false&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Navbar theme button. An array limits the cycle. Choice is saved in &lt;code&gt;localStorage&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;cursor&lt;/code&gt; &lt;/td&gt;&lt;td&gt;boolean &lt;/td&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Custom cursor on or off&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;logo&lt;/code&gt;, &lt;code&gt;logoName&lt;/code&gt;, &lt;code&gt;logoHref&lt;/code&gt; &lt;/td&gt;&lt;td&gt;string &lt;/td&gt;&lt;td&gt;fscss logo, &lt;code&gt;&quot;CORE&quot;&lt;/code&gt;, &lt;code&gt;&quot;/&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Brand image, name and link&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;navLinks&lt;/code&gt; &lt;/td&gt;&lt;td&gt;(string or &lt;code&gt;{ label, href }&lt;/code&gt;)[] &lt;/td&gt;&lt;td&gt;Features, How it works, Pricing &lt;/td&gt;&lt;td&gt;Navbar and drawer links&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctaText&lt;/code&gt;, &lt;code&gt;ctaHref&lt;/code&gt; &lt;/td&gt;&lt;td&gt;string &lt;/td&gt;&lt;td&gt;&lt;code&gt;&quot;Get started&quot;&lt;/code&gt;, &lt;code&gt;&quot;#&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Navbar button&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;hero&quot;&gt;Hero&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Option &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;heroBadge&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Pill above the title&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;heroTitleLine1&lt;/code&gt;, &lt;code&gt;heroTitleLine2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;H1 lines. The second has an animated gradient&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;heroSubtitle&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Paragraph under the H1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;primaryBtnText&lt;/code&gt;, &lt;code&gt;primaryBtnHref&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Main button&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;secondaryBtnText&lt;/code&gt;, &lt;code&gt;secondaryBtnHref&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Ghost button, rendered only when text is set&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;brands&lt;/code&gt;, &lt;code&gt;brandsSubtitle&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Hero chips and a seamless marquee that pauses on hover&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;sections&quot;&gt;Sections&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Section &lt;/th&gt;&lt;th&gt;Keys &lt;/th&gt;&lt;th&gt;Anchor&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Stats &lt;/td&gt;&lt;td&gt;&lt;code&gt;stats: [{ value, label }]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;#stats&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Features &lt;/td&gt;&lt;td&gt;&lt;code&gt;featuresLabel&lt;/code&gt;, &lt;code&gt;featuresTitle&lt;/code&gt;, &lt;code&gt;featuresSub&lt;/code&gt;, &lt;code&gt;features: [{ title, desc, icon }]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;#features&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Steps &lt;/td&gt;&lt;td&gt;&lt;code&gt;stepsLabel&lt;/code&gt;, &lt;code&gt;stepsTitle&lt;/code&gt;, &lt;code&gt;stepsSub&lt;/code&gt;, &lt;code&gt;steps: [{ title, desc }]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;#how&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Panels &lt;/td&gt;&lt;td&gt;&lt;code&gt;panelsLabel&lt;/code&gt;, &lt;code&gt;panelsTitle&lt;/code&gt;, &lt;code&gt;panelsSub&lt;/code&gt;, &lt;code&gt;panels: [{ tag, title, desc }]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pricing &lt;/td&gt;&lt;td&gt;&lt;code&gt;pricingLabel&lt;/code&gt;, &lt;code&gt;pricingTitle&lt;/code&gt;, &lt;code&gt;pricingSub&lt;/code&gt;, &lt;code&gt;yearlyBadge&lt;/code&gt;, &lt;code&gt;pricing: [...]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;#pricing&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;FAQ &lt;/td&gt;&lt;td&gt;&lt;code&gt;faqLabel&lt;/code&gt;, &lt;code&gt;faqTitle&lt;/code&gt;, &lt;code&gt;faqSub&lt;/code&gt;, &lt;code&gt;faq: [{ q, a }]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;#faq&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CTA &lt;/td&gt;&lt;td&gt;&lt;code&gt;cta: { title, sub, text, href }&lt;/code&gt; &lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Stat values keep their prefix, suffix, commas and decimals, so &lt;code&gt;&quot;$1,200&quot;&lt;/code&gt;, &lt;code&gt;&quot;99.9%&quot;&lt;/code&gt; and &lt;code&gt;&quot;12k+&quot;&lt;/code&gt; all count up correctly.&lt;/p&gt;
&lt;p&gt;A pricing plan looks like this. A Monthly/Yearly toggle appears automatically when any plan has &lt;code&gt;yearly&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;{
  tier: &quot;Pro&quot;, amount: &quot;$12&quot;, period: &quot;/mo&quot;, desc: &quot;For product landings.&quot;,
  features: [&quot;Everything in Free&quot;, &quot;Priority support&quot;],
  cta: &quot;Go Pro&quot;, href: &quot;/signup&quot;,
  featured: true, badge: &quot;Most popular&quot;,
  yearly: { amount: &quot;$115&quot;, period: &quot;/yr&quot; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;footer&quot;&gt;Footer&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;footer: {
  tagline: &quot;Declarative UI engine for the modern web.&quot;,
  copy: &quot;© 2026 devtem-dev&quot;,
  columns: [{ heading: &quot;Product&quot;, links: [{ label: &quot;Docs&quot;, href: &quot;/docs&quot; }] }],
  social:  [{ icon: &quot;fab fa-github&quot;, href: &quot;https://github.com/devtem-dev&quot;, label: &quot;GitHub&quot; }],
  legal:   [{ label: &quot;Privacy&quot;, href: &quot;/privacy&quot; }]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full schema, with types and defaults, lives at &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/api&quot;&gt;design.devtem.org/api&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;upgrading-from-0-0-2&quot;&gt;Upgrading from 0.0.2&lt;/h3&gt;
&lt;p&gt;Three option names changed to match what the code actually reads:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;0.0.2 docs &lt;/th&gt;&lt;th&gt;Use now&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;heroTag&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;heroBadge&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;featuresSectionLabel&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;featuresLabel&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;featuresSectionTitle&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;featuresTitle&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;good-to-know&quot;&gt;Good to know&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Font Awesome is optional. It is only needed for &lt;code&gt;icon&lt;/code&gt; fields and footer social icons. The hamburger, checkmarks and accordions are pure CSS.&lt;/li&gt;
&lt;li&gt;Theming uses &lt;code&gt;color-mix()&lt;/code&gt;, so you need Chrome 111+, Safari 16.2+ or Firefox 113+. Older browsers still render the page but lose some tinted surfaces.&lt;/li&gt;
&lt;li&gt;Rendering replaces the container's contents. Give the engine its own wrapper element.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;links&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/docs&quot;&gt;https://design.devtem.org/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Schema reference: &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/api&quot;&gt;https://design.devtem.org/api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Examples: &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/examples&quot;&gt;https://design.devtem.org/examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Builder: &lt;a rel=&quot;nofollow&quot; href=&quot;https://design.devtem.org/builder&quot;&gt;https://design.devtem.org/builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;npm: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/devtemdesign&quot;&gt;https://www.npmjs.com/package/devtemdesign&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/devtem-dev/devtemDesign&quot;&gt;https://github.com/devtem-dev/devtemDesign&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is MIT licensed and open to contributions. If you build something with it, I'd like to see it. Drop a link in the comments, and tell me which section you want next &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32379/devtemdesign-one-config-object-paints-a-whole-landing-page</guid>
<pubDate>Sun, 25 Oct 2026 10:50:19 +0000</pubDate>
</item>
<item>
<title>FSCSS layout helpers: stack, hstack, center, size, rounded, ratio-fit(), etc</title>
<link>https://coderlegion.com/31947/fscss-layout-helpers-stack-hstack-center-size-rounded-ratio-fit-etc</link>
<description>&lt;p&gt;&lt;strong&gt;FSCSS layout helpers: &lt;code&gt;stack&lt;/code&gt;, &lt;code&gt;hstack&lt;/code&gt;, &lt;code&gt;center&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;rounded&lt;/code&gt;, &lt;code&gt;ratio-fit()&lt;/code&gt;, and friends&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From version 1.2.5 FSCSS added a set of &lt;strong&gt;property shorthands&lt;/strong&gt; aimed at everyday layout and chrome—spacing axes, flex stacks, sizing, and aspect boxes—without inventing a new component system. They compile to ordinary CSS. Use them in &lt;code&gt;.fscss&lt;/code&gt; (CLI, runtime, or &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide&quot;&gt;IDE&lt;/a&gt;); production can ship the compiled sheet only.&lt;/p&gt;
&lt;p&gt;Full list and expansions: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/shorthands&quot;&gt;fscss.devtem.org/shorthands&lt;/a&gt; · language: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;fscss@1.2.5 - 1.2.6+&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-these-exist&quot;&gt;Why these exist&lt;/h4&gt;
&lt;p&gt;A lot of UI CSS is the same four ideas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Space &lt;strong&gt;along one axis&lt;/strong&gt; (padding/margin x or y)  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack&lt;/strong&gt; children (column or row + gap)  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Center&lt;/strong&gt; content in a box  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt; a box (square, both axes, or fit a ratio)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Native CSS can do all of that, but the typing is noisy (&lt;code&gt;display: flex; flex-direction: column; gap: …&lt;/code&gt; every time). These helpers expand at compile time so the mental model stays short.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;spacing-axes&quot;&gt;Spacing axes&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Shorthand &lt;/th&gt;&lt;th&gt;Idea &lt;/th&gt;&lt;th&gt;Typical expansion&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;mx&lt;/code&gt; / &lt;code&gt;my&lt;/code&gt; &lt;/td&gt;&lt;td&gt;margin on x / y &lt;/td&gt;&lt;td&gt;&lt;code&gt;margin-left&lt;/code&gt; + &lt;code&gt;margin-right&lt;/code&gt; / top + bottom&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;px&lt;/code&gt; / &lt;code&gt;py&lt;/code&gt; &lt;/td&gt;&lt;td&gt;padding on x / y &lt;/td&gt;&lt;td&gt;same pattern for padding&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;inset-x&lt;/code&gt; / &lt;code&gt;inset-y&lt;/code&gt; &lt;/td&gt;&lt;td&gt;positioned inset on one axis &lt;/td&gt;&lt;td&gt;without forcing the other axis to &lt;code&gt;0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  px: 20px;
  py: 16px;
  mx: auto;
  max-w: 420px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use these when you only care about one axis. Prefer full &lt;code&gt;margin&lt;/code&gt; / &lt;code&gt;padding&lt;/code&gt; when all four sides share one value.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;stack-and-hstack&quot;&gt;&lt;code&gt;stack&lt;/code&gt; and &lt;code&gt;hstack&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Vertical and horizontal flex stacks with gap:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.panel {
  stack: 12px;   /* column + gap */
}

.toolbar {
  hstack: 8px;   /* row + gap */
  fwrap: wrap;   /* if you also use wrap helpers */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Roughly: &lt;code&gt;display: flex&lt;/code&gt;, direction column or row, and &lt;code&gt;gap&lt;/code&gt; from the value. Ideal for forms, toolbars, and card bodies where “children in a line with space between them” is the whole layout story.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;center&quot;&gt;&lt;code&gt;center&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Center children in the box (axis or both, depending on the form your build supports—commonly flex centering):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.hero {
  min-h: 40vh;
  center: both; /* or center: true — see docs */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Handy for empty states, icon wells, and hero blocks without repeating &lt;code&gt;align-items&lt;/code&gt; / &lt;code&gt;justify-content&lt;/code&gt; every time.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;size-and-friends&quot;&gt;&lt;code&gt;size&lt;/code&gt; and friends&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Helper &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;size&lt;/code&gt; &lt;/td&gt;&lt;td&gt;width &lt;strong&gt;and&lt;/strong&gt; height (one value → square, or two values)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;max-size&lt;/code&gt; / &lt;code&gt;min-size&lt;/code&gt; &lt;/td&gt;&lt;td&gt;both axes’ max/min&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;insert: ratio-fit();&lt;/code&gt; &lt;/td&gt;&lt;td&gt;size a box to a ratio inside a container&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.avatar {
  size: 48px;
  rounded: 999px;
}

.thumb {
  size: 100% 180px;
}

.media {
  inset: ratio-fit(16 / 9);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ratio-fit&lt;/code&gt; is for the common “keep 16:9 (or any ratio) without a padding-hack essay.” Exact expansion is in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/shorthands&quot;&gt;shorthands reference&lt;/a&gt;—treat it as the declarative cousin of aspect-ratio + constrained width/height patterns.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;rounded-and-surface-chrome&quot;&gt;&lt;code&gt;rounded&lt;/code&gt; and surface chrome&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  rounded: 16px;
  bg: #121a30;
  shadow: 0 10px 30px #0003;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rounded&lt;/code&gt; maps to &lt;code&gt;border-radius&lt;/code&gt;. Together with color/spacing shorthands (&lt;code&gt;bg&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;m&lt;/code&gt;, …) you can sketch a card shell in a few lines and still compile to standard properties.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;small-composed-example&quot;&gt;Small composed example&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.page {
  min-h: 100vh;
  p: 24px;
  bg: #0b1020;
  color: #e6edf7;
  ff: system-ui, sans-serif;
}

.wrap {
  max-w: 960px;
  mx: auto;
  stack: 20px;
}

.row {
  hstack: 16px;
  fwrap: wrap;
}

.card {
  flex: 1;
  min-w: 240px;
  p: 20px;
  rounded: 16px;
  bg: #121a30;
  border: 1px solid #22304f;
  stack: 12px;
}

.icon {
  size: 40px;
  rounded: 12px;
  center: true;
  bg: #1b2745;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read it top to bottom: page padding → centered column → horizontal row of cards → each card is a vertical stack. No design-system runtime—only CSS the browser already understands after compile.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;runtime-note-fscss-props&quot;&gt;Runtime note: &lt;code&gt;fscss-props&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;If you only need &lt;strong&gt;property&lt;/strong&gt; shorthands in the browser (not full &lt;code&gt;@arr&lt;/code&gt; / &lt;code&gt;@define&lt;/code&gt; / &lt;code&gt;pattern()&lt;/code&gt;), &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/fscss-props&quot;&gt;fscss-props&lt;/a&gt; expands a similar surface at runtime and exposes JS APIs (&lt;code&gt;element.fscssProp&lt;/code&gt;, &lt;code&gt;fscssPropText&lt;/code&gt;) for live updates. Full language features still go through the FSCSS compiler (CLI, &lt;code&gt;runtime.js&lt;/code&gt; / &lt;code&gt;esm.js&lt;/code&gt;, or the IDE).&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;when-to-use-what&quot;&gt;When to use what&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Goal &lt;/th&gt;&lt;th&gt;Prefer&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;One-off page / prototype &lt;/td&gt;&lt;td&gt;Shorthands in &lt;code&gt;.fscss&lt;/code&gt; + CDN runtime or IDE&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Design tokens + charts + modules &lt;/td&gt;&lt;td&gt;&lt;code&gt;@import&lt;/code&gt; packs (&lt;code&gt;st-core@v2&lt;/code&gt;, etc.) &lt;strong&gt;and&lt;/strong&gt; shorthands for local layout&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production &lt;/td&gt;&lt;td&gt;Compile with CLI; ship the CSS file&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JS-driven tweaks of padding/size only &lt;/td&gt;&lt;td&gt;&lt;code&gt;fscss-props&lt;/code&gt; or set CSS variables / longhands from JS&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Shorthands do not replace modules. They remove friction &lt;strong&gt;between&lt;/strong&gt; modules—nav bars, card grids, toolbars—where flex + gap + radius is enough.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;try-it&quot;&gt;Try it&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install fscss@latest 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.6/runtime.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or open the &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide&quot;&gt;FSCSS IDE&lt;/a&gt;, paste a &lt;code&gt;.card { stack: 12px; rounded: 16px; p: 20px; }&lt;/code&gt; block, and hit compile.&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/31947/fscss-layout-helpers-stack-hstack-center-size-rounded-ratio-fit-etc</guid>
<pubDate>Wed, 21 Oct 2026 09:20:38 +0000</pubDate>
</item>
<item>
<title>Shipped: the FSCSS Browser IDE</title>
<link>https://coderlegion.com/31842/shipped-the-fscss-browser-ide</link>
<description>&lt;p&gt;&lt;strong&gt;Multi-file FSCSS in the browser — compile, preview, enable packages, export plain CSS.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No install required. Open &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide&quot;&gt;fscss.devtem.org/ide&lt;/a&gt; and work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-it-is&quot;&gt;What it is&lt;/h3&gt;
&lt;p&gt;The FSCSS IDE is a single-page workspace for &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org&quot;&gt;Figured Shorthand CSS&lt;/a&gt;: write &lt;code&gt;.fscss&lt;/code&gt;, compile to standard CSS, preview HTML, and pull community modules without leaving the tab.&lt;/p&gt;
&lt;p&gt;It targets the same language as the CLI and CDN runtime (&lt;code&gt;fscss@1.2.6+&lt;/code&gt;). Prototypes stay in the browser; production still ships &lt;strong&gt;compiled CSS&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layout-quick-map-&quot;&gt;Layout (quick map)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Area &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Top bar&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Run, Preview, Auto-compile, Export, Import, theme, settings&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Left&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Files, Components, Snippets, Tools&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Center&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Monaco editor + file tabs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Right&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Compiled CSS / live Preview&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bottom&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Terminal + Problems&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Compiler version, errors, cursor&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Drag the dividers. On phones the sidebar overlays and panes stack.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-start&quot;&gt;Quick start&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open or create &lt;code&gt;main.fscss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ctrl+Enter&lt;/strong&gt; — compile (CSS on the right).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ctrl+Shift+Enter&lt;/strong&gt; — compile and open preview.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export&lt;/strong&gt; — CSS, minified CSS, HTML, ZIP, or JSON backup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Autosave uses &lt;code&gt;localStorage&lt;/code&gt; (keys under &lt;code&gt;fside:&lt;/code&gt;). Export a backup if the browser blocks storage.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;multi-file-and-import&quot;&gt;Multi-file and &lt;code&gt;@import&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The workspace is a virtual file system. Local imports resolve against the importing file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((widget-root, widget-card) from &quot;./widgets.fscss&quot;)
@import((*) from st-core@v2)
@import((mirror, stagger) from fscss:micros)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Selective names, aliases (&lt;code&gt;as&lt;/code&gt;), wildcards, and registry-style names work the same way as in the CLI. Remote URLs need quotes; listed library names usually do not.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;packages-libck-libon-xfcmd&quot;&gt;Packages: &lt;code&gt;libck&lt;/code&gt; / &lt;code&gt;libon&lt;/code&gt; / xfcmd&lt;/h3&gt;
&lt;p&gt;Open the terminal (&lt;strong&gt;Ctrl+`&lt;/strong&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;libck st-core
yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or a full repo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;libck https://github.com/fscss-ttr/overlapui.fscss
yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The IDE loads &lt;code&gt;package.json&lt;/code&gt;, checks &lt;code&gt;fscss_version&lt;/code&gt;, downloads &lt;code&gt;file.modules&lt;/code&gt;, verifies &lt;code&gt;@define&lt;/code&gt; helpers, then runs the pack’s &lt;strong&gt;enable&lt;/strong&gt; script when you confirm. Typical enable flow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;snippets for public helpers  &lt;/li&gt;
&lt;li&gt;a small demo folder  &lt;/li&gt;
&lt;li&gt;compile + preview  &lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;liboff st-core
xfcmd list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Disable removes snippets; demo files stay unless you delete them. Authoring side: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/xfcmds-guide&quot;&gt;xfcmds guide&lt;/a&gt; · &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/modules-guide&quot;&gt;modules guide&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-else-is-built-in&quot;&gt;What else is built in&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Monaco&lt;/strong&gt; — FSCSS highlighting, folding, snippets  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Component search&lt;/strong&gt; — registry helpers, one-click import  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compiler version&lt;/strong&gt; — load another &lt;code&gt;fscss@x.y.z&lt;/code&gt; from jsDelivr  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-compile&lt;/strong&gt; while typing (toggle)  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PWA-style&lt;/strong&gt; install chrome on supported browsers  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Console&lt;/strong&gt; — &lt;code&gt;window.xfscss&lt;/code&gt; for ad-hoc API checks  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide-guide&quot;&gt;IDE guide&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-three-lines-many-classes&quot;&gt;Example: three lines → many classes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr classes[ count(1000, 1) ]
*Emails are not allowed*ses[] {
  margin: @arr.classes[]px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compile in the IDE and scroll the CSS pane — or open a prefilled buffer:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide#code=%40arr%20classes%5B%20count(1000%2C%201)%20%5D%0A.item-%40arr.classes%5B%5D%20%7B%0A%20%20%20%20margin%3A%20%40arr.classes%5B%5Dpx%3B%0A%20%20%7D&quot;&gt;Open in IDE&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-enable-a-pack&quot;&gt;Example: enable a pack&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;libck st-core
yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in a new &lt;code&gt;.fscss&lt;/code&gt; file (or the generated demo):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)
@st-root()
@arr data[42, 58, 65, 60, 78, 70, 92]
@st-chart-fill(.fill, data)
@st-chart-line(.line, data)
.chart {
  @st-chart-points(data)
  position: relative;
  height: 220px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML: a &lt;code&gt;.chart&lt;/code&gt; with &lt;code&gt;.fill&lt;/code&gt; and &lt;code&gt;.line&lt;/code&gt; children. Preview updates after compile. Same contract as the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;st-core&lt;/a&gt; templates: JS only patches &lt;code&gt;--st-pN&lt;/code&gt; when data changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ship-path&quot;&gt;Ship path&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Stage &lt;/th&gt;&lt;th&gt;Tool&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Learn / combine modules &lt;/td&gt;&lt;td&gt;IDE + runtime CDN&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Real multi-file project &lt;/td&gt;&lt;td&gt;IDE export ZIP or clone + CLI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production &lt;/td&gt;&lt;td&gt;&lt;code&gt;fscss in.fscss out.css&lt;/code&gt; — no browser compiler on the live site&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g fscss@1.2.6
fscss src/site.fscss dist/site.css
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;links-1&quot;&gt;Links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IDE:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide&quot;&gt;fscss.devtem.org/ide&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDE guide:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/ide-guide&quot;&gt;fscss.devtem.org/ide-guide&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;xfcmds:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/xfcmds-guide&quot;&gt;fscss.devtem.org/xfcmds-guide&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language docs:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/docs&quot;&gt;fscss.devtem.org/&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Libraries:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/libraries&quot;&gt;fscss.devtem.org/libraries&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss&quot;&gt;github.com/Figsh/xfscss&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modules org:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr&quot;&gt;github.com/fscss-ttr&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hub tutorial:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://hub.devtem.org/tutorial&quot;&gt;hub.devtem.org/tutorial&lt;/a&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;The IDE is meant to lower the cost of &lt;em&gt;trying&lt;/em&gt; FSCSS: multi-file imports, package enable in one terminal prompt, and a clear export to plain CSS. It does not replace the CLI for production builds—it shortens the path until you need one.&lt;/p&gt;
&lt;p&gt;Open it, hit &lt;strong&gt;Ctrl+Enter&lt;/strong&gt;, and if a pack has an enable script, &lt;code&gt;libon&lt;/code&gt;  is enough to see a demo.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31842/shipped-the-fscss-browser-ide</guid>
<pubDate>Tue, 20 Oct 2026 11:57:23 +0000</pubDate>
</item>
<item>
<title>Build a Pure CSS Radar Chart — Lightweight, Max Performance</title>
<link>https://coderlegion.com/30746/build-a-pure-css-radar-chart-lightweight-max-performance</link>
<description>&lt;p&gt;Most radar charts pull in SVG path math or a JavaScript charting library. You can skip both.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;st-core.fscss&lt;/strong&gt;, a spider (radar) chart is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an FSCSS array of scores (0–100)&lt;/li&gt;
&lt;li&gt;polar coordinates stored as CSS variables (&lt;code&gt;--st-sxN&lt;/code&gt;, &lt;code&gt;--st-syN&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;shapes drawn with &lt;code&gt;clip-path: polygon()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;rings and spokes from gradients&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No canvas. No SVG. No chart runtime on the critical path.&lt;/p&gt;
&lt;p&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-it-works&quot;&gt;Here’s how it works&lt;/h3&gt;
&lt;h4 id=&quot;1-data-is-an-array&quot;&gt;1. Data is an array&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr skills[80, 65, 90, 55, 70, 85]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Length can be 5, 6, 8 — whatever you need. Geometry (number of polygon vertices) is fixed when FSCSS expands the mixins.&lt;/p&gt;
&lt;h4 id=&quot;2-one-mixin-writes-coordinates&quot;&gt;2. One mixin writes coordinates&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.spider {
  @st-spider-points(skills, 42)  /* array, max radius % of the box */
  position: relative;
  width: 280px;
  height: 280px;
  border-radius: 50%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each index &lt;code&gt;i&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;angle = &lt;code&gt;(i - 1) × 360° / N&lt;/code&gt; (0° at the &lt;strong&gt;top&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;radius = &lt;code&gt;(value / 100) × rmax&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;CSS gets:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;--st-sxN: calc(50% + (r * 1%) * sin(θ));
--st-syN: calc(50% - (r * 1%) * cos(θ));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trig stays in the browser’s &lt;code&gt;calc()&lt;/code&gt;. Updating values later is rewriting those variables.&lt;/p&gt;
&lt;h4 id=&quot;3-renderers-only-read-variables&quot;&gt;3. Renderers only read variables&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@st-spider-fill(.spider-fill, skills)
@st-spider-line(.spider-line, skills)
@st-spider-dots(.spider-dot-, skills, 9px)
@st-spider-grid(.spider-grid)
@st-spider-spokes(.spider-spokes, skills)
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;fill&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Closed polygon through every &lt;code&gt;--st-sx/sy&lt;/code&gt; pair&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;line&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Dual-pass polygon (outer path + path scaled toward center) so the stroke has real thickness&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;dots&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Absolutely positioned markers at each vertex&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;grid&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Concentric rings (&lt;code&gt;radial-gradient&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;spokes&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Thin radial axes (&lt;code&gt;conic-gradient&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; call &lt;code&gt;@st-spider-points&lt;/code&gt; on the &lt;strong&gt;host&lt;/strong&gt;. Children inherit. Same model as linear &lt;code&gt;@st-chart-points&lt;/code&gt; / fill / line in st-core.&lt;/p&gt;
&lt;h4 id=&quot;4-markup-is-plain-html&quot;&gt;4. Markup is plain HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider&quot;&amp;gt;
  &amp;lt;div class=&quot;spider-grid&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-spokes&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-2&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-3&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-4&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-5&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;spider-dot-6&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dots need real nodes — the mixin emits &lt;code&gt;.spider-dot-1&lt;/code&gt; … &lt;code&gt;.spider-dot-N&lt;/code&gt; rules; it does not invent elements.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-values-can-be-displayed-on-the-dot-nodes&quot;&gt;Here’s how values can be displayed on the dot nodes&lt;/h3&gt;
&lt;p&gt;Markers are normal DOM nodes, so you can show the score next to (or inside) each dot without a chart API.&lt;/p&gt;
&lt;h4 id=&quot;option-a-data-value-css-after&quot;&gt;Option A — &lt;code&gt;data-value&lt;/code&gt; + CSS &lt;code&gt;::after&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider-dot-1&quot; data-value=&quot;80&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;spider-dot-2&quot; data-value=&quot;65&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;!-- … --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.spider-dot-1,
.spider-dot-2,
.spider-dot-3,
.spider-dot-4,
.spider-dot-5,
.spider-dot-6 {
  /* st-core already sets left/top from --st-sx / --st-sy */
}

[data-value]::after {
  content: attr(data-value);
  position: absolute;
  left: 50%;
  top: -1.4em;
  transform: translateX(-50%);
  font-size: 0.65rem;
  font-variant-numeric: tabular-nums;
  color: var(--st-text);
  white-space: nowrap;
  pointer-events: none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Push labels slightly outward per axis if they collide with the polygon (different &lt;code&gt;top&lt;/code&gt; / &lt;code&gt;left&lt;/code&gt; offsets per index).&lt;/p&gt;
&lt;h4 id=&quot;option-b-nested-label-element&quot;&gt;Option B — nested label element&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;spider-dot-1&quot;&amp;gt;
  &amp;lt;span class=&quot;dot-label&quot;&amp;gt;80&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.dot-label {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 4px);
  transform: translateX(-50%);
  font-size: 0.65rem;
  color: var(--st-muted);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;option-c-update-labels-from-js-with-the-series&quot;&gt;Option C — update labels from JS with the series&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function setSeries(host, values, rmax = 42) {
  const n = values.length;
  let css = &quot;&quot;;
  values.forEach((v, i) =&amp;gt; {
    const ang = (i * 360) / n;
    const r = (v / 100) * rmax;
    css += `--st-sx${i + 1}: calc(50% + (${r} * 1%) * sin(${ang} * 1deg));`;
    css += `--st-sy${i + 1}: calc(50% - (${r} * 1%) * cos(${ang} * 1deg));`;
    const dot = host.querySelector(`.spider-dot-${i + 1}`);
    if (dot) {
      dot.dataset.value = v;
      // or: dot.querySelector(&quot;.dot-label&quot;).textContent = v;
    }
  });
  host.style.cssText = css;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Geometry and labels stay in sync because both come from the same array pass.&lt;/p&gt;
&lt;p&gt;Axis names (Speed, Quality, …) stay outside the dots — absolute labels around the circle — so vertices stay readable at small sizes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-why-it-s-lighter&quot;&gt;Here’s why it’s lighter&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Traditional radar &lt;/th&gt;&lt;th&gt;st-core radar&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Chart.js / D3 / Recharts bundle &lt;/td&gt;&lt;td&gt;Mixins → CSS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SVG path string updates &lt;/td&gt;&lt;td&gt;Custom properties + &lt;code&gt;clip-path&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Layout + paint on every redraw &lt;/td&gt;&lt;td&gt;Often compositor-only if you transition &lt;code&gt;clip-path&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Library hydration before first paint &lt;/td&gt;&lt;td&gt;CSS paints as soon as the stylesheet applies&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Compile time:&lt;/strong&gt; FSCSS expands loops into concrete &lt;code&gt;polygon()&lt;/code&gt; stops and selectors. Array &lt;strong&gt;length&lt;/strong&gt; is fixed in the stylesheet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Runtime:&lt;/strong&gt; Changing scores is assigning &lt;code&gt;--st-sxN&lt;/code&gt; / &lt;code&gt;--st-syN&lt;/code&gt; (and optional label text). No path serializer, no virtual DOM for the shape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Size:&lt;/strong&gt; A compiled spider block is on the order of &lt;strong&gt;~1 kb&lt;/strong&gt; of CSS for the chart rules — not a 50–200 kb chart dependency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance profile:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First paint: no JS required for a static series  &lt;/li&gt;
&lt;li&gt;Updates: style recalc + composite; avoid layout thrash if size is fixed  &lt;/li&gt;
&lt;li&gt;Multi-series: extra layers are extra divs + inherited variables, not extra chart instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s “max performance” in the practical sense: &lt;strong&gt;minimum work per frame and minimum bytes on the wire.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-are-some-use-cases-of-spider-radar-charts&quot;&gt;Here are some use cases of spider (radar) charts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Skills / competency matrices&lt;/strong&gt; — Speed, Quality, UX, Scale, Cost, Support for a team or candidate.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product scorecards&lt;/strong&gt; — Compare features across dimensions (performance, price, ecosystem, docs, support).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sprint or OKR health&lt;/strong&gt; — Delivery, quality, collaboration, predictability on one glanceable shape.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security / compliance posture&lt;/strong&gt; — Control families as axes; one polygon per system or vendor.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Player or character stats&lt;/strong&gt; — Games and education UIs where polar layout is the expected metaphor.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brand or perception studies&lt;/strong&gt; — Trust, value, innovation, service — survey averages as radii.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anywhere humans already think in “balanced vs spiky” profiles, a radar chart communicates faster than six separate bars.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;here-s-how-to-do-multi-series-radar-charts-with-still-pure-css&quot;&gt;Here’s how to do multi-series radar charts with still pure CSS&lt;/h3&gt;
&lt;p&gt;Each series is a &lt;strong&gt;separate host&lt;/strong&gt; with its own &lt;code&gt;@st-spider-points&lt;/code&gt;. Shared grid and spokes sit underneath.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)
@st-root()
@st-spider-root()

@arr seriesA[70, 55, 40, 85, 90]
@arr seriesB[45, 75, 60, 50, 55]
@arr seriesC[85, 40, 70, 35, 65]

@st-spider-fill(.fill-a, seriesA)
@st-spider-line(.line-a, seriesA)
@st-spider-dots(.dot-a-, seriesA, 8px)

@st-spider-fill(.fill-b, seriesB)
@st-spider-line(.line-b, seriesB)
@st-spider-dots(.dot-b-, seriesB, 8px)

@st-spider-fill(.fill-c, seriesC)
@st-spider-line(.line-c, seriesC)
@st-spider-dots(.dot-c-, seriesC, 8px)

@st-spider-grid(.spider-grid)
@st-spider-spokes-n(.multi-spokes, 5)

.multi-spider {
  position: relative;
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: var(--st-surface);
}

.series-a {
  @st-spider-points(seriesA, 42)
  position: absolute;
  inset: 0;
  --st-accent: #ff9f43;
  --st-spider-fill-opacity: 20%;
}
.series-b {
  @st-spider-points(seriesB, 42)
  position: absolute;
  inset: 0;
  --st-accent: #54a0ff;
  --st-spider-fill-opacity: 16%;
}
.series-c {
  @st-spider-points(seriesC, 42)
  position: absolute;
  inset: 0;
  --st-accent: #ff6b9d;
  --st-spider-fill-opacity: 18%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;multi-spider&quot;&amp;gt;
  &amp;lt;div class=&quot;spider-grid&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;multi-spokes&quot;&amp;gt;&amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-a&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-a&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-a&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-2&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-3&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-4&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-a-5&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-b&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-b&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-b&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-b-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- … --&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;series-c&quot;&amp;gt;
    &amp;lt;div class=&quot;fill-c&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;line-c&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;dot-c-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- … --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why this stays pure CSS:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No JS chart instance per series  &lt;/li&gt;
&lt;li&gt;Isolation is DOM + custom properties, not a scene graph  &lt;/li&gt;
&lt;li&gt;Opacity and accent are tokens (&lt;code&gt;--st-spider-fill-opacity&lt;/code&gt;, &lt;code&gt;--st-accent&lt;/code&gt;)  &lt;/li&gt;
&lt;li&gt;Spoke count can match the axis count via &lt;code&gt;@st-spider-spokes-n(..., 5)&lt;/code&gt; without tying spokes to one series’ array&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If two series shared one host and only one &lt;code&gt;@st-spider-points&lt;/code&gt; call, the last write would win and every series would draw the same shape. Separate hosts fix that — the same multi-series rule as linear st-core charts.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-start-1&quot;&gt;Quick start&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;FSCSS &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss&quot;&gt;CLI&lt;/a&gt;, Vscode &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;extention&lt;/a&gt;, or prototyping via its &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/&quot;&gt;cdn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full API in the repo: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Pure CSS radar charts are not a compromise. With st-core they’re a &lt;strong&gt;styling problem&lt;/strong&gt;: arrays in, variables out, polygons on screen — lightweight by construction, fast by default.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/30746/build-a-pure-css-radar-chart-lightweight-max-performance</guid>
<pubDate>Sun, 11 Oct 2026 20:07:46 +0000</pubDate>
</item>
<item>
<title>Designing overlapped / cascaded UI with overlapui</title>
<link>https://coderlegion.com/29968/designing-overlapped-cascaded-ui-with-overlapui</link>
<description>&lt;p&gt;Build avatar stacks, notification piles, image strips, and radial menus with a token-driven CSS architecture—rest vs open states, no JavaScript for the interaction—using the overlapui FSCSS module.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reference:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/overlapui.fscss&quot;&gt;overlapui.fscss&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Stacked faces in a members column, cards that peek under each other, photos that share one edge, or a center control that fans into actions: that look is usually a pile of one-off rules. &lt;strong&gt;overlapui&lt;/strong&gt; treats it as a small architecture—shared &lt;code&gt;--ou-*&lt;/code&gt; tokens, four cascade patterns, hover and focus-within open states—and compiles to plain CSS.&lt;/p&gt;
&lt;p&gt;This article is description- and code-based: how the layers fit, with copy-paste examples. The only project reference is the module itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-cascaded-ui-means-here&quot;&gt;What “cascaded UI” means here&lt;/h3&gt;
&lt;p&gt;A cascade has two densities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rest&lt;/strong&gt; — children share space (negative margin, peek, or stacked under a hub).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open&lt;/strong&gt; — &lt;code&gt;:hover&lt;/code&gt; or &lt;code&gt;:focus-within&lt;/code&gt; restores gap, full card height, or a radial orbit.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tokens describe density and chrome. Mixins encode the rest/open contract. Markup stays light lists, card roots, or a center-plus-satellites control.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Pattern &lt;/th&gt;&lt;th&gt;Rest &lt;/th&gt;&lt;th&gt;Open&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Avatar &lt;/td&gt;&lt;td&gt;horizontal pull-in &lt;/td&gt;&lt;td&gt;sibling gap&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Card &lt;/td&gt;&lt;td&gt;vertical peek &lt;/td&gt;&lt;td&gt;expanded margins&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Image &lt;/td&gt;&lt;td&gt;overlap + tilt &lt;/td&gt;&lt;td&gt;align + gap&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Circle &lt;/td&gt;&lt;td&gt;satellites hidden under center &lt;/td&gt;&lt;td&gt;polar fan-out&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;
&lt;p&gt;Use FSCSS &lt;strong&gt;1.2.3+&lt;/strong&gt; (1.2.5+ recommended). Load the runtime with &lt;code&gt;defer&lt;/code&gt; or &lt;code&gt;async&lt;/code&gt; when prototyping.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.5/runtime.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from overlapui)

@ou-root()
@avatar-overlap(.avatar-overlap)
@card-overlap(.card-overlap)
@img-overlap(.img-overlap)
@circle-overlap(.circle-overlap)
@ou-reduced-motion()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One-shot install (default class names):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from overlapui)
@overlapui()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Selective import:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((ou-root, ou-avatar-colors avatar-overlap, circle-overlap, ou-reduced-motion) from overlapui)

@ou-root()
@avatar-overlap(.team)
@circle-overlap(.fab)
@ou-reduced-motion()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layer-1-tokens---ou--&quot;&gt;Layer 1 — tokens (&lt;code&gt;--ou-*&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;Theme the product by overriding tokens after &lt;code&gt;@ou-root()&lt;/code&gt;, not by forking mixin internals.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@ou-root()

:root {
  --ou-ring: #0f172a;
  --ou-time: 0.35s;
  --ou-ease: cubic-bezier(0.3, 1.3, 0.5, 1);
  --ou-center-bg: #6366f1;

  @ou-avatar-size(44px, -14px)
  @ou-card-size(34px, 76px)
  @ou-img-size(96px, -52px)
  @ou-circle-size(48px, 80px)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Token area &lt;/th&gt;&lt;th&gt;Variables (examples)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Motion / chrome &lt;/td&gt;&lt;td&gt;&lt;code&gt;--ou-time&lt;/code&gt;, &lt;code&gt;--ou-ease&lt;/code&gt;, &lt;code&gt;--ou-ring&lt;/code&gt;, &lt;code&gt;--ou-ring-width&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Avatar &lt;/td&gt;&lt;td&gt;&lt;code&gt;--ou-size&lt;/code&gt;, &lt;code&gt;--ou-overlap&lt;/code&gt;, &lt;code&gt;--ou-gap&lt;/code&gt;, &lt;code&gt;--ou-c&lt;/code&gt;, &lt;code&gt;--ou-letter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Card &lt;/td&gt;&lt;td&gt;&lt;code&gt;--ou-peek&lt;/code&gt;, &lt;code&gt;--ou-card-h&lt;/code&gt;, &lt;code&gt;--ou-card-gap&lt;/code&gt;, &lt;code&gt;--ou-card-bg&lt;/code&gt;, &lt;code&gt;--ou-card-title&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Image &lt;/td&gt;&lt;td&gt;&lt;code&gt;--ou-img&lt;/code&gt;, &lt;code&gt;--ou-img-overlap&lt;/code&gt;, &lt;code&gt;--ou-img-gap&lt;/code&gt;, &lt;code&gt;--ou-img-tilt&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Circle &lt;/td&gt;&lt;td&gt;&lt;code&gt;--ou-item&lt;/code&gt;, &lt;code&gt;--ou-r&lt;/code&gt;, &lt;code&gt;--ou-center-bg&lt;/code&gt;, &lt;code&gt;--n&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Dense UI (tables, toolbars): smaller size, stronger overlap.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  @ou-avatar-size(28px, -10px)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layer-2-avatar-cascade&quot;&gt;Layer 2 — avatar cascade&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; presence clusters—members, assignees, “also on this thread.”&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul class=&quot;avatar-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
  &amp;lt;li data-alph=&quot;g&quot;&amp;gt;Gina Park&amp;lt;/li&amp;gt;
  &amp;lt;li data-alph=&quot;h&quot;&amp;gt;
    &amp;lt;img src=&quot;https://i.pravatar.cc/80?img=12&quot; alt=&quot;&quot;&amp;gt;
    Hank Lee
  &amp;lt;/li&amp;gt;
  &amp;lt;li data-alph=&quot;5&quot;&amp;gt;5001&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@ou-root()
@avatar-overlap(.avatar-overlap)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Behavior encoded by the module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Rest: each face after the first uses &lt;code&gt;margin-left: var(--ou-overlap)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open: &lt;code&gt;li + li&lt;/code&gt; uses &lt;code&gt;var(--ou-gap)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Letter from &lt;code&gt;data-alph&lt;/code&gt; via &lt;code&gt;::before&lt;/code&gt;; &lt;code&gt;font-size: 0&lt;/code&gt; on the face keeps names in the DOM for assistive tech.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optional &lt;code&gt;img&lt;/code&gt; covers the letter; letter remains fallback.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A–Z / 0–9 map to a palette through FSCSS arrays at compile time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Custom selector:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@avatar-overlap(.members)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul class=&quot;members&quot; tabindex=&quot;0&quot;&amp;gt;
  &amp;lt;li data-alph=&quot;a&quot;&amp;gt;Ada&amp;lt;/li&amp;gt;
  &amp;lt;li data-alph=&quot;n&quot;&amp;gt;Nia&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layer-3-card-cascade&quot;&gt;Layer 3 — card cascade&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; notification piles, activity stacks, ticket previews without a drawer component.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;card-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=1&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;h3&amp;gt;Invoice paid&amp;lt;/h3&amp;gt;
    Acme Corp · $2,400
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=2&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;h3&amp;gt;Seat added&amp;lt;/h3&amp;gt;
    workspace update
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=3&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;h3&amp;gt;Comment&amp;lt;/h3&amp;gt;
    “Ship the cascade”
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@card-overlap(.card-overlap)

:root {
  --ou-peek: 34px;
  --ou-card-h: 76px;
  --ou-card-gap: 10px;
  --ou-card-bg: #1f2a48;
  --ou-card-title: #fff;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rest uses a negative top margin derived from peek and card height so only a band of each card shows. Open restores &lt;code&gt;var(--ou-card-gap)&lt;/code&gt; between siblings. Prefer animating margin/transform over toggling &lt;code&gt;display&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layer-4-image-cascade&quot;&gt;Layer 4 — image cascade&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; editorial strips, gallery teasers, “recent media.”&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;img-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
  &amp;lt;img src=&quot;https://picsum.photos/120?random=4&quot; alt=&quot;&quot;&amp;gt;
  &amp;lt;img src=&quot;https://picsum.photos/120?random=5&quot; alt=&quot;&quot;&amp;gt;
  &amp;lt;img src=&quot;https://picsum.photos/120?random=6&quot; alt=&quot;&quot;&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@img-overlap(.img-overlap)

:root {
  @ou-img-size(96px, -48px)
  --ou-img-tilt: -5deg;
  --ou-img-tilt-even: 4deg;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rest: overlap plus alternating tilt. Open: &lt;code&gt;rotate(0)&lt;/code&gt; and gap between images.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layer-5-circle-cascade&quot;&gt;Layer 5 — circle cascade&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Role:&lt;/strong&gt; settings hub, FAB-style actions, compact toolbars.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;circle-overlap&quot; style=&quot;--n:5&quot; tabindex=&quot;0&quot;&amp;gt;
  &amp;lt;button type=&quot;button&quot; aria-label=&quot;Settings menu&quot;&amp;gt;+&amp;lt;/button&amp;gt;
  &amp;lt;a href=&quot;#profile&quot; aria-label=&quot;Profile&quot;&amp;gt;P&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;#billing&quot; aria-label=&quot;Billing&quot;&amp;gt;B&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;#team&quot; aria-label=&quot;Team&quot;&amp;gt;T&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;#api&quot; aria-label=&quot;API keys&quot;&amp;gt;K&amp;lt;/a&amp;gt;
  &amp;lt;a href=&quot;#logout&quot; aria-label=&quot;Sign out&quot;&amp;gt;×&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@circle-overlap(.circle-overlap)

:root {
  @ou-circle-size(48px, 80px)
  --ou-center-bg: #6366f1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rules of the pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;First child&lt;/strong&gt; is the center control (background &lt;code&gt;--ou-center-bg&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--n&lt;/code&gt; is the number of satellites (exclude the center).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Satellites sit under the center at rest (&lt;code&gt;opacity: 0&lt;/code&gt;, scaled down), then fan with angle &lt;code&gt;360deg / var(--n) * var(--i)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built-in &lt;code&gt;--i&lt;/code&gt; / &lt;code&gt;--c&lt;/code&gt; defaults apply per &lt;code&gt;:nth-child&lt;/code&gt;; override &lt;code&gt;--c&lt;/code&gt; when needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Topbar-friendly footprint:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.settings-wrap {
  position: relative;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.settings-orbit {
  --n: 5;
  --ou-item: 40px;
  --ou-r: 70px;
  margin: 0;
  width: var(--ou-item);
  height: var(--ou-item);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;settings-wrap&quot;&amp;gt;
  &amp;lt;div class=&quot;settings-orbit circle-overlap&quot; style=&quot;--n:5&quot; tabindex=&quot;0&quot;&amp;gt;
    &amp;lt;button type=&quot;button&quot; aria-label=&quot;Settings&quot;&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;a href=&quot;#profile&quot; aria-label=&quot;Profile&quot;&amp;gt;P&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#billing&quot; aria-label=&quot;Billing&quot;&amp;gt;B&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#team&quot; aria-label=&quot;Team&quot;&amp;gt;T&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#api&quot; aria-label=&quot;API&quot;&amp;gt;K&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#out&quot; aria-label=&quot;Sign out&quot;&amp;gt;×&amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;minimal-full-page-all-four-patterns-&quot;&gt;Minimal full page (all four patterns)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
  &amp;lt;title&amp;gt;overlapui cascades&amp;lt;/title&amp;gt;
  &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.5/runtime.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;style&amp;gt;
    @import((*) from overlapui)

    @ou-root()
    @avatar-overlap(.avatar-overlap)
    @card-overlap(.card-overlap)
    @img-overlap(.img-overlap)
    @circle-overlap(.circle-overlap)
    @ou-reduced-motion()

    :root {
      --ou-ring: #0f172a;
      --ou-center-bg: #6366f1;
      @ou-avatar-size(44px, -14px)
      @ou-circle-size(48px, 80px)
    }

    body {
      margin: 0;
      min-height: 100vh;
      display: grid;
      gap: 48px;
      place-content: center;
      padding: 32px 16px;
      background: #0b1220;
      font-family: system-ui, sans-serif;
      color: #e2e8f0;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;ul class=&quot;avatar-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
    &amp;lt;li data-alph=&quot;g&quot;&amp;gt;G&amp;lt;/li&amp;gt;
    &amp;lt;li data-alph=&quot;h&quot;&amp;gt;H&amp;lt;/li&amp;gt;
    &amp;lt;li data-alph=&quot;i&quot;&amp;gt;I&amp;lt;/li&amp;gt;
    &amp;lt;li data-alph=&quot;j&quot;&amp;gt;J&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;

  &amp;lt;div class=&quot;card-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;img src=&quot;https://picsum.photos/96?random=1&quot; alt=&quot;&quot;&amp;gt;
      &amp;lt;h3&amp;gt;Title&amp;lt;/h3&amp;gt;
      subtitle
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;img src=&quot;https://picsum.photos/96?random=2&quot; alt=&quot;&quot;&amp;gt;
      &amp;lt;h3&amp;gt;Title&amp;lt;/h3&amp;gt;
      subtitle
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;img-overlap&quot; tabindex=&quot;0&quot;&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=4&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=5&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;img src=&quot;https://picsum.photos/96?random=6&quot; alt=&quot;&quot;&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;div class=&quot;circle-overlap&quot; style=&quot;--n:4&quot; tabindex=&quot;0&quot;&amp;gt;
    &amp;lt;button type=&quot;button&quot; aria-label=&quot;Menu&quot;&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;a href=&quot;#a&quot; aria-label=&quot;A&quot;&amp;gt;A&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#b&quot; aria-label=&quot;B&quot;&amp;gt;B&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#c&quot; aria-label=&quot;C&quot;&amp;gt;C&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;#d&quot; aria-label=&quot;D&quot;&amp;gt;D&amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;responsive-behavior-tokens-not-new-markup-&quot;&gt;Responsive behavior (tokens, not new markup)&lt;/h3&gt;
&lt;p&gt;Scale density at breakpoints:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (max-width: 520px) {
  :root {
    @ou-avatar-size(36px, -11px)
    @ou-img-size(72px, -36px)
    @ou-circle-size(40px, 64px)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep horizontal stacks from breaking the page:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;body { overflow-x: hidden; }

.center-block {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 100%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;accessibility-part-of-the-contract-&quot;&gt;Accessibility (part of the contract)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep &lt;strong&gt;real labels or names&lt;/strong&gt; in the DOM for avatars; faces may hide the decorative letter with &lt;code&gt;font-size: 0&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put &lt;code&gt;aria-label&lt;/code&gt; on the circle center and on each satellite control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add &lt;code&gt;tabindex=&quot;0&quot;&lt;/code&gt; on stack roots so &lt;code&gt;:focus-within&lt;/code&gt; matches hover.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call &lt;code&gt;@ou-reduced-motion()&lt;/code&gt; so transitions drop out when the user prefers reduced motion.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-the-pieces-fit-checklist-&quot;&gt;How the pieces fit (checklist)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Choose a cascade: avatar, card, image, or circle.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set density with &lt;code&gt;--ou-*&lt;/code&gt; / size helpers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mark the stack root for keyboard open when needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Theme only through tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compile with FSCSS or use the runtime; the browser runs CSS.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Overlapped UI is a &lt;strong&gt;rest/open contract&lt;/strong&gt; plus a &lt;strong&gt;token layer&lt;/strong&gt;. overlapui is that contract as an FSCSS module.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reference&quot;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/overlapui.fscss&quot;&gt;overlapui.fscss&lt;/a&gt; — module source, mixins, and tokens&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29968/designing-overlapped-cascaded-ui-with-overlapui</guid>
<pubDate>Tue, 06 Oct 2026 09:44:22 +0000</pubDate>
</item>
<item>
<title>FSCSS 1.2.5 ships property shorthands: size, px, inset-x, ratio-fit()</title>
<link>https://coderlegion.com/29321/fscss-1-2-5-ships-property-shorthands-size-px-inset-x-ratio-fit</link>
<description>&lt;p&gt;A new FSCSS release. This one's aimed at the pairs everyone keeps typing out by hand:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* before */
width: 32px;
height: 32px;

/* after */
size: 32px;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same deal for the other repeat offenders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  px: 16px;      /* padding-left + padding-right */
  py: 14px;      /* padding-top + padding-bottom */
  mx: auto;      /* margin-left + margin-right */
  max-w: 360px;
}

.overlay-side {
  pos: absolute;
  inset-x: 45px; /* left + right only, top/bottom untouched */
  top: 0;
  h: 4px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;inset-x&lt;/code&gt; one is the one I actually wanted to exist. &lt;code&gt;inset: auto 45px&lt;/code&gt; sets all four sides, there's no native way to say &quot;just left and right&quot; without writing &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; separately. Now there is.&lt;/p&gt;
&lt;p&gt;There's also &lt;code&gt;ratio-fit()&lt;/code&gt; for the fill-container-keep-aspect-ratio pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.media &amp;gt; img {
  inset: ratio-fit(16 / 9);
  obj-fit: cover;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compiles ahead of time, no runtime polyfill needed for any of it if you're already compiling with the CLI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install fscss@1.2.5
fscss input.fscss output.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also ships &lt;code&gt;stack&lt;/code&gt;, &lt;code&gt;hstack&lt;/code&gt;, &lt;code&gt;center&lt;/code&gt;, &lt;code&gt;auto-fit&lt;/code&gt;, &lt;code&gt;truncate&lt;/code&gt;, &lt;code&gt;line-clamp&lt;/code&gt;, border/background shorthands, and the usual aliases (&lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;fw&lt;/code&gt;, &lt;code&gt;jc&lt;/code&gt;, &lt;code&gt;ai&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt;, &lt;code&gt;shadow&lt;/code&gt;...). Full map's linked below. There's a real UI using a chunk of these together, a dual-phone budget app template combining this with modules like &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;st-core@v2 charts&lt;/a&gt;, if you want to see it in context rather than isolated snippets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss/releases/tag/v1.2.5&quot;&gt;https://github.com/Figsh/xfscss/releases/tag/v1.2.5&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Shorthands reference:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/shorthands&quot;&gt;https://fscss.devtem.org/shorthands&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Budget app template:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/templates/budget-app&quot;&gt;https://github.com/fscss-ttr/st-core.fscss/tree/main/templates/budget-app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Anyone using &lt;code&gt;size&lt;/code&gt;/&lt;code&gt;px&lt;/code&gt;/&lt;code&gt;inset-x&lt;/code&gt; already hit any edge cases worth flagging?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29321/fscss-1-2-5-ships-property-shorthands-size-px-inset-x-ratio-fit</guid>
<pubDate>Wed, 30 Sep 2026 23:48:53 +0000</pubDate>
</item>
<item>
<title>FSCSS auto-loop: percentage keyframes</title>
<link>https://coderlegion.com/28689/fscss-auto-loop-percentage-keyframes</link>
<description>&lt;p&gt;This snippet builds a &lt;strong&gt;stepped “processing 0% → 99% → done”&lt;/strong&gt; animation by &lt;strong&gt;looping an array into &lt;code&gt;@keyframes&lt;/code&gt; percentages&lt;/strong&gt; — not a JS timer.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;setup-1&quot;&gt;Setup&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;npm install -g fscss@latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;VS Code: &lt;strong&gt;FSCSS&lt;/strong&gt; language support (&lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS Support&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;File: &lt;code&gt;my-first-fscss.fscss&lt;/code&gt; → compile or runtime-load&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;source-your-idea-tightened-&quot;&gt;Source (your idea, tightened)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h2::after {
  content: &quot;...&quot;;
  animation: count 10s steps(100) forwards;
}

/* 1, 2, … 99  (auto list for keyframe stops) */
@arr numlist[count(99)]

@define counter(start:ing..., process:ing..., end:ed){`
  0% {
    content: &quot;@use(start)0%&quot;;
  }
  @arr.numlist[]% {
    content: &quot;@use(process)@arr.numlist[]%&quot;;
  }
  100% {
    content: &quot;@use(end)&quot;;
  }
`}

@keyframes count {
  @counter(Processing..., Processing..., Processed)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-auto-loop-is-here&quot;&gt;What “auto-loop” is here&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Piece &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;count(99)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Builds a numeric list used as &lt;strong&gt;1–99&lt;/strong&gt; (engine: &lt;code&gt;count(n)&lt;/code&gt; / &lt;code&gt;count(n, 1)&lt;/code&gt; → index list).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@arr numlist[…]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Stores that list.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@arr.numlist[]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Auto-index&lt;/strong&gt;: expand once per item.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@arr.numlist[]%&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Each item becomes a &lt;strong&gt;keyframe selector&lt;/strong&gt;: &lt;code&gt;1%&lt;/code&gt;, &lt;code&gt;2%&lt;/code&gt;, … &lt;code&gt;99%&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@define counter(…)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Template for the keyframe body; &lt;code&gt;@use(start|process|end)&lt;/code&gt; fills labels.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@counter(…)&lt;/code&gt; inside &lt;code&gt;@keyframes&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Injects the whole ladder into &lt;code&gt;count&lt;/code&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So the loop is &lt;strong&gt;compile-time expansion&lt;/strong&gt;, not &lt;code&gt;for&lt;/code&gt; in the browser.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;breakdown&quot;&gt;Breakdown&lt;/h3&gt;
&lt;h4 id=&quot;1-target&quot;&gt;1. Target&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h2::after {
  content: &quot;...&quot;;
  animation: count 10s steps(100) forwards;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Animates &lt;strong&gt;&lt;code&gt;content&lt;/code&gt;&lt;/strong&gt; on the pseudo-element.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;steps(100)&lt;/code&gt; ≈ one visual step per percent over 10s.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;forwards&lt;/code&gt; keeps the final &lt;code&gt;Processed&lt;/code&gt; text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-index-array&quot;&gt;2. Index array&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr numlist[count(99)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same family as modules indexes: a list long enough to drive stops. Here each value is also the &lt;strong&gt;percentage number&lt;/strong&gt; in the output.&lt;/p&gt;
&lt;h4 id=&quot;3-reusable-keyframe-block&quot;&gt;3. Reusable keyframe block&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@define counter(start:…, process:…, end:…){` … `}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;0%&lt;/code&gt;&lt;/strong&gt; — intro label + &lt;code&gt;0%&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@arr.numlist[]%&lt;/code&gt;&lt;/strong&gt; — for each &lt;code&gt;n&lt;/code&gt; in &lt;code&gt;numlist&lt;/code&gt;, emit:&lt;ul&gt;
&lt;li&gt;selector: &lt;code&gt;n%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content: &quot;&amp;lt;process&amp;gt;n%&quot;&lt;/code&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;100%&lt;/code&gt;&lt;/strong&gt; — final label only (e.g. &lt;code&gt;Processed&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-wire-into-a-named-animation&quot;&gt;4. Wire into a named animation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@keyframes count {
  @counter(Processing..., Processing..., Processed)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expands to a long &lt;code&gt;@keyframes count { … }&lt;/code&gt; with ~100 content stops.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shape-of-compiled-css-illustrative-&quot;&gt;Shape of compiled CSS (illustrative)&lt;/h3&gt;
&lt;p&gt;Exact quoting depends on the compiler; structure is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;h2::after {
  content: &quot;...&quot;;
  animation: count 10s steps(100) forwards;
}

@keyframes count {
  0% {
    content: &quot;Processing...0%&quot;;
  }
  1% {
    content: &quot;Processing...1%&quot;;
  }
  2% {
    content: &quot;Processing...2%&quot;;
  }
  /* … */
  99% {
    content: &quot;Processing...99%&quot;;
  }
  100% {
    content: &quot;Processed&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the auto-loop: &lt;strong&gt;one array walk → many keyframe rules&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-html&quot;&gt;Example HTML&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;FSCSS auto-loop counter&amp;lt;/title&amp;gt;
  &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.4/runtime.min.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;link type=&quot;text/fscss&quot; href=&quot;my-first-fscss.fscss&quot;&amp;gt;
  &amp;lt;!-- or: &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;my-first-fscss.css&quot;&amp;gt; after CLI --&amp;gt;
  &amp;lt;style&amp;gt;
    body {
      font-family: system-ui, sans-serif;
      padding: 2rem;
      background: #0f172a;
      color: #e2e8f0;
    }
    h2 {
      font-size: 1.25rem;
      font-weight: 600;
    }
    h2::after {
      margin-left: 0.35em;
      color: #38bdf8;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h2&amp;gt;Status&amp;lt;/h2&amp;gt;
  &amp;lt;!-- ::after shows Processing...0% → … → Processed --&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;compile&quot;&gt;Compile&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fscss my-first-fscss.fscss my-first-fscss.css
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mental-model&quot;&gt;Mental model&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;count(99)  →  list of stops
@arr.x[]%  →  “for each item, emit item% { … }”
@define    →  reuse labels (start / process / end)
@keyframes →  holds the expanded ladder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Auto-loop&lt;/strong&gt; = array auto-index used as &lt;strong&gt;keyframe percentages&lt;/strong&gt; (or selectors elsewhere), so one definition drives N rules.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;variants&quot;&gt;Variants&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Progress label only: change &lt;code&gt;@use(process)&lt;/code&gt; strings.  &lt;/li&gt;
&lt;li&gt;Fewer steps: &lt;code&gt;count(20)&lt;/code&gt; + shorter animation / &lt;code&gt;steps(20)&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;Same pattern for staggered selectors: &lt;code&gt;*Emails are not allowed*[] { animation-delay: … }&lt;/code&gt; (as in arrays docs).&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28689/fscss-auto-loop-percentage-keyframes</guid>
<pubDate>Sat, 26 Sep 2026 07:18:17 +0000</pubDate>
</item>
<item>
<title>How to Use st-core.fscss with Svelte</title>
<link>https://coderlegion.com/27953/how-to-use-st-core-fscss-with-svelte</link>
<description>&lt;p&gt;st-core.fscss draws charts with pure CSS, &lt;code&gt;clip-path&lt;/code&gt; polygons plus custom properties, no JS chart library, no SVG, no canvas. Svelte doesn't need to draw anything for it. It just needs to own the data and reactively set &lt;code&gt;--st-p1&lt;/code&gt;, &lt;code&gt;--st-p2&lt;/code&gt;, and so on whenever your series changes. Wanted to write up the actual integration because getting the responsibilities split correctly took me a minute the first time through.&lt;/p&gt;
&lt;h4 id=&quot;why-compiled-mode-not-the-runtime-cdn&quot;&gt;Why compiled mode, not the runtime CDN&lt;/h4&gt;
&lt;p&gt;Svelte already has a build pipeline. Shipping the FSCSS runtime to a Svelte app just to expand a few chart mixins into static CSS is unnecessary work for the browser to do on every load. The runtime CDN script is fine for a CodePen or a quick static HTML demo, but for an actual Svelte or SvelteKit app you compile once at build time and import plain CSS.&lt;/p&gt;
&lt;p&gt;Three layers, each with one job:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;Job&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;FSCSS CLI &lt;/td&gt;&lt;td&gt;Expand &lt;code&gt;@st-chart-fill&lt;/code&gt; / &lt;code&gt;@st-chart-line&lt;/code&gt; into static CSS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Svelte &lt;/td&gt;&lt;td&gt;Reactive &lt;code&gt;data[]&lt;/code&gt; → &lt;code&gt;--st-pN&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Browser CSS &lt;/td&gt;&lt;td&gt;Paint, plus optional &lt;code&gt;clip-path&lt;/code&gt; transitions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Svelte state  →  style=&quot;--st-p1: 30%; --st-p2: 45%; …&quot;
                      ↓
              compiled st-core.css
                      ↓
                 chart on screen
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;1-write-the-fscss-entry-file&quot;&gt;1. Write the FSCSS entry file&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;src/styles/st-core.fscss&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core@v2)

@st-root()

/* Length at compile time = number of polygon stops */
@arr chartData[0, 0, 0, 0, 0, 0, 0]

@st-chart-fill(.chart-fill, chartData)
@st-chart-line(.chart-line, chartData)

.chart {
  @st-chart-points(chartData)
  position: relative;
  width: 100%;
  height: 220px;
  background: var(--st-surface);
  border-radius: var(--st-radius-lg);
  overflow: hidden;
}

.chart-fill,
.chart-line {
  transition: clip-path 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The zeros are placeholders, they only matter for their count. Svelte overwrites the real Y values through CSS variables at runtime. What you can't change without recompiling is the array's &lt;strong&gt;length&lt;/strong&gt; — match it to your series (7 days of data → 7 entries).&lt;/p&gt;
&lt;h4 id=&quot;2-compile-it-as-part-of-your-build&quot;&gt;2. Compile it as part of your build&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -D fscss@latest
npx fscss src/styles/st-core.fscss src/styles/st-core.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wire it into &lt;code&gt;package.json&lt;/code&gt; so it runs automatically before every build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;build:st-core&quot;: &quot;fscss src/styles/st-core.fscss src/styles/st-core.css&quot;,
    &quot;prebuild&quot;: &quot;npm run build:st-core&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then import the compiled CSS once, wherever makes sense for your app (root layout, &lt;code&gt;app.css&lt;/code&gt;, or the chart's parent component):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import '../styles/st-core.css';
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-map-your-data-to---st-pn&quot;&gt;3. Map your data to &lt;code&gt;--st-pN&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Chart geometry in st-core is top-down, the same way CSS itself is. A high value needs to sit near &lt;code&gt;0%&lt;/code&gt;, not &lt;code&gt;100%&lt;/code&gt;. So every point gets inverted:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;--st-pᵢ = (100 - humanValue) %
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A score of &lt;code&gt;90&lt;/code&gt; ends up &lt;code&gt;10%&lt;/code&gt; from the top, which is exactly where you'd expect it visually.&lt;/p&gt;
&lt;p&gt;Here's a reusable &lt;code&gt;Chart.svelte&lt;/code&gt; that does that mapping reactively, Svelte 4 style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  /** @type {number[]} 0–100, higher = taller */
  export let data = [];
  export let height = '200px';
  export let color = '#9d7eff';

  $: cssVars = [
    ...data.map((value, index) =&amp;gt; `--st-p${index + 1}: ${100 - value}%`),
    `--st-accent: ${color}`,
    `height: ${height}`
  ].join('; ');
&amp;lt;/script&amp;gt;

&amp;lt;div class=&quot;chart&quot; style={cssVars}&amp;gt;
  &amp;lt;div class=&quot;chart-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the Svelte 5 version, using &lt;code&gt;$props&lt;/code&gt; / &lt;code&gt;$derived&lt;/code&gt; instead of the old export-let / reactive-statement pattern:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  let { data = [], height = '200px', color = '#9d7eff' } = $props();

  let cssVars = $derived(
    [
      ...data.map((value, index) =&amp;gt; `--st-p${index + 1}: ${100 - value}%`),
      `--st-accent: ${color}`,
      `height: ${height}`
    ].join('; ')
  );
&amp;lt;/script&amp;gt;

&amp;lt;div class=&quot;chart&quot; style={cssVars}&amp;gt;
  &amp;lt;div class=&quot;chart-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both do the exact same job, just with whichever reactivity API your Svelte version uses.&lt;/p&gt;
&lt;h4 id=&quot;4-use-it-like-any-other-component&quot;&gt;4. Use it like any other component&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-svelte&quot;&gt;&amp;lt;script&amp;gt;
  import './styles/st-core.css';
  import Chart from './lib/Chart.svelte';

  let weekly = [45, 70, 30, 90, 60, 80, 50];

  function randomize() {
    weekly = Array.from({ length: 7 }, () =&amp;gt; Math.floor(Math.random() * 80) + 10);
  }
&amp;lt;/script&amp;gt;

&amp;lt;Chart data={weekly} color=&quot;#4fffb0&quot; height=&quot;250px&quot; &amp;gt;

&amp;lt;!-- Svelte 4 --&amp;gt;
&amp;lt;button type=&quot;button&quot; on:click={randomize}&amp;gt;Randomize&amp;lt;/button&amp;gt;

&amp;lt;!-- Svelte 5 --&amp;gt;
&amp;lt;!-- &amp;lt;button type=&quot;button&quot; onclick={randomize}&amp;gt;Randomize&amp;lt;/button&amp;gt; --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reassigning &lt;code&gt;weekly&lt;/code&gt; recomputes &lt;code&gt;cssVars&lt;/code&gt;, the browser applies the new custom properties, and the compiled CSS's &lt;code&gt;clip-path&lt;/code&gt; transition animates the shape change. No chart library re-rendering, no SVG path recalculation, no manual DOM diffing on your end.&lt;/p&gt;
&lt;h4 id=&quot;fixed-length-vs-changing-length&quot;&gt;Fixed length vs changing length&lt;/h4&gt;
&lt;p&gt;Because geometry is baked in at compile time, the array length is the one thing you can't change on the fly:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Situation &lt;/th&gt;&lt;th&gt;What to do&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Always exactly &lt;strong&gt;N&lt;/strong&gt; points &lt;/td&gt;&lt;td&gt;Compile &lt;code&gt;@arr chartData&lt;/code&gt; with &lt;strong&gt;N&lt;/strong&gt; zeros, always pass exactly &lt;strong&gt;N&lt;/strong&gt; values from Svelte&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Length varies, up to a max of &lt;strong&gt;M&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Compile &lt;strong&gt;M&lt;/strong&gt; zeros, then &lt;code&gt;data.slice(0, M)&lt;/code&gt; and pad shorter series with &lt;code&gt;0&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you compile a template expecting 5 points and hand it 8 values from Svelte, the extra &lt;code&gt;--st-p6&lt;/code&gt;, &lt;code&gt;--st-p7&lt;/code&gt;, &lt;code&gt;--st-p8&lt;/code&gt; have nowhere to go, there's no polygon stop for them until you recompile with a longer array.&lt;/p&gt;
&lt;h4 id=&quot;what-not-to-do-in-production&quot;&gt;What not to do in production&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Fine for a demo, avoid as your only path in a real Svelte app --&amp;gt;
&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.3/runtime.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Runtime mode is for CodePen and quick static HTML, not for a build pipeline that already exists. See the plain &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration/html&quot;&gt;HTML integration sample&lt;/a&gt; if that's what you actually want.&lt;/p&gt;
&lt;h4 id=&quot;the-mental-model-condensed&quot;&gt;The mental model, condensed&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@st-chart-points&lt;/code&gt;, fill, and line define the &lt;strong&gt;structure&lt;/strong&gt; and only ever read &lt;code&gt;--st-p*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Svelte's only job is to &lt;strong&gt;replace&lt;/strong&gt; those variables when the data changes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition: clip-path&lt;/code&gt; gives you motion for free, no animation library needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nothing here is Svelte-specific past step 3, the same pattern maps directly to Vue, React, or vanilla JS, only the reactivity layer changes. Contributions for other frameworks welcome under &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration&quot;&gt;&lt;code&gt;integration/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Svelte sample:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/tree/main/integration/svelte&quot;&gt;integration/svelte&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27953/how-to-use-st-core-fscss-with-svelte</guid>
<pubDate>Mon, 21 Sep 2026 12:45:40 +0000</pubDate>
</item>
<item>
<title>Finally Shipped st-core.fscss v2 — CSS Charts Without the 8-Point Ceiling</title>
<link>https://coderlegion.com/27679/finally-shipped-st-core-fscss-v2-css-charts-without-the-8-point-ceiling</link>
<description>&lt;p&gt;For a while, st-core.fscss charts had one annoying limit: every line, fill, and dot was locked to exactly 8 data points. &lt;code&gt;--st-p1&lt;/code&gt; through &lt;code&gt;--st-p8&lt;/code&gt;, hardcoded. Want to plot 12 weeks of revenue? Tough. Want 5 points for a quick demo? You'd pad the array with zeros and hope nobody noticed.&lt;/p&gt;
&lt;p&gt;v2 gets rid of that.&lt;/p&gt;
&lt;h4 id=&quot;the-core-change-array-arr-&quot;&gt;The core change: Array (&lt;code&gt;@arr&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Instead of passing 8 raw numbers into &lt;code&gt;@st-chart-points&lt;/code&gt;, you now declare a named array of any length:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr myData[50, 10, 97, 35, 66, 50, 80, 54, 70, 60]

@st-chart-fill(.chart-fill, myData)
@st-chart-line(.chart-line, myData)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;st-core reads the array length at compile time, works out even X-spacing across the chart width, and builds the &lt;code&gt;clip-path: polygon()&lt;/code&gt; shape from however many points you gave it. 5 points, 12 points, 50 points, doesn't matter. No more counting to 8.&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha-that-took-me-a-minute-to-internalize&quot;&gt;The gotcha that took me a minute to internalize&lt;/h4&gt;
&lt;p&gt;Here's the part that actually tripped me up while building the multi-series examples: &lt;code&gt;@st-chart-line(selector, array)&lt;/code&gt; and &lt;code&gt;@st-chart-fill(selector, array)&lt;/code&gt; aren't what set the data on an element. They declare the renderer for that selector, the array argument just tells it how many points to expect.&lt;/p&gt;
&lt;p&gt;The thing that actually writes &lt;code&gt;--st-p1&lt;/code&gt;…&lt;code&gt;--st-p{n}&lt;/code&gt; onto an element is &lt;code&gt;@st-chart-points(array)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That distinction matters the moment you have more than one series on a chart. In a multi-line setup, if &lt;code&gt;.line-b&lt;/code&gt; doesn't call &lt;code&gt;@st-chart-points(seriesB)&lt;/code&gt; itself, it silently inherits whatever the parent container set, meaning it renders the wrong dataset. Looks fine at a glance, wrong shape in practice. Caught it while building a 3-series dashboard card where two lines were quietly drawing the same data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.fill-b {
  @st-chart-points(seriesB)   /* required, not optional */
  opacity: 0.3;
  --st-accent: #4fffb0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once that clicked, the mental model got simple: renderer declarations are structural (once per selector), point data is per-element (as many times as you have distinct series).&lt;/p&gt;
&lt;h4 id=&quot;how-it-feels-to-ship&quot;&gt;How it feels to ship&lt;/h4&gt;
&lt;p&gt;Pure CSS charting was already a fun constraint to design inside of, no JS runtime, no SVG, no canvas. Making the dataset size flexible removes the last thing that made it feel like a toy rather than something you'd reach for on a real dashboard. Compiled output is still under a kilobyte. Still zero hydration.&lt;/p&gt;
&lt;p&gt;Docs and full examples are up in the repo, including the legacy v1 reference for anyone still on fixed 8-point charts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;github.com/fscss-ttr/st-core.fscss&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open-source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;with ♥️&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss/blob/main/st-core%40v2.fscss&quot;&gt;source code&lt;/a&gt; is totally math notebook 😅&lt;/p&gt;
&lt;p&gt;Have been writing and debugging from morning till night, and finally released it. The usage is now as simple as CSS.&lt;/p&gt;
&lt;p&gt;😊&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27679/finally-shipped-st-core-fscss-v2-css-charts-without-the-8-point-ceiling</guid>
<pubDate>Sat, 19 Sep 2026 01:08:17 +0000</pubDate>
</item>
<item>
<title>Building Circular UI with circle-progress.fscss (No JavaScript)</title>
<link>https://coderlegion.com/27092/building-circular-ui-with-circle-progress-fscss-no-javascript</link>
<description>&lt;p&gt;Circular progress, pie charts, and step rings usually mean SVG path math or a chart library.&lt;br&gt;
&lt;strong&gt;circle-progress.fscss&lt;/strong&gt; does it with pure CSS — &lt;code&gt;conic-gradient&lt;/code&gt;, masks, and FSCSS &lt;code&gt;@define&lt;/code&gt; helpers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/Circle-progress.fscss&quot;&gt;Figsh/Circle-progress.fscss&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/u3yctwx3zjyki8kezc2l.jpg&quot; alt=&quot;Showcase grid — ring, pie, steps, nested&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-you-get&quot;&gt;What you get&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Component &lt;/th&gt;&lt;th&gt;Define&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Progress ring &lt;/td&gt;&lt;td&gt;&lt;code&gt;@circle-progress&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Solid pie &lt;/td&gt;&lt;td&gt;&lt;code&gt;@circle-pie&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multi-slice pie &lt;/td&gt;&lt;td&gt;&lt;code&gt;@circle-pie-multi&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Steps ring &lt;/td&gt;&lt;td&gt;&lt;code&gt;@circle-steps&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nested rings &lt;/td&gt;&lt;td&gt;Compose by nesting elements&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All driven by CSS variables. Change &lt;code&gt;--progress-value&lt;/code&gt; and the arc follows.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;setup-2&quot;&gt;Setup&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Option A — runtime (quick / prototyping)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.1/runtime.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Option B — CLI compile (production)&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;It works well with &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS vscode extention&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g fscss
fscss styles.fscss styles.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import the module:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from circle-progress)

@progress-root()
@circle-progress(.progress-circle)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full sample page:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/Circle-progress.fscss/blob/main/samples/example.html&quot;&gt;samples/example.html&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-progress-ring&quot;&gt;1. Progress ring&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xyniobclqzivp7pwc619.jpg&quot; alt=&quot;Green 72% progress ring&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;progress-circle p72&quot;&amp;gt;72%&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.p72 { @progress-range(72) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under the hood: a &lt;code&gt;conic-gradient&lt;/code&gt; draws the arc, a radial mask cuts the hole, and a small tip marker rotates with the value.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-color-override&quot;&gt;2. Color override&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kvovpuo4y94ms69x0z71.jpg&quot; alt=&quot;Blue 41% ring&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.p41 {
  @progress-range(41)
  --progress-color-arc: #4d9fff;
  --progress-color-glow: rgba(77, 159, 255, 0.45);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same component — only tokens change.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/f839tpmxd3x6h2guoapq.jpg&quot; alt=&quot;Amber 88% ring&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.p88 {
  @progress-range(88)
  --progress-color-arc: #ffb830;
  --progress-color-glow: rgba(255, 184, 48, 0.4);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-pie-slice&quot;&gt;3. Pie slice&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/mf1n05au58qgg8q12rmo.jpg&quot; alt=&quot;Solid 30% pie&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;circle-pie p30&quot;&amp;gt;30%&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@circle-pie(.circle-pie)
.p30 { @progress-range(30) --progress-color-arc: #ff5070; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No mask — the conic fill is the whole disk.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-multi-pie&quot;&gt;4. Multi pie&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/y5j9mdr3u7kj5s3shbjd.jpg&quot; alt=&quot;4-slice multi pie&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;circle-pie-multi pie-a&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@circle-pie-multi(.circle-pie-multi)

.pie-a {
  --pie-a: 30;
  --pie-b: 25;
  --pie-c: 20;
  --pie-d: 15;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four slices from four percentage tokens.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-steps-ring&quot;&gt;5. Steps ring&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kf4fdn5b2sku8i69g0gh.jpg&quot; alt=&quot;Steps 5/8&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;circle-steps steps-5&quot;&amp;gt;5/8&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@circle-steps(.circle-steps)
.steps-5 { @steps-done(5) @steps-total(8) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5wn0qso3ir24p8yls4ka.jpg&quot; alt=&quot;Steps 3/6 violet&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.steps-3 {
  @steps-done(3)
  @steps-total(6)
  --progress-color-arc: #9d7eff;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-nested-rings&quot;&gt;6. Nested rings&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0t2ioeqg26kwji0w9gta.jpg&quot; alt=&quot;Nested green outer + blue inner&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;progress-circle p72&quot; style=&quot;--progress-size:200px&quot;&amp;gt;
  &amp;lt;div class=&quot;progress-circle p41&quot;
       style=&quot;--progress-size:120px;--progress-stroke:10px&quot;&amp;gt;
    41
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same define, different size/stroke on the child — composition without new APIs.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mental-model-1&quot;&gt;Mental model&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tokens&lt;/strong&gt; — &lt;code&gt;@progress-root()&lt;/code&gt; sets size, stroke, colors, value.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shape&lt;/strong&gt; — one &lt;code&gt;@define&lt;/code&gt; per pattern (ring, pie, steps).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value&lt;/strong&gt; — &lt;code&gt;@progress-range(n)&lt;/code&gt; or &lt;code&gt;--progress-value&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme&lt;/strong&gt; — override &lt;code&gt;--progress-color-arc&lt;/code&gt; / &lt;code&gt;--progress-color-glow&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No SVG paths. No chart library. Change a variable, the circle updates.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-to-use-runtime-vs-cli&quot;&gt;When to use runtime vs CLI&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Use case &lt;/th&gt;&lt;th&gt;Prefer&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Demos, CodePen, quick tests &lt;/td&gt;&lt;td&gt;CDN &lt;strong&gt;runtime&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production, static deploy &lt;/td&gt;&lt;td&gt;&lt;strong&gt;CLI&lt;/strong&gt; compile to plain CSS&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Runtime keeps FSCSS syntax in the page. CLI ships zero-runtime CSS.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;try-it-1&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;Clone or copy from the repo and open the sample:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/Circle-progress.fscss&quot;&gt;github.com/Figsh/Circle-progress.fscss&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Sample: &lt;code&gt;samples/example.html&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Swap values, nest rings, add a pie — same module, different tokens.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Built with FSCSS &lt;code&gt;@define&lt;/code&gt; · pure CSS circular UI&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/27092/building-circular-ui-with-circle-progress-fscss-no-javascript</guid>
<pubDate>Sun, 13 Sep 2026 23:02:30 +0000</pubDate>
</item>
<item>
<title>FSCSS v1.2.0 is out: two clean entry points, full backward compatibility, and inline</title>
<link>https://coderlegion.com/26253/fscss-v1-2-0-is-out-two-clean-entry-points-full-backward-compatibility-and-inline</link>
<description>&lt;p&gt;FSCSS is open source, built in the open, and free for anyone to use, extend, or pick apart. That's the whole point of it. Every release is meant to make it easier for other developers to build with, not harder. v1.2.0 is the clearest example of that yet.&lt;/p&gt;
&lt;h3 id=&quot;what-shipped&quot;&gt;What shipped&lt;/h3&gt;
&lt;p&gt;Previous versions of FSCSS scattered their browser-facing files across the package: &lt;code&gt;/e/exec.js&lt;/code&gt;, &lt;code&gt;/exec.js&lt;/code&gt;, &lt;code&gt;/xfscss.js&lt;/code&gt;, &lt;code&gt;/xfscss.min.js&lt;/code&gt;, and more. Depending on what you needed, you'd end up guessing which file was the right one to load.&lt;/p&gt;
&lt;p&gt;v1.2.0 consolidates all of that into two public entry points:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- Auto-run, for plain HTML pages --&amp;gt;
&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.0/runtime.min.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Manual control, for tooling and modules
import xfscss from &quot;https://cdn.jsdelivr.net/npm/fscss@1.2.0/esm.js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;runtime.js&lt;/code&gt; auto-scans the page and compiles everything on load, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; blocks, &lt;code&gt;&amp;lt;link type=&quot;fscss&quot;&amp;gt;&lt;/code&gt; sources, and now inline &lt;code&gt;style&lt;/code&gt; attributes too. &lt;code&gt;esm.js&lt;/code&gt; never auto-runs and never touches page loading, since it's meant for using FSCSS as a tool: an FSCSS-to-CSS converter, a build step, or any pipeline where you decide what gets processed and when.&lt;/p&gt;
&lt;p&gt;Use one or the other. Never both.&lt;/p&gt;
&lt;h3 id=&quot;nothing-breaks&quot;&gt;Nothing breaks&lt;/h3&gt;
&lt;p&gt;This is the part worth repeating: the npm package and CLI are completely unchanged, and every method that worked in the 1.1.x line still works exactly the same way in 1.2.0. &lt;code&gt;@define&lt;/code&gt;, &lt;code&gt;@fun&lt;/code&gt;, &lt;code&gt;@event&lt;/code&gt;, &lt;code&gt;@arr&lt;/code&gt;, &lt;code&gt;pattern()&lt;/code&gt;, all of it. The consolidation only touches the browser entry-point files. If you're compiling ahead of time with the CLI, this release changes nothing for you.&lt;/p&gt;
&lt;h3 id=&quot;inline-styles-now-go-through-the-full-pipeline&quot;&gt;Inline styles now go through the full pipeline&lt;/h3&gt;
&lt;p&gt;This is the headline feature. From v1.2.0, &lt;code&gt;style=&quot;...&quot;&lt;/code&gt; attributes are compiled on boot the same way &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; blocks are:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;style&amp;gt;
  @define btn-style(bg: #636EE7){
    padding: 10px 20px;
    color: color-mix(#dddddd, @use(bg));
    background: @use(bg);
    border: 2px solid;
    border-radius: 25px;
    font-weight: 700;
  }
&amp;lt;/style&amp;gt;

&amp;lt;button style=&quot;@btn-style(#1a2a4f)&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&amp;lt;button style=&quot;@btn-style(#f00f00)&quot;&amp;gt;Search&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7y11c39cjxmto7kaj3b8.jpg&quot; alt=&quot;FSCSS output&quot;&gt;&lt;/p&gt;
&lt;p&gt;Combined with &lt;code&gt;xfscss.process()&lt;/code&gt;, that means an inline mixin call can be recompiled at runtime and swapped straight back into the element, no rewritten CSS, no framework state:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const update = async (btn) =&amp;gt; {
  const newStyle = await xfscss.process(&quot;@btn-style(@arr.colors!.randint)&quot;);
  btn.setAttribute(&quot;style&quot;, newStyle);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full walkthrough of this: including a detail (method declared inline stay resolvable even after their declaration is cleaned out of the attribute), if you want to go deeper: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/figsh/fscss-v120-lets-you-write-mixins-directly-in-style-heres-why-thats-a-bigger-deal-than-2a31&quot;&gt;FSCSS v1.2.0 lets you write mixins directly in style=&quot;...&quot;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters-for-an-open-source-tool&quot;&gt;Why this matters for an open-source tool&lt;/h3&gt;
&lt;p&gt;None of this is about adding complexity for its own sake. FSCSS exists to cut down on repetitive CSS and give developers capabilities plain CSS doesn't have natively, and every release should make that easier to reach for, not harder to keep up with. Two entry points instead of a pile of internal files. Full compatibility instead of a migration guide. A feature that makes the JS API and the language work together instead of sitting side by side.&lt;/p&gt;
&lt;p&gt;Open source only stays useful if the people using it can trust an upgrade won't cost them a rewrite. That's the bar this release was held to.&lt;/p&gt;
&lt;h3 id=&quot;try-it-2&quot;&gt;try it&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install fscss@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.2.0/runtime.min.js&quot; async&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Vscode: &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;API reference: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/api&quot;&gt;fscss.devtem.org/api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss&quot;&gt;github.com/Figsh/xfscss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NPM: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;npmjs.com/package/fscss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bug reports, feature requests, and pull requests are all welcome on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Figsh/xfscss&quot;&gt;GitHub&lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26253/fscss-v1-2-0-is-out-two-clean-entry-points-full-backward-compatibility-and-inline</guid>
<pubDate>Sat, 05 Sep 2026 08:26:35 +0000</pubDate>
</item>
<item>
<title>Why Readable CSS Matters More as AI Writes More of It</title>
<link>https://coderlegion.com/25391/why-readable-css-matters-more-as-ai-writes-more-of-it</link>
<description>&lt;p&gt;Most CSS tooling is built around one assumption: the person writing the stylesheet remembers the exact name of the thing they want. A mixin, a utility class, a function signature. Get the name slightly wrong and nothing happens, or worse, something wrong happens silently.&lt;/p&gt;
&lt;p&gt;That assumption breaks down in two places at once right now: beginners, and &lt;em&gt;AI-generated CSS&lt;/em&gt;. Neither is good at remembering exact names. Both are good at describing what they want.&lt;/p&gt;
&lt;h3 id=&quot;the-exact-name-problem&quot;&gt;The exact-name problem&lt;/h3&gt;
&lt;p&gt;Say you want a rounded button with a purple background. In most systems you need to already know the answer is &lt;code&gt;.btn-primary&lt;/code&gt;, or &lt;code&gt;mixin button-primary()&lt;/code&gt;, or whatever your team decided six months ago. If you don't know it, you either guess, search the codebase, or write it from scratch again, adding one more near-duplicate button style to the pile.&lt;/p&gt;
&lt;p&gt;A beginner doesn't have that memorized name yet. &lt;em&gt;An AI model generating CSS&lt;/em&gt; doesn't reliably have it either, it will confidently produce a plausible-looking class name that doesn't exist in your project, because plausible-looking is exactly what language models are good at, and existing in your specific codebase is not something they can check without help.&lt;/p&gt;
&lt;p&gt;Both groups are, in a real sense, working from intent rather than from an exact API surface. The tooling just hasn't caught up to that.&lt;/p&gt;
&lt;h3 id=&quot;matching-by-description-instead-of-by-name&quot;&gt;Matching by description instead of by name&lt;/h3&gt;
&lt;p&gt;FSCSS's &lt;code&gt;pattern()&lt;/code&gt; method takes the opposite approach. Instead of calling a style by its exact name, you describe it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.6: &quot;rounded primary button&quot;, `
  border: none;
  border-radius: 8px;
  padding: 10px 20px;
  background: #764ba2;
  color: white;
  font-weight: 600;
`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and later, anywhere in the stylesheet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.btn {
  rounded primary button
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The phrase doesn't need to match the description exactly. It needs to be close enough, measured by a similarity score against a &lt;em&gt;threshold&lt;/em&gt; you set. Someone who writes &lt;code&gt;rounded purple button&lt;/code&gt; instead of &lt;code&gt;rounded primary button&lt;/code&gt; still gets the right result, because the words overlap enough to clear the bar.&lt;/p&gt;
&lt;p&gt;This is a small syntax change with a real consequence: the thing being memorized shifts from an exact identifier to a rough description. Rough descriptions are the thing beginners already know how to produce, and the thing AI models are already extremely good at producing.&lt;/p&gt;
&lt;h3 id=&quot;why-this-helps-beginners-specifically&quot;&gt;Why this helps beginners specifically&lt;/h3&gt;
&lt;p&gt;A new developer doesn't need to browse a component library, search for &lt;code&gt;card&lt;/code&gt;, &lt;code&gt;panel&lt;/code&gt;, &lt;code&gt;surface&lt;/code&gt;, and &lt;code&gt;box&lt;/code&gt; to guess which one your project picked, and read the source to confirm what it actually does. They write what they want in the words they'd use to ask a coworker for it. If a close-enough pattern exists, it resolves. If it doesn't, they get to write real CSS instead, which they were going to have to learn eventually anyway.&lt;/p&gt;
&lt;p&gt;This doesn't remove the need to learn CSS. It removes the need to memorize your project's specific naming conventions before you can be productive in it.&lt;/p&gt;
&lt;h3 id=&quot;why-this-helps-ai-generated-css-specifically&quot;&gt;Why this helps AI-generated CSS specifically&lt;/h3&gt;
&lt;p&gt;When a model generates &lt;code&gt;@my-exact-mixin-name()&lt;/code&gt;, it is guessing at a name it has no way to verify against your actual codebase. When it generates &lt;code&gt;soft elevated card with subtle shadow&lt;/code&gt; inside a selector, it is describing an outcome, and the matching happens against patterns that actually exist in your project. A wrong guess at an exact name fails silently or breaks the build. A wrong guess at a description either matches something close enough to be useful, or matches nothing and falls through as literal, harmless text, which is a far safer failure mode.&lt;/p&gt;
&lt;p&gt;This is also why the earlier comment on the &lt;code&gt;pattern()&lt;/code&gt; announcement post, about this being especially useful for AI-generated CSS, is worth taking seriously. Fuzzy matching by description is closer to how models actually operate than exact-name lookup ever was.&lt;/p&gt;
&lt;h3 id=&quot;the-tradeoff-you-have-to-manage&quot;&gt;The tradeoff you have to manage&lt;/h3&gt;
&lt;p&gt;None of this is free. A pattern library is only as reliable as the distinctness of its descriptions. If two patterns describe closely related things in overlapping words, for example &lt;code&gt;white button with purple label&lt;/code&gt; next to &lt;code&gt;solid purple primary button with white label&lt;/code&gt;, a given phrase can score close on both, and the result depends on small wording differences rather than clear intent. This came up directly while building &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/patterns.fscss&quot;&gt;patterns.fscss&lt;/a&gt;: an early draft had two button patterns worded closely enough that matching between them was genuinely ambiguous.&lt;/p&gt;
&lt;p&gt;The fix isn't a smarter matching algorithm. It's writing descriptions the way you'd want a beginner, or a model, to actually phrase the request, and keeping related-but-different patterns lexically distinct from each other. That discipline matters more as a pattern library grows, not less.&lt;/p&gt;
&lt;h3 id=&quot;where-to-start&quot;&gt;Where to start&lt;/h3&gt;
&lt;p&gt;If you want to see this in practice, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/patterns.fscss&quot;&gt;patterns.fscss&lt;/a&gt; is a small demo module of cards, buttons, hover effects, and animations, all callable by description. Fork it, read how the descriptions are worded, and use it as a template for your own project's pattern.&lt;/p&gt;
&lt;p&gt;Pattern Feature: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/pattern&quot;&gt;fscss.devtem.org/pattern&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;NPM: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;npmjs.com/package/fscss&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Visual studio code extention: &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS support&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/25391/why-readable-css-matters-more-as-ai-writes-more-of-it</guid>
<pubDate>Tue, 25 Aug 2026 22:13:05 +0000</pubDate>
</item>
<item>
<title>Understanding exec() in FSCSS — Two Completely Different Jobs</title>
<link>https://coderlegion.com/25158/understanding-exec-in-fscss-two-completely-different-jobs</link>
<description>&lt;h3 id=&quot;understanding-exec-in-fscss&quot;&gt;Understanding &lt;code&gt;exec()&lt;/code&gt; in FSCSS&lt;/h3&gt;
&lt;p&gt;In FSCSS, &lt;strong&gt;&lt;code&gt;exec()&lt;/code&gt;&lt;/strong&gt; serves two completely different purposes depending on where you write it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inside your stylesheet: as a utility / debugging function  &lt;/li&gt;
&lt;li&gt;In JavaScript / HTML: to load and compile the styles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s break both down clearly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-inside-stylesheets-debugging-amp-logging&quot;&gt;1. Inside Stylesheets: Debugging &amp;amp; Logging&lt;/h3&gt;
&lt;p&gt;When used inside an &lt;code&gt;@event&lt;/code&gt; block or after processing an array, &lt;code&gt;exec()&lt;/code&gt; lets you run small JavaScript helpers directly from your CSS.&lt;br&gt;
Its main job here is &lt;strong&gt;printing debug information&lt;/strong&gt; to the browser console so you can check if your logic is working.&lt;/p&gt;
&lt;h4 id=&quot;console-logging&quot;&gt;Console Logging&lt;/h4&gt;
&lt;p&gt;You can pass flags like &lt;code&gt;_log&lt;/code&gt;, &lt;code&gt;_warn&lt;/code&gt;, &lt;code&gt;_info&lt;/code&gt;, or &lt;code&gt;_error&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@event scaleSize(e) {
  if e &amp;gt;= 8 {
    return| num(8*2)em exec(_log, &quot;Scaling to 16em for optimal readability&quot;)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;checking-array-data&quot;&gt;Checking Array Data&lt;/h4&gt;
&lt;p&gt;You can also inspect the current values inside an FSCSS array:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@arr fruits[ apple, orange, banana ]

/* Prints: apple orange banana straight to your browser console */
exec(_log, &quot;@arr.fruits&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is extremely useful while building complex logic with &lt;code&gt;@event&lt;/code&gt;, &lt;code&gt;@arr&lt;/code&gt;, or calculated values.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-in-javascript-amp-imports-compilation-engine&quot;&gt;2. In JavaScript &amp;amp; Imports: Compilation Engine&lt;/h3&gt;
&lt;p&gt;Outside the stylesheet, &lt;code&gt;exec&lt;/code&gt; is the &lt;strong&gt;core engine&lt;/strong&gt; that compiles and runs FSCSS source code.&lt;/p&gt;
&lt;h4 id=&quot;cdn-initialization-html-&quot;&gt;CDN Initialization (HTML)&lt;/h4&gt;
&lt;p&gt;The compiler file loaded via CDN is named &lt;code&gt;exec.min.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.1.25/exec.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
  /* Tells the engine to execute the initialization for a module */
  @import(exec(_init circle-progress))
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;programmatic-javascript-usage&quot;&gt;Programmatic JavaScript Usage&lt;/h4&gt;
&lt;p&gt;You can also import and control the compiler directly from JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { exec } from &quot;https://cdn.jsdelivr.net/npm/fscss@1.1.25/e/xfscss.min.js&quot;;

new exec({
  type: &quot;URL&quot;,
  content: &quot;styles.fscss&quot;,
  onSuccess: &quot;Styles loaded successfully!&quot;,
  onError: &quot;Oops, style compilation failed!&quot;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives you full control over loading, success messages, and error handling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-summary&quot;&gt;Quick Summary&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Location              &lt;/th&gt;&lt;th&gt;Purpose                              &lt;/th&gt;&lt;th&gt;Common Use Case&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Inside &lt;code&gt;.fscss&lt;/code&gt;       &lt;/td&gt;&lt;td&gt;Debugging &amp;amp; console logging          &lt;/td&gt;&lt;td&gt;Check &lt;code&gt;@event&lt;/code&gt; logic or array values&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;In HTML / JavaScript  &lt;/td&gt;&lt;td&gt;Compile and run FSCSS                &lt;/td&gt;&lt;td&gt;Load styles or initialize modules&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two different jobs. Same name.&lt;br&gt;
Once you know the context, &lt;code&gt;exec()&lt;/code&gt; becomes much clearer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Useful links&lt;/strong&gt;  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docs → &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org&quot;&gt;fscss.devtem.org&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;vscode extention &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;FSCSS Support&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;npm → &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;npmjs.com/package/fscss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/25158/understanding-exec-in-fscss-two-completely-different-jobs</guid>
<pubDate>Sat, 22 Aug 2026 22:45:11 +0000</pubDate>
</item>
<item>
<title>pattern() in FSCSS 1.1.25</title>
<link>https://coderlegion.com/24831/pattern-in-fscss-1-1-25</link>
<description>&lt;p&gt;FSCSS v1.1.25 ships a new core method: &lt;strong&gt;&lt;code&gt;pattern()&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You describe what you want in plain language, and FSCSS matches the closest pattern at compile time.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.5: &quot;Hello World Card&quot;, `
  border-radius: 12px;
  padding: 24px;
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
`)

.card {
  hello world card
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compiles to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.card {
  border-radius: 12px;
  padding: 24px;
  background: linear-gradient(135deg, #667eea, #764ba2);
  color: #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,0.25);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice &lt;code&gt;.card&lt;/code&gt; doesn't call anything — it just contains a phrase close enough to &lt;code&gt;&quot;Hello World card&quot;&lt;/code&gt; to trigger the match.&lt;/p&gt;
&lt;h3 id=&quot;why-this-exists&quot;&gt;Why this exists&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@define&lt;/code&gt; is precise but rigid. You call it by exact name, with exact arguments. That's great for a library you built yourself, but it breaks down in a few real situations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Design handoffs.&lt;/strong&gt; A designer or junior dev writes &lt;code&gt;dark card with rounded corners&lt;/code&gt; inside a rule instead of hunting through your define library for the right call signature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-generated markup.&lt;/strong&gt; Tools that generate FSCSS from natural-language prompts can emit descriptive phrases instead of needing to know your exact mixin names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast prototyping.&lt;/strong&gt; You don't always remember what you named something six months ago. &lt;code&gt;pattern()&lt;/code&gt; lets close-enough phrasing still resolve correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;pattern()&lt;/code&gt; doesn't replace other variable methods — it sits next to it, for cases where flexibility matters more than precision.&lt;/p&gt;
&lt;h3 id=&quot;the-syntax&quot;&gt;The syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(threshold: &quot;description&quot;, `
  css to inject
`)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;threshold&lt;/strong&gt; — a number from 0 to 1. The minimum similarity score a phrase must reach to trigger this pattern. Omit it and it defaults to &lt;code&gt;1&lt;/code&gt; (near-exact match only).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;description&lt;/strong&gt; — the plain-language phrase you're matching against.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;css&lt;/strong&gt; — what gets injected when a phrase matches. Use backticks so the CSS can freely contain single or double quotes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Threshold uses a colon, deliberately — &lt;code&gt;0.5:&lt;/code&gt; reads as a labeled parameter, distinct from a plain comma-separated argument. It's there so the number's &lt;em&gt;purpose&lt;/em&gt; is visually obvious to anyone reading your stylesheet.&lt;/p&gt;
&lt;h3 id=&quot;how-matching-works&quot;&gt;How matching works&lt;/h3&gt;
&lt;p&gt;Every phrase in your code gets compared against every pattern description using similarity scoring (Dice coefficient, if you're curious — it compares word overlap between the phrase and the description). The pattern with the &lt;strong&gt;highest score that also clears its own threshold&lt;/strong&gt; wins.&lt;/p&gt;
&lt;p&gt;That means:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.8: &quot;dark background and light color&quot;, `
  background: #212121;
  color: #ffffff;
`)

.chip {
  style dark background and light color
}

.tooltip {
  dark background light color
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both &lt;code&gt;.chip&lt;/code&gt; and &lt;code&gt;.tooltip&lt;/code&gt; resolve to the same CSS, even though neither phrase matches the description word-for-word. That's the point — you're not looking up a name, you're describing an outcome.&lt;/p&gt;
&lt;h4 id=&quot;tuning-the-threshold&quot;&gt;Tuning the threshold&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High threshold (0.8–1)&lt;/strong&gt; → strict. Good for short, generic descriptions where you don't want accidental matches (&lt;code&gt;&quot;button&quot;&lt;/code&gt; alone would be too easy to trigger by accident).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low threshold (0.4–0.6)&lt;/strong&gt; → forgiving. Good for longer, more specific descriptions where a few words might vary but the intent is clearly the same.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If two patterns could plausibly match the same phrase, the highest-scoring one wins — so keep descriptions distinct from each other, especially at lower thresholds.&lt;/p&gt;
&lt;h3 id=&quot;property-patterns-vs-block-patterns&quot;&gt;Property patterns vs. block patterns&lt;/h3&gt;
&lt;p&gt;Same split as &lt;code&gt;@define&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Property patterns&lt;/strong&gt; inject a group of declarations inside a rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.6: &quot;rounded primary button&quot;, `
  border: none;
  border-radius: 8px;
  padding: 10px 20px;
  background: #ffffff;
  color: #764ba2;
  font-weight: 600;
  cursor: pointer;
`)

.btn {
  rounded primary button
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Block patterns&lt;/strong&gt; inject full structures — keyframes, media queries, whole at-rules — and can stand alone outside any selector:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.7: &quot;Animated keyframe for spin&quot;, `
  @keyframes spin {
    0% { transform: rotate(0); }
    100% { transform: rotate(360deg); }
  }
`)

An Animated keyframe for spin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;content-injection-example&quot;&gt;Content injection example&lt;/h3&gt;
&lt;p&gt;Because the CSS argument is just a string, &lt;code&gt;pattern()&lt;/code&gt; isn't limited to properties — it can inject anything valid at that point in your stylesheet, including pseudo-element content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;pattern(0.9: &quot;hello world heading&quot;, `h1:before{content: 'Hello World';}`)

  hello world heading

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-s-shipped-in-v1-1-25&quot;&gt;What's shipped in v1.1.25&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CDN&lt;/li&gt;
&lt;li&gt;API&lt;/li&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;VSCode Extention&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;try-it-3&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pattern()&lt;/code&gt; is available on &lt;strong&gt;v1.1.25&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/pattern&quot;&gt;fscss.devtem.org/pattern&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/24831/pattern-in-fscss-1-1-25</guid>
<pubDate>Tue, 18 Aug 2026 12:18:46 +0000</pubDate>
</item>
<item>
<title>I built pure CSS 3D dice cube module: FSCSS</title>
<link>https://coderlegion.com/24274/i-built-pure-css-3d-dice-cube-module-fscss</link>
<description>&lt;p&gt;&lt;code&gt;cube.fscss&lt;/code&gt; is a lightweight, pure CSS 3D cube system built on &lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/&quot;&gt;FSCSS&lt;/a&gt;. It renders a spinnable 6-face cube using nothing but perspective, transform-style: preserve-3d, and translateZ/rotateX/rotateY — no JavaScript, no 3D library, no canvas.&lt;/p&gt;
&lt;p&gt;Here's example whole thing rendered live, nothing but &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
@import((*) from cube)

@cube-root()
@cube-wrapper(.my-cube)
@cube-face(.my-cube div)
@cube-face-front(.my-cube .f1)
@cube-face-back(.my-cube .f2)
@cube-face-left(.my-cube .f3)
@cube-face-right(.my-cube .f4)
@cube-face-top(.my-cube .f5)
@cube-face-bottom(.my-cube .f6)
@cube-micro-dice(.my-cube .cube)

.my-cube {
  @cube-size(80px)
  transform: rotateY(50deg) rotateX(30deg);
  animation: spin 5s linear infinite;
}

@keyframes spin {
  0%   { transform: rotateX(20deg) rotateY(40deg); }
  100% { transform: rotateX(380deg) rotateY(400deg); }
}
&amp;lt;/style&amp;gt;

&amp;lt;div class=&quot;my-cube&quot;&amp;gt;
  &amp;lt;div class=&quot;cube f1 dice-1&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;cube f2 dice-2&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;cube f3 dice-3&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;cube f4 dice-4&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;cube f5 dice-5&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;cube f6 dice-6&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No dice PNGs. No SVG paths. No canvas draw calls. Every pip is a &lt;code&gt;radial-gradient&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-build-this&quot;&gt;Why build this&lt;/h3&gt;
&lt;p&gt;Already had a chart/dashboard module (&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;st-core.fscss&lt;/a&gt;) built on &lt;code&gt;clip-path&lt;/code&gt; polygons — no JS charting library, just CSS custom properties driving the shape. I wanted to see how far that &quot;CSS does the visual math&quot; philosophy could stretch into actual 3D space. A cube felt like the right next test: real &lt;code&gt;perspective&lt;/code&gt;, real &lt;code&gt;preserve-3d&lt;/code&gt;, real face geometry — not a flat illusion.&lt;/p&gt;
&lt;h3 id=&quot;how-the-geometry-works&quot;&gt;How the geometry works&lt;/h3&gt;
&lt;p&gt;A cube is 6 absolutely-positioned faces sharing one parent set to &lt;code&gt;transform-style: preserve-3d&lt;/code&gt;. Each face gets pushed out from the center by half the cube's edge length, then rotated to face outward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;front  → translateZ(half)
back   → rotateY(180deg) translateZ(half)
top    → rotateX(90deg)  translateZ(half)
bottom → rotateX(-90deg) translateZ(half)
right  → rotateY(90deg)  translateZ(half)
left   → rotateY(-90deg) translateZ(half)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire cube's dimensions live in two CSS variables: &lt;code&gt;--cube-size&lt;/code&gt; and &lt;code&gt;--cube-half&lt;/code&gt; (always size ÷ 2). Resize the whole thing with one call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.my-cube {
  @cube-size(150px)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Faces, offsets, and dice dot size all scale together — no manually recalculating six &lt;code&gt;translateZ&lt;/code&gt; values by hand.&lt;/p&gt;
&lt;h3 id=&quot;dice-pips-no-font-glyphs&quot;&gt;Dice pips, no font glyphs&lt;/h3&gt;
&lt;p&gt;I originally reached for Unicode dice characters (⚀⚁⚂⚃⚄⚅) for the pips, but font rendering for those glyphs is wildly inconsistent across platforms — some Android/Chrome stacks show them thin, outlined, or as tofu boxes. Stacked &lt;code&gt;radial-gradient&lt;/code&gt;s solved it instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.dice-3 {
  background-image:
    radial-gradient(circle var(--cube-dot-size) at 25% 25%, var(--cube-dot-color) 99%, transparent 100%),
    radial-gradient(circle var(--cube-dot-size) at 50% 50%, var(--cube-dot-color) 99%, transparent 100%),
    radial-gradient(circle var(--cube-dot-size) at 75% 75%, var(--cube-dot-color) 99%, transparent 100%);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Font-independent, crisp at any size, and the dot radius is itself a variable — so it scales automatically with &lt;code&gt;@cube-size()&lt;/code&gt; instead of overflowing on a big cube or vanishing on a small one.&lt;/p&gt;
&lt;h3 id=&quot;it-s-not-just-dice&quot;&gt;It's not just dice&lt;/h3&gt;
&lt;p&gt;Faces and face &lt;em&gt;content&lt;/em&gt; are decoupled on purpose. The face mixins only handle position — what renders inside is a separate &quot;micro&quot; you apply on top. Right now there are eight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cube-micro-fire&lt;/code&gt; — pulsing gradient-text fire&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-matrix&lt;/code&gt; — scrolling digital rain&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-neon&lt;/code&gt; — pulsing cyan/magenta glow border&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-float&lt;/code&gt; — subtle bounce, stacks with anything&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-glass&lt;/code&gt; — frosted &lt;code&gt;backdrop-filter&lt;/code&gt; panel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-holo&lt;/code&gt; — holographic shifting gradient text&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-glitch&lt;/code&gt; — RGB-split glitch shift&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cube-micro-dice&lt;/code&gt; — the pips above&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mix and match per face on the same cube:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@cube-micro-fire(.my-cube .f1)
@cube-micro-matrix(.my-cube .f2)
@cube-micro-neon(.my-cube .f3)
@cube-micro-glass(.my-cube .f5)
@cube-micro-holo(.my-cube .f6)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;try-it-4&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;CDN, drop-in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt; 
  @import((*) from cube)
  /* your mixins here */
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or compile ahead of time with the CLI for a plain &lt;code&gt;.css&lt;/code&gt; file, zero runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -g fscss
fscss style.fscss style.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full docs, token reference, and more examples: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/cube.fscss&quot;&gt;github.com/fscss-ttr/cube.fscss&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you build something with it — I'd love to see. And if you hit issue, open an issue, it's open-source. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;help us star the repo so more dev can discover it. 🙏&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/24274/i-built-pure-css-3d-dice-cube-module-fscss</guid>
<pubDate>Fri, 07 Aug 2026 22:17:10 +0000</pubDate>
</item>
<item>
<title>Designing Stats UIs with FSCSS and st-core</title>
<link>https://coderlegion.com/13949/designing-stats-uis-with-fscss-and-st-core</link>
<description>&lt;p&gt;If you've ever built a stats dashboard, you know the drill — a bunch of divs, a charting library, a pile of JavaScript, and somehow it still looks off.&lt;/p&gt;
&lt;p&gt;What if you could drop a chart into a page with just CSS? No canvas, no SVG, no JS dependency. That's what &lt;strong&gt;FSCSS&lt;/strong&gt; and its &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org/libraries&quot;&gt;st-core&lt;/a&gt;&lt;/strong&gt; plugin are about.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-fscss-&quot;&gt;What is FSCSS?&lt;/h3&gt;
&lt;p&gt;FSCSS (Figured Shorthand CSS) is a lightweight, modern CSS preprocessor that simplifies your styling workflow with intuitive shorthand syntax. It allows you to write cleaner, more maintainable stylesheets with less code.&lt;/p&gt;
&lt;p&gt;CDN: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/fscss@1.1.24/exec.min.js&quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NPM: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;https://www.npmjs.com/package/fscss&lt;/a&gt; &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;meet-st-core&quot;&gt;Meet st-core&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;st-core&lt;/strong&gt; is an FSCSS plugin built specifically for stats UIs. It gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A design token system (colors, spacing, radius) via &lt;code&gt;@st-root()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A pure-CSS area/line chart driven by data points&lt;/li&gt;
&lt;li&gt;Stat cards with value, label, and delta&lt;/li&gt;
&lt;li&gt;Category bar fills&lt;/li&gt;
&lt;li&gt;Layout helpers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Import it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;building-a-simple-stats-card-chart&quot;&gt;Building a Simple Stats Card + Chart&lt;/h3&gt;
&lt;p&gt;Here's a minimal working example with FSCSS via CDN.&lt;/p&gt;
&lt;h4 id=&quot;html&quot;&gt;HTML&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;stat-card&quot;&amp;gt;
  &amp;lt;div class=&quot;st-stat-label&quot;&amp;gt;Revenue&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;st-stat-value&quot;&amp;gt;$1,326.03&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;st-stat-delta up&quot;&amp;gt;+5.1% vs last week&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&quot;chart&quot;&amp;gt;
  &amp;lt;div class=&quot;chart-fill&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-line&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;chart-dot&quot; data-point=&quot;$45.07&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;css-fscss-&quot;&gt;CSS (FSCSS)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import((*) from st-core)

/* 1. Init design tokens */
@st-root()
@st-container(body)

/* 2. Register components */
@st-stat-card(.stat-card)
@st-chart-fill(.chart-fill)
@st-chart-line(.chart-line)
@st-chart-dot(.chart-dot, 84, 66)

/* 3. Feed data to the chart */
.chart {
  position: relative;
  width: 300px;
  height: 200px;
  border-radius: 25px;
  overflow: hidden;

  @st-chart-points(20, 35, 33, 30, 48, 35, 66, 37)
}

/* 4. Optional tweaks */
.chart-line {
  @st-chart-line-width(2px)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@st-chart-points&lt;/code&gt; sets eight y-values. The fill and line are drawn automatically via &lt;code&gt;clip-path&lt;/code&gt;. The dot lands at &lt;code&gt;(84%, 66%)&lt;/code&gt; — no JS, no math on your end.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-the-chart-works&quot;&gt;How the Chart Works&lt;/h3&gt;
&lt;p&gt;Under the hood, &lt;code&gt;@st-chart-points(v1, v2 ... v8)&lt;/code&gt; maps each value to a CSS custom property (&lt;code&gt;--st-p1&lt;/code&gt; through &lt;code&gt;--st-p8&lt;/code&gt;). The fill and line components consume those as &lt;code&gt;clip-path: polygon(...)&lt;/code&gt; coordinates.&lt;/p&gt;
&lt;p&gt;It's pure CSS geometry. No canvas, no SVG, no runtime calculations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-few-use-cases&quot;&gt;A Few Use Cases&lt;/h3&gt;
&lt;p&gt;st-core is a good fit when you want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lightweight dashboards&lt;/strong&gt; — personal finance trackers, health stats, workout logs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin panels&lt;/strong&gt; — a quick KPI card row without pulling in a full charting lib&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prototypes&lt;/strong&gt; — get a chart on screen in minutes, style it later&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FSCSS projects&lt;/strong&gt; — consistent tokens and components across your whole app&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;category-bars-too&quot;&gt;Category Bars Too&lt;/h3&gt;
&lt;p&gt;Need a simple progress bar? There's a plugin for that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@st-cat-bar-fill(.bar, 65)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Renders a bar filled to 65%. Good for breakdowns, budgets, category splits.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;worth-trying&quot;&gt;Worth Trying&lt;/h3&gt;
&lt;p&gt;The whole thing is one import. No build step, no npm install for the components, no chart configuration object to wrestle with.&lt;/p&gt;
&lt;p&gt;If you're already using FSCSS, st-core slots right in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org&quot;&gt;FSCSS docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/fscss-ttr/st-core.fscss&quot;&gt;st-core on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;FSCSS on npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/13949/designing-stats-uis-with-fscss-and-st-core</guid>
<pubDate>Sat, 04 Apr 2026 13:14:55 +0000</pubDate>
</item>
<item>
<title>Write CSS in camelCase or snake_case — FSCSS</title>
<link>https://coderlegion.com/13606/write-css-in-camelcase-or-snakecase-fscss</link>
<description>&lt;p&gt;CSS naming hasn't evolved.&lt;/p&gt;
&lt;p&gt;Everything else in development has — JavaScript, Python, APIs — but CSS is still stuck in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;background-color: #111;
border-radius: 8px;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hyphens. Everywhere.&lt;/p&gt;
&lt;p&gt;Meanwhile, the rest of your stack uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;camelCase (JavaScript)&lt;/li&gt;
&lt;li&gt;snake_case (Python, databases)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That mismatch isn't just annoying — it's inefficient.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-problem-context-switching&quot;&gt;The Real Problem: Context Switching&lt;/h3&gt;
&lt;p&gt;Every time you write CSS, your brain does a silent conversion:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;backgroundColor&lt;/code&gt; → &lt;code&gt;background-color&lt;/code&gt;&lt;br&gt;
&lt;code&gt;user_name&lt;/code&gt; → &lt;code&gt;user-name&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That's friction. And friction slows teams down.&lt;/p&gt;
&lt;p&gt;Even modern systems still enforce kebab-case in CSS — meaning developers constantly translate between conventions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fscss-changes-the-rule&quot;&gt;FSCSS Changes the Rule&lt;/h3&gt;
&lt;p&gt;With &lt;strong&gt;FSCSS&lt;/strong&gt; (Figured Shorthand Cascading Style Sheets), you don't adapt to CSS...&lt;/p&gt;
&lt;p&gt;CSS adapts to you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;write-css-like-your-actual-code&quot;&gt;Write CSS Like Your Actual Code&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;camelCase — Frontend-friendly:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@import((*) from camelCase)

.card {
  backgroundColor: #111;
  borderRadius: 12px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;snake_case — Backend-friendly:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@import((*) from snake_case)

.card {
  background_color: #111;
  border_radius: 12px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Both compile to:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;background-color: #111;
border-radius: 12px;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No hacks. No runtime overhead. Just valid CSS.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters-more-than-it-looks&quot;&gt;Why This Matters More Than It Looks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Your brain stops translating&lt;/strong&gt;&lt;br&gt;
Stick to one mental model - whichever matches how you already think.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Your stack becomes consistent&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React / Next.js → camelCase&lt;/li&gt;
&lt;li&gt;Django / APIs → snake_case&lt;/li&gt;
&lt;li&gt;CSS → now matches both&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Teams move faster&lt;/strong&gt;&lt;br&gt;
No more &lt;em&gt;&quot;Is it borderRadius or border-radius again?&quot;&lt;/em&gt; you already know, because you're already using it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;it-goes-beyond-naming&quot;&gt;It Goes Beyond Naming&lt;/h3&gt;
&lt;p&gt;FSCSS also lets you build reusable styling logic and more:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@define glassEffect() {
  backdrop-filter: blur(12px);
  background: rgba(255, 255, 255, 0.2);
}

.hero {
  @glassEffect()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your CSS starts behaving more like a system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;learn-more&quot;&gt;Learn More&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Snake case guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://figsh.devtem.org/how_to_use_snakecase_fscss&quot;&gt;https://figsh.devtem.org/how_to_use_snakecase_fscss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Camel case guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://figsh.devtem.org/how_to_use_camelcase_fscss&quot;&gt;https://figsh.devtem.org/how_to_use_camelcase_fscss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Kebab-case&lt;/strong&gt; made sense when CSS was isolated. But modern development is interconnected.&lt;/p&gt;
&lt;p&gt;FSCSS brings CSS into alignment with the rest of your stack and removes one of the most subtle productivity killers in frontend development.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13606/write-css-in-camelcase-or-snakecase-fscss</guid>
<pubDate>Sat, 28 Mar 2026 14:52:45 +0000</pubDate>
</item>
<item>
<title>⚡ Creating Custom Functions in FSCSS (Yes, CSS Can Do This)</title>
<link>https://coderlegion.com/13593/creating-custom-functions-in-fscss-yes-css-can-do-this</link>
<description>&lt;p&gt;One thing about FSCSS is that you can define your own functions… inside your styles. Here’s a simple example of how that works. &lt;/p&gt;
&lt;h3 id=&quot;custom-randint-function&quot;&gt;Custom randint Function&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@define randint(array){
@random(@use(array)) 
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What this function does:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Takes an array as an argument.&lt;/li&gt;
&lt;li&gt;Returns a random value directly from that array.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Defining Arrays&lt;br&gt;
First, we set up our possible values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;@arr sizes[ 100px, 150px, 200px ] 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Using the Function&lt;br&gt;
We call our custom function and assign it to a variable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;$height: @randint(@arr.sizes); 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;$height&lt;/code&gt; dynamically becomes either &lt;code&gt;100px&lt;/code&gt;, &lt;code&gt;150px&lt;/code&gt;, or &lt;code&gt;200px&lt;/code&gt; (randomly selected at compile).&lt;/p&gt;
&lt;h3 id=&quot;why-this-is-interesting&quot;&gt;Why This Is Interesting&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reusable Logic: You can create complex logic once and use it across your entire design system.&lt;/li&gt;
&lt;li&gt;Dynamic Design Systems: Build layouts that feel organic and varied rather than static and repetitive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-usage&quot;&gt;Example Usage&lt;/h3&gt;
&lt;p&gt;Applying that logic to a class is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scss&quot;&gt;.box { 
  height: $height; 
  width: $height; 
  background: #2c5364; 
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every compile can produce a slightly different UI!&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;https://www.npmjs.com/package/fscss&lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13593/creating-custom-functions-in-fscss-yes-css-can-do-this</guid>
<pubDate>Sat, 28 Mar 2026 11:33:44 +0000</pubDate>
</item>
<item>
<title>FSCSS introduction</title>
<link>https://coderlegion.com/13592/fscss-introduction</link>
<description>&lt;p&gt;FSCSS (Figured Shorthand Cascading Style Sheet) is a styling approach designed to simplify CSS by using shorthand techniques. It allows developers to write more concise and efficient styles, reducing redundancy and improving readability.&lt;/p&gt;
&lt;h4 id=&quot;key-features-of-fscss-&quot;&gt;Key Features of FSCSS:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shorthand Methods&lt;/strong&gt;: Techniques like &lt;code&gt;%2&lt;/code&gt; allow multiple properties to share the same value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapid Tag Formatting (rtF)&lt;/strong&gt;: Enables quick styling of multiple elements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Animation Efficiency&lt;/strong&gt;: Provides a compact way to define animations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Script Integration&lt;/strong&gt;: Works with FSCSS-specific scripts to streamline styling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FSCSS is particularly useful for projects that require clean and maintainable CSS structures.&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/fscss&quot;&gt;NPM package&lt;/a&gt; &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Figsh.fscss&quot;&gt;vscode extention&lt;/a&gt;.&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://figsh.devtem.org/projects&quot;&gt;Projects series&lt;/a&gt; &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://fscss.devtem.org&quot;&gt;Website&lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13592/fscss-introduction</guid>
<pubDate>Sat, 28 Mar 2026 11:28:57 +0000</pubDate>
</item>
</channel>
</rss>