Skip to content

About

A little bit about me and my work. Enjoy! πŸŽ‰βœˆοΈπŸŒπŸ“·πŸ§

Topics

Resources

Stars

11 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

3,281 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Hello & Welcome! πŸ‘‹

You have found the readme for the rehanbutt.com codebase. Feel free to explore the code and see how my site is built.

I use Jekyll as my static site templating engine and Scaffold CSS for layout. Scaffold is copied untouched into _sass/scaffold/; to update it, copy that folder over from naher94/scaffoldcss. The site deploys through the GitHub Actions workflow in .github/workflows/deploy.yml.

Curious how the site has evolved over time? Check out the releases over the years. Pretty fun to time travel! Like my own personal waybackmachine. πŸ˜‰

Production Notes

Run Jekyll with --livereload automatically refresh the page with each change you make to the source files jekyll s --livereload or --port 4500 to run a couple Jekyll sites in parallel. When using --livereload for concurrent sites make sure to set a port for --livereload like jekyll s --livereload --livereload-port 8080 --port 4001

Jekyll uses the Kramdown markdown parser allowing for extended functionality, such as adding classes to elements.

Jekyll also uses the Liquid language as its templating language. Here are a couple great references:

Content Notes

Projects

  • When a project is featured an additional tile image is needed
  • Tile Titles should be the project name followed by a brief descriptor of the work type (e.g. "CMU NYC Brand & Web Presence", "Esporre Portfolio Tool")
  • Tile Descriptions should say something along the lines of a one-liner around the central theme of the project
  • Page Titles should be very similar to Tile Titles with but may be a longer phrase of the project

Photography

  • Hero images for photosets should be 2x1 in aspect ratio
  • Map View should follow the Figma template
  • Detailed map should be exported as 1x1 and tightly bound
  • Images should be at max 1400px wide and size less then 1mb
  • Flag SVG template in Figma

Holiday Greetings

The footer on every page swaps "Have a nice Tuesday!" for a greeting on holidays. Every greeting lives in one list in holidays() in js/app.js. Add one line, in date order:

{ on: onDate(3, 14), text: "Happy Pi Day! πŸ₯§" },

on is one of four rules:

Rule Use Example
onDate(month, day) Same date every year onDate(3, 14) for Mar 14
nthWeekday(n, weekday, month) "2nd Sunday of May" type nthWeekday(2, 0, 5) for Mother's Day
hebrewDate(monthName, day, offset) A Hebrew calendar date hebrewDate("Nisan", 15, -1) for Passover, from the evening before
lunarNewYear Lunar New Year Already in the list
  • Months run 1–12; weekdays run 0 (Sunday) to 6 (Saturday).
  • Hebrew months are spelled the way the browser spells them: Tishri, Heshvan, Kislev, Tevet, Shevat, Adar, Adar I, Adar II, Nisan, Iyar, Sivan, Tamuz, Av, Elul.
  • Use "Adar" for holidays in Adar, such as Purim. Leap years have Adar I and Adar II instead, and "Adar" then means Adar II, where those holidays are kept.
  • offset is optional; use -1 for holidays that start the evening before.
  • When two holidays land on the same day, the one higher in the list shows.
  • Not supported yet: "last Monday of May" type dates (Memorial Day) and other lunar calendars (Islamic, Hindu). Each needs a new rule beside the others.
  • Lunar New Year comes from a table of dates for 2026–2050, since the browser's Chinese calendar is a day off in some years. After 2050 it falls back to the browser.

To preview a date, run this in the browser console on any page (months count from 0 here, so 1 is February), then reload to go back to today:

currentDate = () => new Date(2027, 1, 6); getHappyDayString()

Dribbble Share Links

<a href="https://rehanbutt.com">My Website</a> | <a href="https://twitter.com/naher94/">Twitter</a> | <a href="https://instagram.com/naher94">Instagram</a> | <a href="https://500px.com/naher94">500px</a> | <a href="https://www.pinterest.com/naher94/">Pinterest</a> | <a href="https://github.com/naher94">Github</a>

.MOV to .MP4

ffmpeg -i demo.mov -vcodec h264 demo.mp4 Convert .mov to .mp4 on a Mac

Development Notes

General

  • Scaffold reference for grid spans, flex helpers and breakpoints: https://scaffoldcss.rehanbutt.com/docs

    • Scaffold has no offsets. For a centered column use .centered-column (two-thirds from md) or .centered-column-half, defined in css/rehan.scss.
  • Internal links using custom collections <a href="{% link _projects/file.markdown %}">click here</a>

  • To fix a whitespace issue when using a Jekyll includes remove the - on either end of the tag. {% include external-link.html %} instead of {%- include external-link.html -%}

Code Snippets are helpful in populating common sections such as a resource's frontmatter. For information on available option within a VSCode Snippets reference their development documentation.

External Links

  • External links should use the external-link component via {{% include external-link.html %}} which includes a non-visual tag noting it goes external for enhanced accessibility <a target="_blank" href="https://rehanbutt.com/">Rehan Butt<span class="visually-hidden">Opens a new window</span></a>

Loop Videos

  • Short looping clips use {% include loop-video.html src="../img/foo.mp4" title="Description" natural=true %} (add audio=true for an unmute button). The pause/play button is wired up in loopVideos() in js/app.js.
  • Hover to play: put data-hover-play on a wrapping element (the two grids in _projects/interactions.markdown do) and every loop video inside rests on its first frame. A mouse hover plays it and loops while hovered; moving off lets the current pass finish, then it returns to the first frame. Touch taps toggle play/pause. Without the attribute, videos autoplay as usual.
  • Under prefers-reduced-motion every loop video starts paused, hover or not.

2D Array Hack

In some cases using a 2D array is cleaner and easier than setting up a collection. Even though its not formally supported in Liquid it is possible with this hack.

  1. Create a string with 2 sets of unique dividing characters in the case below , and |
  2. Assign and split that string to generate an array
  3. Loop through that array to generate the 2nd degree element
  4. Use that content of the 2nd degree element!
  5. Done!
{% assign array-list = "(A|B|C),(D|E|F),(H|I|J)" | remove: "(" | remove: ")" | split: ',' %}
{% for item in array-list %}
{% assign each = item | split: '|' %}
  <p>{{each[0]}}</p>
  <p>{{each[1]}}</p>
  <p>{{each[2]}}</p>
{% endfor %}
<!-- example from 2020.rehanbutt.com -->
<!-- devices -->
<div class="devices cell grid-x">
  <div class="cell">
    <h3>Device breakdown</h3>
  </div>
  {% assign device-list = "(πŸ‡ΊπŸ‡Έ|73%|Desktop),(πŸ‡¨πŸ‡³|26%|Mobile),(πŸ‡©πŸ‡ͺ|1%|Tablet)" | remove: "(" | remove: ")" | split: ',' %}

  {% for device in device-list %}
  {% assign each = device | split: '|' %}
  <div class="cell small-6 medium-4 device-container">
    <div class="device-wrapper">
      <p class="flag">{{each[0]}}</p>
      <p class="percentage">{{each[1]}}</p>
      <p class="device-type">{{each[2]}}</p>
    </div>
  </div>
  {% endfor %}
</div>

_projects Frontmatter Tags

Type post
Tag Use Data Type
layout The template for structural reference .html
title The h1 that shows at the top of the project page string
tile-name The project name that shows on index on hover string
thumbnail thumbnail file name image .png or .jpg when featured project
flag Notification style tag generally reads New and In Progress string
date YYYY-MM-DD date
tag For reference at a later date possibly for filtering string
published Whether it renders in the portfolio bool
featured Whether it renders in as featured (bigger tiles) bool
tile-description Description of the project that shows on a featured tile string
Type post-hero

Some additional Frontmatter Tags when using the post-hero template

Tag Use Data Type
hero-background-color The background color of the hero section and branded header/nav string eg. #FFFFFF
hero-background-color-dark The background color of the hero section and branded header/nav when in dark mode string eg. #CCCCCC
hero-accent-color Used to update the nav items and logo in the header string eg. #1f2937
hero-accent-color-dark Used to update the nav items and logo in the header in dark mode string eg. #1f2937
hero-image The image that loads into the hero section path as a string eg. fashion/lustre-hero.jpg; File should be an .png 1600 x 861
hero-image-alt The alt text for the hero image string eg. 2 outfits in studio lighting
hero-background The image that acts as a brand or vibe element in the hero section path as a string eg. shineregistry/shine-hero-background.svg; File should be an .svg 780 x 448

Type post-photo

Tag Use Data Type
layout The template for structural reference .html
title The h1 that shows at the top of the project page string
thumbnail thumbnail file name image .png or .jpg when featured project
thumbnail-alt The alt text for the thumbnail image string eg. Burj Al Arab Atrium
hero-image The image that loads into the hero section path as a string eg. dubai-expo/dubai-hero.jpg; File should be an .jpg aspect ratio 2x1
hero-image-alt The alt text for the hero image string eg. Dubai Expo Center Dome
featured Whether it renders on / bool
big-tile Whether it renders as a big tile on /photography bool eg. true
date YYYY-MM-DD date
display-date Use in the case of multiple dates string eg. Winter 2012 & Spring 2022

Type post-article

note that there is a separate file for article styling articles.scss

Tag Use Data Type
layout The template for structural reference .html
article Should always be true as it helps set the link type across the site bool eg. true
title The name of the article string
description A short explanation of the article string
hero-image The image that loads into the hero section path as a string eg. product-principles-strategies/hero.jpg; File should be an .jpg aspect ratio 2x1
hero-image-alt The alt text for the hero image string eg. Illustration of Pepper the Product Panda excited to showcase product principles and strategies
tags an array of related topics for the resource [leadership,product development] comma separated string
content-type The media type such as reference, tool, interactive, video, my content, reading, publication, portfolio, article, blog. string
date YYYY-MM-DD date

_resources Frontmatter Tags

Tag Use Data Type
layout The template for structural reference .html
title The main name that references the resource in each tile string
link The external url to the resource string
description A short explanation of the resource string
tags an array of related topics for the resource [film,games,tools] comma separated string
content-type The media type such as reference, tool, interactive, video, my content, reading, publication, portfolio, article, blog. string

_work-experience Frontmatter Tags

Tag Use Data Type
role The job title string
date-start YYYY-MM-DD date
date-end YYYY-MM-DD or "Present" if current experience date or string of "Present"
company Name of the organization string
group Optional team, lab or course inside the company, appended to the role as Role・Group. No entry sets it today β€” the descriptions name the group instead string
description Explanation of the role string
logo A logo representing the job for visual context .svg
sort-order Order in which the experiences are sorted and grouped 1 being the top int
collapsed Renders the role inside a <details> element that starts closed boolean

_speaking Frontmatter Tags

Tag Use Data Type
title The name of the event string
date YYYY-MM-DD If the date is a future date an "Upcoming" badge will be shown date
location The conference where the speaking event took place string
description A short explanation of the speaking event string
link url to the event's recording or related materials string
logo A logo representing the event for visual context .svg

Type System

_sass/variables.scss holds the type vocabulary beside $semantic-colors: two maps, one mixin, register named at the call site.

h2            { @include type-style(product, section-title); }
.count .value { @include type-style(expressive, counter); }

The two registers

Product is type that repeats β€” project pages, resource collections, articles. One decision lands on seventy-odd pages, so it stays systematic and on the scale. Expressive is set for a single moment and answers to nothing else.

The axis is repetition, not volume. Loud is not expressive: the post callouts run across eleven case studies and the tag headings repeat sixty times down one page, so both are product however large they are set. A style that turns up on a second page has stopped being expressive.

The register sits in the call rather than on the entry, so moving a style between registers makes you look at every place it is used.

The names come from IBM Carbon, the axis does not: Carbon splits on user intent, which on an editorial site would have one value nearly everywhere. Two consequences β€” expressive here is a short list of one-offs rather than a parallel hierarchy, so the maps are not symmetric and are not meant to be. Carbon's rule that type stays consistent within a component still holds.

The two families

Zilla names, Lato reads. Headings, titles and nav are Zilla; sentences, labels, metadata and UI are Lato. For anything ambiguous, ask whether you are naming it or saying it β€” a section header names, a pull-quote says.

Product follows that rule; expressive is exempt, which is the point of it. counter is Lato Black at 90px, a text face doing display work, and is the most distinctive type on the site because of that.

Zilla has no size floor β€” it is the display face by role, so the nav stays Zilla at every width. Caveat annotates photo captions and Consolas appears in code. Neither is in the maps.

Naming

The name is the job the type does, never a tag and never a rank: a section heading is section-title whether it is marked up as an h2 or an h3, which is what class="h1" on an <h2> exists to avoid. No numbered names β€” title-1 / title-2 reintroduces the same confusion one layer down. Size lives in the value, so nav-link can move onto the scale without being renamed.

Values

$product-type has two halves. Hierarchy β€” page-title, section-title, body, body-strong, body-sm β€” is rank on the page. Component styles β€” nav-link, card-title, control, meta, eyebrow β€” are named for the thing they are, because nothing is "one step below" a nav link. $expressive-type holds counter and statement.

Sizes are rem against the 16px root, never em: an em resolves against whichever parent it lands in, which is how one declaration ended up rendering at six different sizes.

Values match what the site already renders, with two exceptions:

  • statement is aspirational. Nothing is set that way yet. It is the slot for a hero line or a statement over an image, set tighter than a heading on both axes because large type reads loose at settings that suit 16px.
  • section-title changed what renders. The 32 .tag-group h2 headings on the resources page were Lato Black; a section header names, so the entry is Zilla Bold. Adopting it moved them to Zilla 700, and they merged with the project and collection titles already at that rank on 27 other pages.

API

@include type-style($register, $style-key);   // emits the whole style
type-value($register, $style-key, $prop);     // reads one property

Every value is emitted as a custom property reference, the way colours go through semantic-color():

:root       { --type-eyebrow-lg-size: 0.75rem; }
.sub-header { font-size: var(--type-eyebrow-lg-size); }

The reference is the attribution β€” the audits read the entry's name off it rather than inferring it from the value, which failed whenever two entries rendered alike. The name carries no register, so a key may appear in only one map; the build fails if it doesn't.

Optional properties (letter-spacing, text-transform, font-style) are emitted only when the style defines them, so a rule never carries a letter-spacing: normal that would override an inherited value. Nor a var() to a property the entry leaves out: that would still win the cascade, then compute to the parent's value over a rule like the heading reset's 1.4.

at holds per-breakpoint exceptions, with size staying the desktop value β€” a desktop base with md down corrections, matching how the rest of the site is written rather than mobile-first:

callout-lg: (family: $lato, size: 3.125rem, font-weight: $lato-regular, line-height: 1.3,
             at: (md down: (size: 2.5rem))),

Two things to know about it:

  • Keys are unquoted. Each key is spread into scaffold.breakpoint() as size and direction, so 'md down' collapses to one string and loses it.
  • Moving an override into the map changes its specificity. page-title's 48px lived on a bare h1 selector, so two h1s ignored it β€” the 404 title and the about hero, both re-applying page-title at class specificity. From the map the query redefines the entry's custom properties on :root, so every call site shrinks with it, those two included. A property only the at block sets is still declared at each call site, inside the query. A mobile step meant for only some call sites wants its own entry instead.

Unknown names fail the build, with the valid ones listed:

Unknown product type style `nosuchstyle`.
Known: page-title, section-title, body, body-strong, body-sm, nav-link, card-title, control, meta, eyebrow, callout-lg, callout-md, callout-sm.

Unknown type register `productive`. Known: product, expressive.

Unknown breakpoint `smal` on product/callout-lg. Known: small, medium, large, xlarge, xxlarge.

product/callout-lg has no `size`. A base style must set both family and size.

Migrating onto it

Little is migrated yet. The atlas and specimen sheet in Tooling show what each rule resolves to β€” in particular Across widths, which follows one element across every breakpoint instead of listing each width as a separate style. Most of what looks like two styles is one declaration seen twice.

Tooling

Color Audit

_tools/color-audit.py scans the SCSS and CSS sources alongside the compiled stylesheet and reports every colour the site uses β€” how often, which file it is authored in, which selectors it renders on, and whether it comes from a design token, a hand-written literal, Sass (mix(), rgba(), darken()) or a vendor stylesheet. Gradients are counted as single entries rather than as their individual stops.

Rendered colours are traced through the Sass source map back to the partial and line that produced them, so a compiled value is judged by the declaration that actually made it. A colour reached through a token is judged by how the token was written rather than by the rule that used it, since the rule only says var(--color-x).

bundle exec jekyll build && python3 _tools/color-audit.py

The build must come first β€” the script reads _site/css/rehan.css for the rendered side, so skipping it compares new source against stale output.

It writes two files beside itself, both gitignored:

  • color-audit.json β€” the dataset
  • color-atlas.html β€” a standalone page that plots the palette as circles sized by usage, clustered by hue or by file, with a panel for each colour's source lines and selectors. Open it directly in a browser.

Use --no-html for the data only, or --out / --html to redirect either output.

Colours can be checked off as you work through them. Mark reviewed in the detail panel takes a colour off the grid, the Reviewed filter brings them back or clears them, and the state lives in localStorage so it survives regenerating the page. It is keyed by the entry's id with the colour's value stored alongside β€” so if a colour changes after you reviewed it, the tick is dropped and the colour reappears with a note saying why. Reviews are tracked separately for the authored and rendered views, and the key is namespaced because browsers treat every file:// page as one storage origin.

Type Audit

_tools/type-audit.py is the typography equivalent. It walks every built page in _site, matches the compiled CSS rules to each element, sorts them by specificity and carries font-size down the tree β€” so it reports what type actually renders as, not what the stylesheet declares. That distinction matters here: more than half the font-sizes in the compiled stylesheet are still em, and Sass never resolves those, so a declaration on its own says nothing about the size on screen.

bundle exec jekyll build && python3 _tools/type-audit.py

Each distinct combination of family, size, weight, style, line-height, tracking and case counts as one style. File attribution comes from the Sass source map (_site/css/rehan.css.map), so every style knows which partial wrote it. The map entry comes from the var(--type-*) reference itself, resolved against :root at each width.

It writes these beside itself, all gitignored:

  • type-audit.json β€” the dataset
  • type-atlas.html β€” a standalone page listing each style as a specimen rendered at its true size. Open it directly in a browser.
  • type-specimens.html β€” a second view of the same data, drawn as cards at the size they render and counted by elements. It opens on the combined view (a card per style, largest first); switching to By property breaks it into font-sizes, weights and families, each section showable or hideable.
  • type-cascade.json / type-cascade.html β€” the third view, and the only one organised by where a value came from rather than by what it renders as. See below.

Type Cascade

The atlas and the specimen sheet both group type by outcome: every element that renders Lato 20px/400 lands in one bucket. That is the right shape for "what does this site use," and the wrong shape for "why is this element like this" β€” a card can only show a resolved value and the file:line that produced it, which makes a size inherited from body four levels up indistinguishable from one written on the element itself.

type-cascade.html is one tree for the whole site, drawn as a node network: columns by depth, curved edges, and a box per node. A node is an element that declares a type property; elements that declare none are collapsed away, because an element that changes nothing is not a branch point. That takes 6,970 elements down to 225 nodes, median depth 4.

Every node is drawn as one of two things, which is the whole idea:

  • Where type renders β€” text actually rests on the node β€” it shows the specimen. AaBb in the real family, weight, slant, tracking and case, drawn at the real size up to a 46px cap, captioned with the atlas card number, the true size and the family. A node resolves to exactly one card, so the specimen is never an average of several.
  • Where it does not β€” a pure branch point, nothing resting on it β€” there is nothing to draw, so it says what it contributes in words: wt bold, size 100%, fam Zilla Slab. Coloured by whether the value came from the map, a variable or a literal.

Identity is the chain of signatures from the root, so the same path on ninety pages is one node carrying a count of ninety β€” which makes the tree a synthesis rather than any one page's DOM, and it says so in the footer. A signature is the element's tag and classes plus a stamp for the rules that won on it: a bare <p> inside .cta-bar and one inside .home are both p but are matched by different descendant selectors and end up with different type. Keying on tag and classes alone put seven distinct styles on one html/body/p node and recorded whichever page happened to be walked last. Each node prints the source lines its declarations came from, so two siblings that share a signature and a rendered style are still told apart.

Clicking a node gives the route:

  • Sets here β€” what this node declares, with the value, the file and line, and whether it came from a map entry, a Sass variable or a bare literal.
  • Arrives from above β€” for every property the node does not set, the value and the ancestor that decided it, as a link.
  • Elements β€” how many rest here, split into the ones that set some of their own type and the ones that inherit all of it. That split is also the bar on every row, and the headline figure: at desktop, 952 of 6,970 elements (14%) inherit their type entirely.
  • Resolves to β€” the atlas cards these elements become, by the same C-numbers the specimen sheet uses, at the selected width.

The width selector re-resolves the whole tree, so a node that sets a size only at small appears there and nowhere else. It ships its own JSON rather than riding along in type-audit.json, which would put 480KB of ancestry inside two pages that never ask for it.

