Responsive helpers #9

Description

@minimaldesign

Assessment

Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

Effort: M.

What changed since the original plan

  • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
  • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
  • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
  • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

Measured cost

Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

ScopeRules addedMinifiedGzipBrotli
Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
+ spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
+ spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
+ spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
+ spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

Decision: the trimmed set (522 rules, +16% minified)

Ship display helpers, text-align variants, and spacing variants restricted to:

  • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
  • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
  • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

The trims are principled, not just thrifty:

  • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
  • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
  • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

Implementation plan

1. Base display helpers (new)

In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

.display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

2. Responsive variants

  • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
  • Covered helpers:
    • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
    • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
    • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
  • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
  • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
  • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

3. Generator

Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

4. Explicitly still out of scope

Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

5. Docs & verification

  • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
  • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
  • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
  • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    Responsive helpers #9

    Description

    @minimaldesign

    Assessment

    Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

    Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

    Effort: M.

    What changed since the original plan

    • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
    • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
    • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
    • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

    Measured cost

    Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

    Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

    ScopeRules addedMinifiedGzipBrotli
    Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
    + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
    + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
    + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
    + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

    Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

    Decision: the trimmed set (522 rules, +16% minified)

    Ship display helpers, text-align variants, and spacing variants restricted to:

    • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
    • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
    • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

    The trims are principled, not just thrifty:

    • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
    • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
    • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

    Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

    Implementation plan

    1. Base display helpers (new)

    In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

    .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

    Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

    2. Responsive variants

    • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
    • Covered helpers:
      • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
      • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
      • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
    • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
    • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
    • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

    3. Generator

    Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

    4. Explicitly still out of scope

    Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

    5. Docs & verification

    • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
    • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
    • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
    • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

    Metadata

    Metadata

    Assignees

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      Responsive helpers #9

      Description

      @minimaldesign

      Assessment

      Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

      Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

      Effort: M.

      What changed since the original plan

      • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
      • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
      • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
      • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

      Measured cost

      Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

      Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

      ScopeRules addedMinifiedGzipBrotli
      Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
      + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
      + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
      + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
      + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

      Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

      Decision: the trimmed set (522 rules, +16% minified)

      Ship display helpers, text-align variants, and spacing variants restricted to:

      • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
      • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
      • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

      The trims are principled, not just thrifty:

      • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
      • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
      • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

      Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

      Implementation plan

      1. Base display helpers (new)

      In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

      .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

      Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

      2. Responsive variants

      • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
      • Covered helpers:
        • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
        • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
        • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
      • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
      • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
      • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

      3. Generator

      Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

      4. Explicitly still out of scope

      Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

      5. Docs & verification

      • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
      • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
      • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
      • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

      Metadata

      Metadata

      Assignees

      Labels

      enhancementNew feature or request

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        Responsive helpers #9

        Description

        @minimaldesign

        Assessment

        Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

        Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

        Effort: M.

        What changed since the original plan

        • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
        • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
        • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
        • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

        Measured cost

        Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

        Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

        ScopeRules addedMinifiedGzipBrotli
        Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
        + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
        + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
        + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
        + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

        Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

        Decision: the trimmed set (522 rules, +16% minified)

        Ship display helpers, text-align variants, and spacing variants restricted to:

        • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
        • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
        • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

        The trims are principled, not just thrifty:

        • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
        • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
        • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

        Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

        Implementation plan

        1. Base display helpers (new)

        In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

        .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

        Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

        2. Responsive variants

        • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
        • Covered helpers:
          • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
          • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
          • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
        • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
        • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
        • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

        3. Generator

        Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

        4. Explicitly still out of scope

        Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

        5. Docs & verification

        • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
        • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
        • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
        • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

        Metadata

        Metadata

        Assignees

        Labels

        enhancementNew feature or request

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          Responsive helpers #9

          Description

          @minimaldesign

          Assessment

          Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

          Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

          Effort: M.

          What changed since the original plan

          • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
          • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
          • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
          • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

          Measured cost

          Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

          Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

          ScopeRules addedMinifiedGzipBrotli
          Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
          + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
          + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
          + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
          + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

          Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

          Decision: the trimmed set (522 rules, +16% minified)

          Ship display helpers, text-align variants, and spacing variants restricted to:

          • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
          • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
          • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

          The trims are principled, not just thrifty:

          • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
          • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
          • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

          Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

          Implementation plan

          1. Base display helpers (new)

          In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

          .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

          Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

          2. Responsive variants

          • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
          • Covered helpers:
            • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
            • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
            • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
          • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
          • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
          • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

          3. Generator

          Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

          4. Explicitly still out of scope

          Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

          5. Docs & verification

          • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
          • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
          • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
          • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

          Metadata

          Metadata

          Assignees

          Labels

          enhancementNew feature or request

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            Responsive helpers #9

            Description

            @minimaldesign

            Assessment

            Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

            Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

            Effort: M.

            What changed since the original plan

            • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
            • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
            • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
            • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

            Measured cost

            Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

            Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

            ScopeRules addedMinifiedGzipBrotli
            Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
            + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
            + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
            + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
            + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

            Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

            Decision: the trimmed set (522 rules, +16% minified)

            Ship display helpers, text-align variants, and spacing variants restricted to:

            • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
            • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
            • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

            The trims are principled, not just thrifty:

            • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
            • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
            • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

            Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

            Implementation plan

            1. Base display helpers (new)

            In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

            .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

            Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

            2. Responsive variants

            • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
            • Covered helpers:
              • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
              • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
              • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
            • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
            • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
            • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

            3. Generator

            Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

            4. Explicitly still out of scope

            Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

            5. Docs & verification

            • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
            • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
            • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
            • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

            Metadata

            Metadata

            Assignees

            Labels

            enhancementNew feature or request

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Responsive helpers #9

              Description

              @minimaldesign

              Assessment

              Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

              Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

              Effort: M.

              What changed since the original plan

              • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
              • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
              • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
              • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

              Measured cost

              Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

              Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

              ScopeRules addedMinifiedGzipBrotli
              Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
              + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
              + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
              + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
              + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

              Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

              Decision: the trimmed set (522 rules, +16% minified)

              Ship display helpers, text-align variants, and spacing variants restricted to:

              • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
              • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
              • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

              The trims are principled, not just thrifty:

              • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
              • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
              • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

              Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

              Implementation plan

              1. Base display helpers (new)

              In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

              .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

              Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

              2. Responsive variants

              • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
              • Covered helpers:
                • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
                • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
                • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
              • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
              • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
              • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

              3. Generator

              Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

              4. Explicitly still out of scope

              Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

              5. Docs & verification

              • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
              • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
              • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
              • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

              Metadata

              Metadata

              Assignees

              Labels

              enhancementNew feature or request

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                Responsive helpers #9

                Description

                @minimaldesign

                Assessment

                Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

                Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

                Effort: M.

                What changed since the original plan

                • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
                • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
                • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
                • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

                Measured cost

                Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

                Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

                ScopeRules addedMinifiedGzipBrotli
                Display + text-align only (original scope)42+2.1 kB (+1.4%)+0.3 kB+0.1 kB
                + spacing, full cross-product (16 props x 28 sizes)1386+62.8 kB (+43%)+6.5 kB (+30%)+1.9 kB (+12%)
                + spacing, minus w/h (14 x 28)1218+56.5 kB+5.9 kB+1.8 kB
                + spacing, logical props only (10 x 28)882+41.3 kB+4.5 kB+1.3 kB
                + spacing, logical props, sizes 0-xl3 (10 x 16)522+23.7 kB (+16%)+2.7 kB (+12%)+0.9 kB (+5.5%)

                Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

                Decision: the trimmed set (522 rules, +16% minified)

                Ship display helpers, text-align variants, and spacing variants restricted to:

                • Properties (10):m, mt, mb, mis, mie, p, pt, pb, pis, pie
                • Sizes (16):0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
                • Breakpoints (3):sm (>=480px), md (>=768px), lg (>=1024px)

                The trims are principled, not just thrifty:

                • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
                • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
                • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

                Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

                Implementation plan

                1. Base display helpers (new)

                In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

                .display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

                Naming wrinkle to document:.display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

                2. Responsive variants

                • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
                • Covered helpers:
                  • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
                  • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
                  • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
                • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
                • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
                • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

                3. Generator

                Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

                4. Explicitly still out of scope

                Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

                5. Docs & verification

                • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
                • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
                • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
                • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

                Metadata

                Metadata

                Assignees

                Labels

                enhancementNew feature or request

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions