+
{% include foot.html %}
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);
}