The atlas and specimen sheet resolve the stylesheet at each of the project's breakpoints, read from $breakpoints in _settings.scss rather than restated. Width is a multi-select filter alongside Family and Weight, all widths on by default, and selecting several shows every style present at any of them. A style that covers only part of the current selection carries a badge naming the widths it does cover; one that holds across all of them carries none, since a badge on every row says nothing.

No figure anywhere is a sum across widths β€” the same paragraph counted at three widths would inflate a 6,970-element site to 12,055. Totals report the busiest single width, and the atlas's count column lists one figure per selected width rather than adding them. Breakpoints that resolve to identical type are collapsed and not offered as separate choices: this site defines five, but xlarge and xxlarge render exactly as large does, so three are shown.

This matters more than it sounds. The body font-size drops to 16px on small, so most em-derived type shifts with it: 728 off-scale elements at desktop, 3,594 at 375px. Before this, small only rules were dropped entirely and mobile type went unreported β€” which is how the mobile menu came to be listed at a size no phone renders. A style is flagged on a row only when it exists at one width and no other; the panel's widths row carries the full picture.

Both pages carry a third column, Rendered at, listing the actual occurrences of whatever is selected β€” about.html:312 Β· Work Experience Β· h2.cell.small-12.medium-shrink β€” grouped by page and sorted by line. It samples up to 60 per style and always states the true total, so a truncated list never reads as the whole picture.

Each occurrence expands onto the declarations that produced that one element, with the partial and line for every property, and the Sass name beside the value where there is one β€” font-weight: 900 ($lato-black), line-height: 1.6 ($paragraph-lineheight). The name says which decision produced the number, which the number alone cannot. It is read back from the source line and only shown when that line actually declares the property, since the source map occasionally points at the rule rather than the declaration. Elements of a style nearly always resolve through the same rules, so the sets are stored once and referenced β€” which is what makes the difference visible when they don't: the four Work Experience headings resolve through about.scss, the Speaking one through a duplicate block in speaking.scss.

Every card carries a short id β€” S4, W2, F3, DS7 in Declared, C1 in the combined view β€” so a specimen can be named in conversation. The ids are fixed to the value rather than to render order, so they survive re-sorting.

The specimen sheet has a Computed / Declared switch. Computed draws each card at its real rendered size and the panel says which authored spellings feed it. Declared shows what was typed β€” 23 spellings against 19 computed values β€” and the panel says what each one computes to. Declared sizes are drawn nominally against a 16px base and the page says so: 1em on this site renders anywhere from 10px to 70px, so those cards show the spelling, not the rendering.

The page exists to answer whether the site needs everything it currently has, so it is built around merging:

  • By role groups styles by everything except their line-height. Rows inside a group render identically apart from their leading, so they are the merge candidates; the header says how many elements collapsing them would move, and each row is marked keep or -> <target>.
  • The detail panel states the recommendation in words for any style, in any grouping: keep it, merge it into a named line-height, or nothing to merge. Where it says merge, it names the cost in elements and pages and the file and line the line-height is written at. The named target and every other leading in the role are links, so a recommendation can be reviewed rather than taken on trust β€” they work regardless of the current filters.
  • Matrix plots size against weight per family. Adjacent rows with small counts are near-duplicates; clicking a cell opens its styles.
  • Narrow to isolates either the styles that share a role with another, or the long tail under 25 uses.
  • The detail panel shows the authored declaration and source line behind every property β€” font-size: 1em β†’ _sass/header.scss:48 β€” so a row can be acted on without going hunting.

Grouping by family or by file, and filters for family, weight, source and on/off scale are all there too. Sort by uses, size or pages; clicking the active sort reverses it, and the direction applies to the groups as well as the rows inside them.

The scale it reports against is the SCALE tuple at the top of the script. It is fitted to real usage rather than to a formula, and it describes the site as it is β€” edit it when the scale is decided.

Same flags as the colour audit, plus --pages N to sample the first N pages for a quick look.

CSS Reachability

_tools/css-audit.py asks one question of every rule in the compiled stylesheet: does anything on the site match it?

bundle exec jekyll build && python3 _tools/css-audit.py

It reports rules rather than selectors, because a rule is only deletable when its whole selector list is dead. A live rule carrying dead entries in its list is counted separately as bloat β€” that is usually an @extend emitting combinations the markup never produces, and the fix is in the Sass rather than in deleting a line.

