Skip to content
jheaglePublic

About

Personal portfolio site: joshuaheagle.com

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

287 Commits

Folders and files

Repository files navigation

Repository for my personal website

Modules

siteNav

The homepage's section nav: pinned to the top by its classes, and made smarter here.

  • Hides while the visitor scrolls down and comes back when they scroll up (or tab into it), so it doesn't cover the page while reading.
  • On phones it collapses behind the Menu button (the list's data-closed attribute; from md up the classes keep it a row whatever the attribute says).
  • Marks the link of the section being read with data-current and aria-current.

Without this script the nav is still a plain, pinned list of links: the Menu button stays hidden and the list stays open.

roleTitles

The typing effect on the homepage's role line, run by title-switcher (https://www.npmjs.com/package/title-switcher).

The page's real text stays put: the role line's static "Full-Stack Engineer" is what screen readers, applicant tracking systems and visitors without JavaScript get. The lines to type live in a <template>, which browsers don't render and readers don't read. This module copies them into an aria-hidden span, tucks the static text away visually (data-animated on the line switches its group-data-animated:sr-only class on), and starts the typing. When the visitor's system asks for reduced motion, nothing changes at all.

titleSwitcherSandbox

The title-switcher sandbox on /projects/: visitors type their own lines, set the speed and order, and watch title-switcher (https://www.npmjs.com/package/title-switcher) type them out.

title-switcher has no "stop": pause() only stops it from moving to the next line. So each (re)start builds a fresh container on the stage and lets the old instance finish in the background, detached and unseen. The lines become text nodes (never HTML), so whatever a visitor types is shown, not run.

siFuncionaSandbox

The si-funciona sandbox: pick a function, edit its inputs, and see the call and its result update as you type. Runs the real library (https://www.npmjs.com/package/si-funciona), loaded as window.siFunciona.

Inputs are read as text, numbers or JSON (JSON.parse, never eval), and results are shown with textContent, so nothing a visitor types is run as code. Rendered into each <div data-sandbox='si-funciona'>, so /projects/ and the si-funciona page share one sandbox.

pseudoDomSandbox

The pseudo-dom sandbox, "same question, two DOMs": pseudo-dom imitates the browser's DOM for tests in Node, so the page asks it and the real browser DOM the same questions (CSS selectors, then a few predefined changes) and shows both answers side by side. Runs the real library (pseudo-dom), loaded as window.pseudoDom.

The real side parses visitor HTML in a separate, inert document (document.implementation.createHTMLDocument), where scripts and handlers like onerror never run; pseudo-dom never runs anything either. Rendered into each <div data-sandbox='pseudo-dom'>, so /projects/ and the pseudo-dom page share one sandbox.

matrixDomSandbox

The matrix-dom sandbox: a 10 by 10 board to try the grid geometry Battleship is built on. Draw a line between two cells, see a cell's neighbours, or place a fleet the way Battleship does. Runs the real library (matrix-dom), loaded as window.matrixDom: on /projects/ the site's own bundle already defines it, and the matrix-dom page loads it from browser/vendor.

Rendered into each <div data-sandbox='matrix-dom'>, so /projects/ and the matrix-dom page share one sandbox.

jsonFsQuerySandbox

The json-fs-query sandbox: a small library database (authors, books, members, loans) kept as JSON files on this site, queried in the browser by the real library (json-fs-query, window.jsonFsQuery) over fetch(). Pick a question to load its query, edit it, or write your own read. Writes only work in Node, and json-fs-query says so.

Results are drawn with textContent, so whatever a query returns is shown, not run. Rendered into each <div data-sandbox='json-fs-query'>, so /projects/ and the json-fs-query page share one sandbox. The database is built by scripts/build-library-database.mjs (with json-fs-query itself).

jsonDomSandbox

The json-dom sandbox: describe a piece of page as JSON (json-dom's own item format) and watch json-dom render it, next to the HTML it produced. Runs the real library (json-dom), loaded as window.jsonDom: on /projects/ the site's own bundle defines it, and the json-dom page loads it from browser/vendor.

Visitor JSON goes through an allowlist first: only plain content tags and a few safe attributes render, so nothing like onclick, <script> or a javascript: link can run. Whatever was dropped is listed. Rendered into each <div data-sandbox='json-dom'>, so /projects/ and the json-dom page share one sandbox.

collectYourStuffSandbox

The collect-your-stuff sandbox: try a Queue, a Stack and a (doubly) linked list one call at a time, and see the collection redrawn after each call from the library's own state. Runs the real library (https://www.npmjs.com/package/collect-your-stuff), loaded as window.collectYourStuff.

Values are shown with textContent, so whatever a visitor types is displayed, not run. Rendered into each <div data-sandbox='collect-your-stuff'>, so /projects/ and the collect-your-stuff page share one sandbox.

Constants

projects : Array.<ProjectTile>

Functions

tileContent(project) ⇒ DomItem

A single tile's content: an icon badge plus title/description, all wrapped in one clickable link.

projectGrid(projects) ⇒ DomItem

Builds a matrix-dom grid of project tiles, one per entry in projects, in display order (row-major: left to right, then top to bottom). Any trailing columns left over when projects.length doesn't divide evenly into columns are left empty (marked 'tile-empty', and invisible).

Typedefs

ProjectTile : object

The real projects shown in the homepage's project grid, in display order.

siteNav

The homepage's section nav: pinned to the top by its classes, and made smarter here.

  • Hides while the visitor scrolls down and comes back when they scroll up (or tab into it), so it doesn't cover the page while reading.
  • On phones it collapses behind the Menu button (the list's data-closed attribute; from md up the classes keep it a row whatever the attribute says).
  • Marks the link of the section being read with data-current and aria-current.

Without this script the nav is still a plain, pinned list of links: the Menu button stays hidden and the list stays open.

siteNav.scrollTolerance

Scroll movements smaller than this (in px) don't change whether the nav is shown, so tiny jitters don't flicker it.

Kind: static constant of siteNav

siteNav.nextHidden ⇒ boolean

Decide whether the nav should be hidden after a scroll.

Kind: static constant of siteNav

Param Type Description
state Object
state.hidden boolean Whether the nav is hidden now.
state.lastY number The scroll position at the previous decision.
state.y number The scroll position now.
state.pinnedFrom number The scroll position where the nav starts to stick to the top; above it the nav is in its natural place under the header, so it always shows.
state.menuOpen boolean The phone menu is open: keep the nav showing.
state.holding boolean A nav link was just followed: keep the nav showing until that scroll ends.

siteNav.currentSection ⇒ string | null

Pick the section being read: the last one whose top has passed the reading line (a third of the way down the viewport), or none above the first section.

Kind: static constant of siteNav

Param Type Description
sections Array.<{id: string, top: number}> Each section's id and its top relative to the viewport, in page order.
readingLine number Distance from the top of the viewport, in px.

siteNav.initSiteNav ⇒ Object | null

Wire up the nav.

Kind: static constant of siteNav
Returns: Object | null - null when the page has no nav.

Param Type
document Document
window Window

roleTitles

The typing effect on the homepage's role line, run by title-switcher (https://www.npmjs.com/package/title-switcher).

The page's real text stays put: the role line's static "Full-Stack Engineer" is what screen readers, applicant tracking systems and visitors without JavaScript get. The lines to type live in a <template>, which browsers don't render and readers don't read. This module copies them into an aria-hidden span, tucks the static text away visually (data-animated on the line switches its group-data-animated:sr-only class on), and starts the typing. When the visitor's system asks for reduced motion, nothing changes at all.

roleTitles.timing

Typing speed (ms per character) and the cursor's blink interval (ms), passed to title-switcher.

Kind: static constant of roleTitles

roleTitles.startRoleTitles ⇒ Object | null

Start the typing effect, unless something it needs is missing or the visitor prefers reduced motion.

Kind: static constant of roleTitles
Returns: Object | null - The running TitleSwitcher, or null when it didn't start.

Param Type Default Description
document Document
window Window
[TitleSwitcher] function window.TitleSwitcher The title-switcher class (swappable in tests).

titleSwitcherSandbox

The title-switcher sandbox on /projects/: visitors type their own lines, set the speed and order, and watch title-switcher (https://www.npmjs.com/package/title-switcher) type them out.

title-switcher has no "stop": pause() only stops it from moving to the next line. So each (re)start builds a fresh container on the stage and lets the old instance finish in the background, detached and unseen. The lines become text nodes (never HTML), so whatever a visitor types is shown, not run.

titleSwitcherSandbox.markup

The sandbox's controls and stage. Static markup (no visitor data goes in here), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them. Rendered into each <div data-sandbox='title-switcher'>, so /projects/ and the title-switcher page share one sandbox.

Kind: static constant of titleSwitcherSandbox

titleSwitcherSandbox.parseLines ⇒ Array.<string>

The visitor's lines: one per row, trimmed, blank rows dropped.

Kind: static constant of titleSwitcherSandbox

Param Type
text string

titleSwitcherSandbox.buildTitles ⇒ HTMLElement

Build a fresh title container (one span per line) for title-switcher to animate.

Kind: static constant of titleSwitcherSandbox

Param Type
document Document
lines Array.<string>

titleSwitcherSandbox.initTitleSwitcherSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='title-switcher'> and wire it up. One sandbox per page (its ids are fixed). Starts on its own unless the visitor prefers reduced motion; then it waits for Start.

Kind: static constant of titleSwitcherSandbox
Returns: Object | null - null when the sandbox isn't on the page.

Param Type Default
document Document
window Window
[TitleSwitcher] function window.TitleSwitcher

siFuncionaSandbox

The si-funciona sandbox: pick a function, edit its inputs, and see the call and its result update as you type. Runs the real library (https://www.npmjs.com/package/si-funciona), loaded as window.siFunciona.

Inputs are read as text, numbers or JSON (JSON.parse, never eval), and results are shown with textContent, so nothing a visitor types is run as code. Rendered into each <div data-sandbox='si-funciona'>, so /projects/ and the si-funciona page share one sandbox.

siFuncionaSandbox.functions : Array.<{group: string, name: string, label: (string|undefined), chip: (string|undefined), about: string, inputs: Array.<{label: string, kind: string, value: string}>, run: (function()|undefined)}>

The functions on offer, grouped as in the picker. Each input has a kind: 'text', 'number', 'numbers' or 'json'. An entry can show several functions at once with its own run, a label for the call line and a short chip for its button.

Kind: static constant of siFuncionaSandbox

siFuncionaSandbox.parseInput ⇒ *

Turn one input's text into its argument.

Kind: static constant of siFuncionaSandbox
Throws:

  • Error For JSON that doesn't parse, or text that isn't a number.
Param Type Description
kind string 'text', 'number', 'numbers' (a comma-separated list of whole numbers) or 'json'
text string

siFuncionaSandbox.formatValue ⇒ string

Show a value the way it would read in code: strings quoted, everything else as indented JSON.

Kind: static constant of siFuncionaSandbox

Param Type
value *

siFuncionaSandbox.runFunction ⇒ Object

Run a function from the list with the given input texts.

Kind: static constant of siFuncionaSandbox

Param Type Description
library Object The si-funciona exports.
fn Object
texts Array.<string> One per input.

siFuncionaSandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of siFuncionaSandbox

siFuncionaSandbox.queueMarkup

The queueTimeout demo's markup (static), shown below the function explorer.

Kind: static constant of siFuncionaSandbox

siFuncionaSandbox.queueSteps

The queue demo's starting steps: a long delay first, so the two orders differ.

Kind: static constant of siFuncionaSandbox

siFuncionaSandbox.expectedOrder ⇒ Object

When each step fires: all at once from the start (setTimeout), or each after the one before (queueTimeout).

Kind: static constant of siFuncionaSandbox
Returns: Object - Each in firing order.

Param Type
steps Array.<{label: string, delay: number}>

siFuncionaSandbox.initQueueDemo ⇒ Object

Wire up the queueTimeout demo inside an already rendered sandbox.

Kind: static constant of siFuncionaSandbox

Param Type Description
document Document
window Window
library Object The si-funciona exports (for queueTimeout).

siFuncionaSandbox.initSiFuncionaSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='si-funciona'> and wire it up. One per page (fixed ids).

Kind: static constant of siFuncionaSandbox
Returns: Object | null - null when the sandbox isn't on the page.

Param Type Default
document Document
window Window
[library] Object window.siFunciona

pseudoDomSandbox

The pseudo-dom sandbox, "same question, two DOMs": pseudo-dom imitates the browser's DOM for tests in Node, so the page asks it and the real browser DOM the same questions (CSS selectors, then a few predefined changes) and shows both answers side by side. Runs the real library (pseudo-dom), loaded as window.pseudoDom.

The real side parses visitor HTML in a separate, inert document (document.implementation.createHTMLDocument), where scripts and handlers like onerror never run; pseudo-dom never runs anything either. Rendered into each <div data-sandbox='pseudo-dom'>, so /projects/ and the pseudo-dom page share one sandbox.

pseudoDomSandbox.exampleHtml

Starting HTML to try.

Kind: static constant of pseudoDomSandbox

pseudoDomSandbox.suggestedSelectors

Selectors to try with one tap.

Kind: static constant of pseudoDomSandbox

pseudoDomSandbox.changes : Object.<string, {label: string, code: string, run: function(Element, Document, Array.<string>): void}>

The predefined changes: each runs the same code on a root element of either DOM, and may log what it saw.

Kind: static constant of pseudoDomSandbox

pseudoDomSandbox.matchTexts ⇒ Array.<string>

The text of each match, for comparing and showing.

Kind: static constant of pseudoDomSandbox

Param Type
root Element
selector string

pseudoDomSandbox.normalizeHtml ⇒ string

HTML with formatting whitespace between tags removed, so both DOMs' output compares fairly.

Kind: static constant of pseudoDomSandbox

Param Type
html string

pseudoDomSandbox.prettyHtml ⇒ string

Indent HTML one tag per line, for reading the two results side by side.

Kind: static constant of pseudoDomSandbox

Param Type
html string

pseudoDomSandbox.buildRoots ⇒ Object

Two fresh roots holding the same HTML: one in pseudo-dom, one in an inert real document.

Kind: static constant of pseudoDomSandbox

Param Type Description
library Object The pseudo-dom exports.
document Document The page's document (for createHTMLDocument).
html string

pseudoDomSandbox.compareQuery ⇒ Object

Ask both DOMs the same selector.

Kind: static constant of pseudoDomSandbox

Param Type
library Object
document Document
html string
selector string

pseudoDomSandbox.compareChange ⇒ Object

Run a predefined change on both DOMs.

Kind: static constant of pseudoDomSandbox

Param Type Description
library Object
document Document
html string
name string A key of changes.

pseudoDomSandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of pseudoDomSandbox

pseudoDomSandbox.initPseudoDomSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='pseudo-dom'> and wire it up. One per page.

Kind: static constant of pseudoDomSandbox

Param Type Default
document Document
window Window
[library] Object window.pseudoDom

matrixDomSandbox

The matrix-dom sandbox: a 10 by 10 board to try the grid geometry Battleship is built on. Draw a line between two cells, see a cell's neighbours, or place a fleet the way Battleship does. Runs the real library (matrix-dom), loaded as window.matrixDom: on /projects/ the site's own bundle already defines it, and the matrix-dom page loads it from browser/vendor.

Rendered into each <div data-sandbox='matrix-dom'>, so /projects/ and the matrix-dom page share one sandbox.

matrixDomSandbox.size

The board's size, and the fleet Battleship places (ship lengths, longest first).

Kind: static constant of matrixDomSandbox

matrixDomSandbox.modes

The modes, with one line on what each shows.

Kind: static constant of matrixDomSandbox

matrixDomSandbox.key ⇒ string

A point's key, for sets and lookups.

Kind: static constant of matrixDomSandbox

Param Type
point Object

matrixDomSandbox.formatPoint ⇒ string

A point as it reads in code.

Kind: static constant of matrixDomSandbox

Param Type
point Object

matrixDomSandbox.validPlacements ⇒ Array.<Array.<{x: number, y: number}>>

Every place a ship of this length could go without crossing an occupied cell: Battleship's validPlacements.

Kind: static constant of matrixDomSandbox
Returns: Array.<Array.<{x: number, y: number}>> - Each placement as its cells.

Param Type Description
library Object The matrix-dom exports.
matrix Object
length number
occupied Set.<string> Keys of cells already taken.

matrixDomSandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of matrixDomSandbox

matrixDomSandbox.initMatrixDomSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='matrix-dom'> and wire it up. One per page.

Kind: static constant of matrixDomSandbox

Param Type Default
document Document
window Window
[library] Object window.matrixDom

jsonFsQuerySandbox

The json-fs-query sandbox: a small library database (authors, books, members, loans) kept as JSON files on this site, queried in the browser by the real library (json-fs-query, window.jsonFsQuery) over fetch(). Pick a question to load its query, edit it, or write your own read. Writes only work in Node, and json-fs-query says so.

Results are drawn with textContent, so whatever a query returns is shown, not run. Rendered into each <div data-sandbox='json-fs-query'>, so /projects/ and the json-fs-query page share one sandbox. The database is built by scripts/build-library-database.mjs (with json-fs-query itself).

jsonFsQuerySandbox.databasePath

Where the database lives on this site.

Kind: static constant of jsonFsQuerySandbox

jsonFsQuerySandbox.libraryDate

The library's "today", so overdue loans stay overdue.

Kind: static constant of jsonFsQuerySandbox

jsonFsQuerySandbox.records

The records, with their sizes, for the browse buttons.

Kind: static constant of jsonFsQuerySandbox

jsonFsQuerySandbox.questions

Questions to load with one tap: each shows off a part of the query language.

Kind: static constant of jsonFsQuerySandbox

jsonFsQuerySandbox.cellText ⇒ string

A cell's text: arrays of rows become a compact list (each row's values joined), other values as they read.

Kind: static constant of jsonFsQuerySandbox

Param Type
value *

jsonFsQuerySandbox.columnsOf ⇒ Array.<string>

The columns for some rows: every key, in the order first seen.

Kind: static constant of jsonFsQuerySandbox

Param Type
rows Array.<Object>

jsonFsQuerySandbox.isGrouped ⇒ boolean

Whether a result is grouped (group by returns groups with field, value and rows or groups).

Kind: static constant of jsonFsQuerySandbox

Param Type
result Array

jsonFsQuerySandbox.buildTable ⇒ HTMLElement

Build a table element for rows (text only).

Kind: static constant of jsonFsQuerySandbox

Param Type
document Document
rows Array.<Object>

jsonFsQuerySandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of jsonFsQuerySandbox

jsonFsQuerySandbox.initJsonFsQuerySandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='json-fs-query'> and wire it up. One per page.

Kind: static constant of jsonFsQuerySandbox

Param Type Default
document Document
window Window
[library] Object window.jsonFsQuery

jsonDomSandbox

The json-dom sandbox: describe a piece of page as JSON (json-dom's own item format) and watch json-dom render it, next to the HTML it produced. Runs the real library (json-dom), loaded as window.jsonDom: on /projects/ the site's own bundle defines it, and the json-dom page loads it from browser/vendor.

Visitor JSON goes through an allowlist first: only plain content tags and a few safe attributes render, so nothing like onclick, <script> or a javascript: link can run. Whatever was dropped is listed. Rendered into each <div data-sandbox='json-dom'>, so /projects/ and the json-dom page share one sandbox.

jsonDomSandbox.allowedTags

The tags a visitor's structure may use. '#text' is a text node (its text goes in nodeValue).

Kind: static constant of jsonDomSandbox

jsonDomSandbox.allowedAttributes

The attributes a visitor's structure may set.

Kind: static constant of jsonDomSandbox

jsonDomSandbox.examples

Starting structures to load with one tap.

Kind: static constant of jsonDomSandbox

jsonDomSandbox.isSafeHref ⇒ boolean

Whether a link is safe to keep: http(s), mailto, a page path or an anchor (never javascript: or data:).

Kind: static constant of jsonDomSandbox

Param Type
href string

jsonDomSandbox.sanitize ⇒ Object | null

Keep only allowed tags, attributes and links, recording what was dropped.

Kind: static constant of jsonDomSandbox
Returns: Object | null - A clean item, or null when the whole item was dropped.

Param Type Default Description
item * A visitor's item (parsed JSON).
dropped Array.<string> Collects a note for each thing removed.
[where] string "'root'" Where in the structure this item is, for the notes.

jsonDomSandbox.prettyHtml ⇒ string

Indent HTML one tag per line, for reading (whitespace inside text is left alone).

Kind: static constant of jsonDomSandbox

Param Type
html string

jsonDomSandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of jsonDomSandbox

jsonDomSandbox.initJsonDomSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='json-dom'> and wire it up. One per page.

Kind: static constant of jsonDomSandbox

Param Type Default
document Document
window Window
[library] Object window.jsonDom

collectYourStuffSandbox

The collect-your-stuff sandbox: try a Queue, a Stack and a (doubly) linked list one call at a time, and see the collection redrawn after each call from the library's own state. Runs the real library (https://www.npmjs.com/package/collect-your-stuff), loaded as window.collectYourStuff.

Values are shown with textContent, so whatever a visitor types is displayed, not run. Rendered into each <div data-sandbox='collect-your-stuff'>, so /projects/ and the collect-your-stuff page share one sandbox.

collectYourStuffSandbox.modes

The modes, their operations, and one line on how each collection behaves.

Kind: static constant of collectYourStuffSandbox

collectYourStuffSandbox.nextLabel ⇒ string

The next label to suggest: A, B, … Z, then AA, AB, …

Kind: static constant of collectYourStuffSandbox

Param Type Description
count number How many have been suggested so far.

collectYourStuffSandbox.readList ⇒ Array.<*>

Read a linked list's values in order.

Kind: static constant of collectYourStuffSandbox

Param Type Description
list Object Anything with forEach(node => …) over nodes with .data.

collectYourStuffSandbox.formatValue ⇒ string

Show a value the way it reads in code.

Kind: static constant of collectYourStuffSandbox

Param Type
value *

collectYourStuffSandbox.createState ⇒ Object

A fresh state: an empty Queue, Stack and list, built with the library's own classes.

Kind: static constant of collectYourStuffSandbox

Param Type Default Description
library Object The collect-your-stuff exports.
[doubly] boolean false A DoublyLinkedList instead of a LinkedList.

collectYourStuffSandbox.runOperation ⇒ Object

Run one operation on the state, through the library.

Kind: static constant of collectYourStuffSandbox
Returns: Object - The call as it reads in code, what it returned, and a message when it can't run (no list item selected).

Param Type Description
state Object
mode string 'queue', 'stack' or 'list'
operation string One of that mode's operations.
value string The value to add (for adding operations).
selected number | null The selected list position (for insertAfter and remove).

collectYourStuffSandbox.contents ⇒ Array.<*>

The values to draw for a mode: queue front first, stack top first, list first to last.

Kind: static constant of collectYourStuffSandbox

Param Type
state Object
mode string

collectYourStuffSandbox.markup

The sandbox's static markup (no visitor data), styled with Tailwind classes; assets/tailwind/site.css scans this folder for them.

Kind: static constant of collectYourStuffSandbox

collectYourStuffSandbox.initCollectYourStuffSandbox ⇒ Object | null

Render the sandbox into the page's <div data-sandbox='collect-your-stuff'> and wire it up. One per page.

Kind: static constant of collectYourStuffSandbox

Param Type Default
document Document
window Window
[library] Object window.collectYourStuff

Kind: global constant

tileContent(project) ⇒ DomItem

A single tile's content: an icon badge plus title/description, all wrapped in one clickable link.

Kind: global function

Param Type
project Object

projectGrid(projects) ⇒ DomItem

Builds a matrix-dom grid of project tiles, one per entry in projects, in display order (row-major: left to right, then top to bottom). Any trailing columns left over when projects.length doesn't divide evenly into columns are left empty (marked 'tile-empty', and invisible).

Kind: global function

Param Type
projects Array.<{title: string, href: string, description: string}>

ProjectTile : object

The real projects shown in the homepage's project grid, in display order.

Kind: global typedef
Properties

Name Type Description
title string the display name
href string where the tile's single link goes (a project's own page, or GitHub when it has no page)
description string a brief description shown on the tile

About

Personal portfolio site: joshuaheagle.com

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages