diff --git a/_layouts/default.html b/_layouts/default.html index b2d0ce0..bc4b6e1 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -12,11 +12,11 @@ layout: compress
-
+
{% include nav.html %}
-
+
{% capture anchor_svg %}{% include icon.html name="fa-anchor" class="w-3 h-3 inline-block" %}{% endcapture %} {% include anchor_headings.html html=content anchorBody=anchor_svg anchorClass="content-center" %}
@@ -24,19 +24,20 @@ layout: compress {% if site.data.buttons.size > 0 %} -
+
{% for button in site.data.buttons %} - {{ button.alt }} + {{ button.alt }} {% endfor %}
{% endif %} diff --git a/index.md b/index.md index 6df02f6..0249361 100644 --- a/index.md +++ b/index.md @@ -144,8 +144,8 @@ Since being exposed to computer programming on [Scratch](https://scratch.mit.edu grid-template-columns: repeat(2, minmax(0, 1fr)); width: var(--projects-breakout-width); max-width: none; - margin-left: calc((80ch - var(--projects-breakout-width)) / 2); - margin-right: calc((80ch - var(--projects-breakout-width)) / 2); + margin-left: calc((85ch - var(--projects-breakout-width)) / 2); + margin-right: calc((85ch - var(--projects-breakout-width)) / 2); } } .prose :where(code):not(:where([class~=not-prose], diff --git a/scripts/post-build.js b/scripts/post-build.js index a6b372b..d215944 100644 --- a/scripts/post-build.js +++ b/scripts/post-build.js @@ -13,7 +13,7 @@ function shouldSkip(c) { // Icons if (c.includes('fa-') || c.includes('icon-fa')) return true; if (c.startsWith('not-')) return true; - + return false; } @@ -31,15 +31,15 @@ function getVarShortName(num) { function processFile(filePath) { let html = fs.readFileSync(filePath, 'utf8'); let modified = false; - + const matches = html.match(/class="[^"]+"/g) || []; - + // Collect --tw-* variables (first file only, or reset per file - using global map) const varMatches = html.match(/--tw-[a-z0-9-]*/g) || []; varMatches.forEach(v => { if (!varMap[v]) varMap[v] = getVarShortName(varIdx++); }); - + // First pass: collect class mappings matches.forEach(m => { const arr = m.slice(7, -1).split(' ').filter(c => c); @@ -47,13 +47,13 @@ function processFile(filePath) { if (!shouldSkip(c) && !classMap[c]) classMap[c] = 'c' + idx++; }); }); - + // Also create dark: mappings (e.g., dark:text-primary-300 → same cXX as text-primary-300) Object.keys(classMap).forEach(orig => { const darkVer = 'dark:' + orig; classMap[darkVer] = classMap[orig]; }); - + // Also create responsive mappings (sm:, md:, lg: → same cXX as base) ['sm:', 'md:', 'lg:', 'xl:', '2xl:'].forEach(prefix => { Object.keys(classMap).forEach(orig => { @@ -63,8 +63,8 @@ function processFile(filePath) { } }); }); - -// Second pass: replace classes in HTML + + // Second pass: replace classes in HTML matches.forEach(m => { const arr = m.slice(7, -1).split(' ').filter(c => c); const newArr = arr.map(c => shouldSkip(c) ? c : classMap[c]); @@ -73,7 +73,7 @@ function processFile(filePath) { modified = true; } }); - + // Replace --tw-* variables in HTML and inline styles Object.entries(varMap).forEach(([orig, short]) => { const varRegex = new RegExp(orig.replace(/[-]/g, '\\-'), 'g'); @@ -82,19 +82,19 @@ function processFile(filePath) { modified = true; } }); - + // Third pass: replace in ALL CSS Object.entries(classMap).forEach(([orig, short]) => { if (shouldSkip(orig)) return; - + // For regular classes: escape dashes, NOT brackets (CSS keeps them as-is) let escaped = orig.replace(/-/g, String.raw`\-`); - + // 1. Base class: .flex-row → .c6 const basePattern = String.raw`\.` + escaped; const baseRegex = new RegExp(basePattern + '(?=[{:, >])', 'g'); html = html.replace(baseRegex, '.' + short); - + // 2. Dark variant: .dark\:flex-row → .c6 if (orig.startsWith('dark:')) { const baseClass = orig.slice(5); @@ -103,18 +103,18 @@ function processFile(filePath) { const darkRegex = new RegExp(darkPattern + '(?=[{:, >])', 'g'); html = html.replace(darkRegex, '.' + short); } - + // 3. Responsive variant if (orig.match(/^(sm|md|lg|xl|2xl):/)) { const baseClass = orig.replace(/^(sm|md|lg|xl|2xl):/, ''); const prefix = orig.match(/^(sm|md|lg|xl|2xl):/)[1]; const baseEscaped = baseClass.replace(/-/g, String.raw`\-`); - + const respPattern = String.raw`\.` + prefix + String.raw`\\:` + baseEscaped; const respRegex = new RegExp(respPattern + '(?=[{:, >])', 'g'); html = html.replace(respRegex, '.' + short); } - + // 4. Arbitrary value classes: w-[88px], max-w-[80ch], border-t-[6px] // In CSS: brackets are escaped as \[ and \] if (orig.includes('[')) { @@ -124,7 +124,7 @@ function processFile(filePath) { const arbiPattern = '.' + cssClass; const arbiRegex = new RegExp(arbiPattern + '(?=[{:, >])', 'g'); html = html.replace(arbiRegex, '.' + short); - + // Dark + arbitrary if (orig.startsWith('dark:')) { const baseClass = orig.slice(5); @@ -133,7 +133,7 @@ function processFile(filePath) { const darkArbiRegex = new RegExp(darkArbiPattern + '(?=[{:, >])', 'g'); html = html.replace(darkArbiRegex, '.' + short); } - + // Responsive + arbitrary if (orig.match(/^(sm|md|lg|xl|2xl):/)) { const baseClass = orig.replace(/^(sm|md|lg|xl|2xl):/, ''); @@ -145,8 +145,8 @@ function processFile(filePath) { } } }); - -// Fourth pass: handle CSS selectors with no- prefix classes + + // Fourth pass: handle CSS selectors with no- prefix classes // e.g., a:not(.no-external-icon) → a:not(.cXX) // e.g., a[data-no-external-icon] → a[data-cXX] Object.entries(classMap).forEach(([orig, short]) => { @@ -155,17 +155,17 @@ function processFile(filePath) { const escaped = orig.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const selectorRegex = new RegExp('\\.' + escaped + '(?=[^a-zA-Z0-9]|$)', 'g'); html = html.replace(selectorRegex, '.' + short); - + // Also replace data-no-external-icon in attribute selectors: [data-no-external-icon] → [data-cXX] const attrRegex = new RegExp('\\[data-' + escaped + '\\]', 'g'); html = html.replace(attrRegex, '[data-' + short + ']'); - + // And data-no-external-icon="..." in HTML: data-no-external-icon="..." → data-cXX="..." const htmlAttrRegex = new RegExp('data-' + escaped + '=', 'g'); html = html.replace(htmlAttrRegex, 'data-' + short + '='); } }); - + if (modified) fs.writeFileSync(filePath, html); } @@ -184,7 +184,7 @@ function updateCSS(filePath) { if (!fs.existsSync(filePath)) return; let css = fs.readFileSync(filePath, 'utf8'); let modified = false; - + // Replace --tw-* variables in CSS Object.entries(varMap).forEach(([orig, short]) => { const varRegex = new RegExp(orig.replace(/[-]/g, '\\-'), 'g'); @@ -193,38 +193,57 @@ function updateCSS(filePath) { modified = true; } }); - + + // Replace ALL class names in CSS selectors Object.entries(classMap).forEach(([orig, short]) => { - if (orig.startsWith('no-')) { - const escaped = orig.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - - // Replace .no-external-icon in any CSS selector - const selectorRegex = new RegExp('\\.' + escaped + '(?=[^a-zA-Z0-9]|$)', 'g'); - if (css.match(selectorRegex)) { - css = css.replace(selectorRegex, '.' + short); + if (shouldSkip(orig)) return; + + let escaped = orig.replace(/-/g, String.raw`\-`); + + // 1. Base class in selectors: .flex-row → .c6 + const selectorRegex = new RegExp('\\.' + escaped + '(?=[^a-zA-Z0-9\\-]|$)', 'g'); + if (css.match(selectorRegex)) { + css = css.replace(selectorRegex, '.' + short); + modified = true; + } + + // 2. Dark variant: .dark\:flex-row → .c6 + if (orig.startsWith('dark:')) { + const baseClass = orig.slice(5); + let baseEscaped = baseClass.replace(/-/g, String.raw`\-`); + const darkPattern = String.raw`\.dark\\:` + baseEscaped; + const darkRegex = new RegExp(darkPattern + '(?=[^a-zA-Z0-9\\-]|$)', 'g'); + if (css.match(darkRegex)) { + css = css.replace(darkRegex, '.' + short); modified = true; } - - // Replace [data-no-external-icon] attribute selector - const attrRegex = new RegExp('\\[data-' + escaped + '\\]', 'g'); - if (css.match(attrRegex)) { - css = css.replace(attrRegex, '[data-' + short + ']'); + } + + // 3. Responsive variant: .md\:flex-row → .c6 + if (orig.match(/^(sm|md|lg|xl|2xl):/)) { + const baseClass = orig.replace(/^(sm|md|lg|xl|2xl):/, ''); + const prefix = orig.match(/^(sm|md|lg|xl|2xl):/)[1]; + const baseEscaped = baseClass.replace(/-/g, String.raw`\-`); + const respPattern = String.raw`\.` + prefix + String.raw`\\:` + baseEscaped; + const respRegex = new RegExp(respPattern + '(?=[^a-zA-Z0-9\\-]|$)', 'g'); + if (css.match(respRegex)) { + css = css.replace(respRegex, '.' + short); modified = true; } } - - // Arbitrary value classes + + // 4. Arbitrary value classes: .w-[88px] → .c5 if (orig.includes('[')) { const cssClass = orig.replace(/\[/g, '\\x5c\\x5b').replace(/\]/g, '\\x5c\\x5d'); const arbiPattern = '.' + cssClass; - const arbiRegex = new RegExp(arbiPattern + '(?=[{:, >])', 'g'); + const arbiRegex = new RegExp(arbiPattern + '(?=[^a-zA-Z0-9\\-]|$)', 'g'); if (css.match(arbiRegex)) { css = css.replace(arbiRegex, '.' + short); modified = true; } } }); - + if (modified) fs.writeFileSync(filePath, css); }