A wrong "dead" would send you deleting working code, so anything the matcher cannot evaluate exactly is widened first β€” a > b to a b, a + b to b, .x:hover to .x, .x[open] to .x β€” and reported dead only when even the wider form matches nothing. Widening can only match more, never less, so every error falls on the safe side. Two blind spots are reported separately instead of being folded in: classes a script adds at runtime (every identifier in the site's JavaScript is collected, and a rule naming one is held back as script-reachable) and pages that are not built, since the audit only knows _site.

It writes two files beside itself, both gitignored:

  • css-audit.json β€” the dataset
  • css-atlas.html β€” a standalone page built around deciding what to remove. It opens with the whole stylesheet's weight split into unreachable, dead list entries, script-reachable and matched, then explains each verdict and what it asks you to do. Rules are split into two lanes, because they need different actions: yours are deleted one at a time, ordered by how much compiled CSS each removal saves, while vendor rules are meant to be handled wholesale rather than one at a time. That second lane still assumes Foundation's @include structure in _sass/app.scss, which is gone, so it needs reworking for Scaffold before it reports anything useful. Group by file or verdict, mark dead entries inside their selector list, and the panel names the file and line with the specific next step. Open it directly in a browser.

Byte figures are compiled output before gzip, not the size of the source edit β€” a few lines of nested Sass can emit a kilobyte of selectors.

--list prints every dead rule with its source line; --list-file _sass/home.scss narrows that to one file; --no-html skips the page.

Shared Cascade

All three audits sit on _tools/cascade.py β€” the built pages parsed into a DOM, the compiled stylesheet parsed into rules, selector matching, and the Sass source map that maps any byte of the output back to the partial and line that wrote it. It is not run directly.

Reference

Places that Link to Me

Links Logo? Location? Bio? Notes
https://www.linkedin.com/in/rehan-butt/ βœ… header image βœ… βœ…
https://instagram.com/naher94 βœ–οΈ βœ–οΈ βœ…
https://www.threads.net/@naher94 βœ–οΈ βœ–οΈ βœ…
https://github.com/naher94 βœ–οΈ βœ–οΈ βœ… Twitter link
https://twitter.com/naher94 βœ… header image βœ… βœ…
https://codepen.io/rehanbutt βœ–οΈ βœ–οΈ βœ–οΈ Twitter link
https://www.pinterest.com/naher94/ βœ–οΈ βœ–οΈ βœ–οΈ
https://500px.com/p/rehan_butt βœ–οΈ βœ… βœ… Twitter & Instagram links
https://dribbble.com/rehanbutt βœ–οΈ βœ… βœ… social links
https://www.imdb.com/name/nm15449795/ βœ–οΈ βœ–οΈ βœ–οΈ rehanbutt.com & Linkedin links
https://adplist.org/mentors/rehan-butt βœ… header image βœ… βœ… Twitter link
https://wellfound.com/u/rehan-butt βœ–οΈ βœ… βœ… social links
https://medium.com/@rehan-butt βœ–οΈ βœ–οΈ βœ…
https://www.youtube.com/c/RehanButt1994 βœ… header image βœ–οΈ βœ–οΈ social links
https://www.clubhouse.com/@rehanbutt βœ–οΈ βœ… βœ… Twitter link
https://letterboxd.com/rehanbutt βœ–οΈ βœ–οΈ βœ…
https://www.stickermule.com/u/rehanbutt βœ–οΈ βœ–οΈ βœ–οΈ
http://BRND.life βœ… footer βœ–οΈ βœ–οΈ
http://QULR.life βœ… footer βœ–οΈ βœ–οΈ
http://naher94.github.io/esporre/ βœ–οΈ βœ–οΈ βœ–οΈ
https://stackoverflow.com/users/12394272/rehan-butt βœ–οΈ βœ–οΈ βœ… social links
https://community.cmu.edu/s/
https://saes.alumnifire.com βœ–οΈ βœ–οΈ βœ… bio & photo
https://jekyllrb.com/showcase/ βœ… βœ–οΈ βœ–οΈ Full screen shot of the home page v4.0.0
https://dl.acm.org/profile/99661657353 βœ–οΈ βœ–οΈ βœ…
https://devzgn.com βœ–οΈ βœ–οΈ βœ–οΈ just a link

About

A little bit about me and my work. Enjoy! πŸŽ‰βœˆοΈπŸŒπŸ“·πŸ§

Topics

Resources

Stars

11 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages