├── .gitignore ├── .prettierignore ├── 11ty ├── bundler.js ├── css │ ├── config.js │ └── index.js ├── filters │ ├── dates.js │ ├── index.js │ └── text.js ├── image.js ├── markdown.js ├── pluralize.js └── shortcodes │ ├── codepen.js │ ├── index.js │ └── media.js ├── LICENSE ├── README.md ├── eleventy.config.js ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── favicon │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.svg │ ├── maskable_icon.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── safari-pinned-tab.svg ├── fonts │ ├── JetBrainsMono-Regular.woff2 │ └── Tanker-Regular.woff2 ├── icons │ ├── arrow-square-out.svg │ ├── avatar.svg │ ├── bluesky.svg │ ├── browsers.svg │ ├── buymeacoffee.svg │ ├── calendar.svg │ ├── codepen.svg │ ├── cursor-click.svg │ ├── dropper.svg │ ├── email.svg │ ├── file.svg │ ├── github.svg │ ├── heart.svg │ ├── home.svg │ ├── linkedin-fill.svg │ ├── linkedin.svg │ ├── map-pin.svg │ ├── mastodon.svg │ ├── monitor.svg │ ├── moon.svg │ ├── music-notes.svg │ ├── question.svg │ ├── snowflake.svg │ ├── spotify.svg │ ├── sun.svg │ ├── timer.svg │ └── user.svg ├── images │ ├── center-align-top-row-grid.png │ ├── codepen │ │ ├── BavjjGE.png │ │ ├── ExjwZZg.png │ │ ├── GRBJLwE.png │ │ ├── GRJPXLa.png │ │ ├── JjOaabp.png │ │ ├── JjRYaZw.png │ │ ├── JjYLLNR.png │ │ ├── LYNNYyQ.png │ │ ├── MWJoYxv.png │ │ ├── MWgbqON.png │ │ ├── NWmEMmJ.png │ │ ├── OJLMgYY.png │ │ ├── OJRLBYJ.png │ │ ├── OJrJZqR.png │ │ ├── PoOberB.png │ │ ├── PoPpKKg.png │ │ ├── PoxMPzM.png │ │ ├── VoExWp.png │ │ ├── VwLKbWJ.png │ │ ├── WRqEaV.png │ │ ├── XWJGQqy.png │ │ ├── XWbWKwL.png │ │ ├── XWxZgqx.png │ │ ├── YzPmyjx.png │ │ ├── abJEeXL.png │ │ ├── abmyQBN.png │ │ ├── bGWmExK.png │ │ ├── bGqVyxm.png │ │ ├── dwreYm.png │ │ ├── dyNQJpp.png │ │ ├── gJNPON.png │ │ ├── gORYwrg.png │ │ ├── gORePQx.png │ │ ├── gOWjwme.png │ │ ├── jOWXzrw.png │ │ ├── jOWyepg.png │ │ ├── jOzybYa.png │ │ ├── jgGxKR.png │ │ ├── joqYEj.png │ │ ├── oNLZmvV.png │ │ ├── poNGaGO.png │ │ ├── vYJWVvy.png │ │ ├── vYwKNzR.png │ │ ├── wvJPjQa.png │ │ ├── wvmjomb.png │ │ ├── xxRNRwP.png │ │ ├── xxYrpzP.png │ │ ├── xxwBLMy.png │ │ ├── yLeGbgJ.png │ │ ├── zYqYvEV.png │ │ └── zYxzQqa.png │ ├── gallery-grid-lines.png │ ├── github-template-chooser-example.jpg │ ├── headshot-bw.jpg │ ├── headshot.jpg │ └── projects-list-focus-voiceover-text.png ├── robots.txt ├── site.webmanifest ├── social │ ├── 50-50-overflow.png │ ├── a-horizontal-scroll-list-and-custom-keyboard-navigation.png │ ├── animating-with-the-flip-plugin-for-gsap.png │ ├── blog-posts.png │ ├── blog-questions-challenge.png │ ├── click-spark.png │ ├── creating-time.png │ ├── css-custom-property-fallbacks.png │ ├── css-property-new-style.png │ ├── detect-js-support-in-css.png │ ├── external-links-issue-template-options.png │ ├── full-bleed-table-scrolling.png │ ├── grid-gap-behavior.png │ ├── grid-stacks.png │ ├── handling-events-web-components.png │ ├── horizontal-scrolling-in-a-centered-max-width-container.png │ ├── inverted-media-queries-and-breakpoints.png │ ├── layout-breakouts-css-grid.png │ ├── migrating-to-eleventy.png │ ├── password-input-components.png │ ├── penguin.jpg │ ├── pixel-canvas.png │ ├── ryan-mulligan-dev.png │ ├── scroll-driven-animations.png │ ├── scroll-triggered-animations-style-queries.png │ ├── scrolling-rails-and-button-controls.png │ ├── scrollspy-nav.png │ ├── site-rebuild-v3.png │ ├── some-things-about-keyframes.png │ ├── someone-great.png │ ├── sticky-page-header-shadow-scroll.png │ ├── style-review.png │ ├── target-toggler.png │ ├── the-infinite-marquee.png │ ├── the-shape-of-runs-to-come.png │ ├── we-can-has-it-all.png │ └── website-themes-and-color-schemes.png └── videos │ ├── codepen │ ├── BavjjGE.mp4 │ ├── BavjjGE.webm │ ├── ExjwZZg.mp4 │ ├── ExjwZZg.webm │ ├── GRBJLwE.mp4 │ ├── GRBJLwE.webm │ ├── GRJPXLa.mp4 │ ├── GRJPXLa.webm │ ├── JjOaabp.mp4 │ ├── JjOaabp.webm │ ├── JjYLLNR.mp4 │ ├── JjYLLNR.webm │ ├── LYNNYyQ.mp4 │ ├── LYNNYyQ.webm │ ├── MKaVzM.mp4 │ ├── MKaVzM.webm │ ├── MWJoYxv.mp4 │ ├── MWJoYxv.webm │ ├── MWgbqON.mp4 │ ├── MWgbqON.webm │ ├── NWmEMmJ.mp4 │ ├── NWmEMmJ.webm │ ├── OJLMgYY.mp4 │ ├── OJLMgYY.webm │ ├── OJRLBYJ.mp4 │ ├── OJRLBYJ.webm │ ├── OJrJZqR.mp4 │ ├── OJrJZqR.webm │ ├── PoOberB.mp4 │ ├── PoOberB.webm │ ├── PoPpKKg.mp4 │ ├── PoPpKKg.webm │ ├── PoxMPzM.mp4 │ ├── PoxMPzM.webm │ ├── VoExWp.mp4 │ ├── VoExWp.webm │ ├── VwLKbWJ.mp4 │ ├── VwLKbWJ.webm │ ├── WRqEaV.mp4 │ ├── WRqEaV.webm │ ├── XWJGQqy.mp4 │ ├── XWJGQqy.webm │ ├── XWbWKwL.mp4 │ ├── XWbWKwL.webm │ ├── XWxZgqx.mp4 │ ├── XWxZgqx.webm │ ├── abJEeXL.mp4 │ ├── abJEeXL.webm │ ├── abmyQBN.mp4 │ ├── abmyQBN.webm │ ├── bGqVyxm.mp4 │ ├── bGqVyxm.webm │ ├── dwreYm.mp4 │ ├── dwreYm.webm │ ├── dyNQJpp.mp4 │ ├── dyNQJpp.webm │ ├── gJNPON.mp4 │ ├── gJNPON.webm │ ├── gORYwrg.mp4 │ ├── gORYwrg.webm │ ├── gORePQx.mp4 │ ├── gORePQx.webm │ ├── jOWXzrw.mp4 │ ├── jOWXzrw.webm │ ├── jOWyepg.mp4 │ ├── jOWyepg.webm │ ├── jOzybYa.mp4 │ ├── jOzybYa.webm │ ├── jgGxKR.mp4 │ ├── jgGxKR.webm │ ├── joqYEj.mp4 │ ├── joqYEj.webm │ ├── poNGaGO.mp4 │ ├── poNGaGO.webm │ ├── vYJWVvy.mp4 │ ├── vYJWVvy.webm │ ├── vYwKNzR.mp4 │ ├── vYwKNzR.webm │ ├── wvJPjQa.mp4 │ ├── wvJPjQa.webm │ ├── wvmjomb.mp4 │ ├── wvmjomb.webm │ ├── xxRNRwP.mp4 │ ├── xxRNRwP.webm │ ├── xxYrpzP.mp4 │ ├── xxYrpzP.webm │ ├── xxwBLMy.mp4 │ ├── xxwBLMy.webm │ ├── zYqYvEV.mp4 │ ├── zYqYvEV.webm │ ├── zYxzQqa.mp4 │ └── zYxzQqa.webm │ ├── detect-js-support-in-css.jpg │ ├── detect-js-support-in-css.mp4 │ ├── detect-js-support-in-css.webm │ ├── scroll-driven-animations-1.mp4 │ ├── scroll-driven-animations-1.png │ ├── scroll-driven-animations-1.webm │ ├── scroll-driven-animations-2.mp4 │ ├── scroll-driven-animations-2.png │ ├── scroll-driven-animations-2.webm │ ├── shiny-cta-angle-offset.mp4 │ ├── shiny-cta-angle-offset.png │ └── shiny-cta-angle-offset.webm └── src ├── _data ├── articles.json ├── codepens.json ├── meta.js ├── navigation.json ├── resume.json ├── spotify.js ├── themes.json └── weather.js ├── _includes ├── article-list.webc ├── info-cta.webc ├── inline-svg.webc ├── last-played-track.webc ├── lite-youtube.webc ├── page-timer.webc ├── say-hey-hey.webc ├── say-my-name.webc ├── scroll-pen.webc ├── site-footer.webc ├── site-header.webc ├── target-toggler.webc └── theme-machine.webc ├── _layouts ├── base.webc └── post.webc ├── blog ├── 50-50-overflow.md ├── blog-questions-challenge-2025.md ├── blog.json ├── click-spark.md ├── creating-time.md ├── css-custom-prop-fallbacks.md ├── css-marquee.md ├── css-property-new-style.md ├── detect-js-support-in-css.md ├── external-links-issue-template-options.md ├── full-bleed-table-scrolling.md ├── grid-gap.md ├── grid-stacks.md ├── gsap-flip-cart.md ├── handling-events-web-components.md ├── invert-media-queries.md ├── layout-breakouts.md ├── migrating-to-11ty.md ├── password-input-components.md ├── penguin.md ├── pixel-canvas.md ├── project-keyboard-navigation.md ├── scroll-driven-animations.md ├── scroll-triggered-animations-style-queries.md ├── scrollspy-nav.md ├── scrolly-rail.md ├── site-rebuild.md ├── some-things-about-keyframes.md ├── someone-great.md ├── sticky-header-scroll-shadow.md ├── style-review.md ├── target-toggler.md ├── the-shape-of-runs-to-come.md ├── themes-and-schemes.md ├── we-can-has-it-all.md └── x-scrolling-centered-max-width-container.md ├── css ├── _base.css ├── _box.css ├── _callout.css ├── _code.css ├── _cta.css ├── _dimensions.css ├── _input.css ├── _layout.css ├── _motion.css ├── _quote.css ├── _reset.css ├── _type.css ├── _utils.css ├── css.json ├── styles.css └── themes.11ty.js ├── feed.njk └── pages ├── 404.webc ├── blog.webc ├── blogroll ├── blogroll.11tydata.js ├── blogroll.11tydata.json └── blogroll.webc ├── index.webc ├── pages.json └── resume.webc /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies installed by npm 2 | node_modules 3 | 4 | # build artefacts 5 | _site 6 | .cache 7 | .vscode 8 | 9 | # secrets and errors 10 | .env 11 | .log 12 | 13 | # macOS related files 14 | .DS_Store 15 | 16 | # Local Netlify folder 17 | .netlify 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md -------------------------------------------------------------------------------- /11ty/bundler.js: -------------------------------------------------------------------------------- 1 | import { minify } from "terser"; 2 | import { transform } from "lightningcss"; 3 | import lightningcssConfig from "./css/config.js"; 4 | 5 | /** 6 | * Modify page bundle output 7 | * {@link https://github.com/11ty/eleventy-plugin-bundle#modify-the-bundle-output} 8 | */ 9 | export default [ 10 | async function (content) { 11 | if (this.type === "css") { 12 | let { code } = transform({ 13 | code: Buffer.from(content), 14 | ...lightningcssConfig, 15 | }); 16 | 17 | return code; 18 | } 19 | 20 | if (this.type === "js" && process.env.CONTEXT === "production") { 21 | const minified = await minify(content); 22 | return minified.code; 23 | } 24 | 25 | return content; 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /11ty/css/config.js: -------------------------------------------------------------------------------- 1 | import browserslist from "browserslist"; 2 | import { browserslistToTargets } from "lightningcss"; 3 | 4 | /** 5 | * Shared Lightning CSS configuration 6 | * {@link https://lightningcss.dev/docs.html} 7 | */ 8 | let mixins = new Map(); 9 | 10 | const minify = process.env.CONTEXT === "production" ? true : false; 11 | const sourceMap = false; 12 | const targets = browserslistToTargets(browserslist("> 0.2% and not dead")); 13 | const drafts = { 14 | nesting: true, 15 | customMedia: true, 16 | }; 17 | const customAtRules = { 18 | mixin: { 19 | prelude: "", 20 | body: "style-block", 21 | }, 22 | apply: { 23 | prelude: "", 24 | }, 25 | }; 26 | const visitor = { 27 | Rule: { 28 | custom: { 29 | mixin(rule) { 30 | mixins.set(rule.prelude.value, rule.body.value); 31 | return []; 32 | }, 33 | apply(rule) { 34 | return mixins.get(rule.prelude.value); 35 | }, 36 | }, 37 | }, 38 | }; 39 | 40 | export default { 41 | minify, 42 | sourceMap, 43 | targets, 44 | drafts, 45 | customAtRules, 46 | visitor, 47 | }; 48 | -------------------------------------------------------------------------------- /11ty/css/index.js: -------------------------------------------------------------------------------- 1 | import { parse } from "path"; 2 | import { bundle } from "lightningcss"; 3 | import lightningcssConfig from "./config.js"; 4 | 5 | /** 6 | * Process CSS files with Lightning CSS 7 | * {@link https://11ty.rocks/posts/process-css-with-lightningcss/#process-with-lightningcss-only-no-sass} 8 | */ 9 | export default (eleventyConfig) => { 10 | eleventyConfig.addTemplateFormats("css"); 11 | 12 | eleventyConfig.addExtension("css", { 13 | outputFileExtension: "css", 14 | compile: async function (_inputContent, inputPath) { 15 | let parsed = parse(inputPath); 16 | 17 | if (parsed.name.startsWith("_")) return; 18 | 19 | return async () => { 20 | let { code } = await bundle({ 21 | filename: inputPath, 22 | ...lightningcssConfig, 23 | }); 24 | 25 | return code; 26 | }; 27 | }, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /11ty/filters/dates.js: -------------------------------------------------------------------------------- 1 | const ordinal = (n) => { 2 | var s = ["th", "st", "nd", "rd"]; 3 | var v = n % 100; 4 | return n + (s[(v - 20) % 10] || s[v] || s[0]); 5 | }; 6 | 7 | const postDate = (dateObj, options) => { 8 | return new Intl.DateTimeFormat( 9 | "en-US", 10 | options || { 11 | year: "numeric", 12 | month: "long", 13 | day: "numeric", 14 | timeZone: "utc", 15 | } 16 | ).format(dateObj); 17 | }; 18 | 19 | const getDeployDate = () => { 20 | let zone = "America/Los_Angeles"; 21 | let d = new Date(new Date().toLocaleString("en-US", { timeZone: zone })); 22 | let day = d.getDate(); 23 | let hour = d.getHours(); 24 | let month = new Intl.DateTimeFormat("en-US", { month: "long", timeZone: zone }).format(d); 25 | 26 | let date = `${month} ${ordinal(day)}`; 27 | let timeOfDay = (hour < 12 && "morning") || (hour < 18 && "afternoon") || "evening"; 28 | 29 | return { 30 | timeOfDay, 31 | date, 32 | }; 33 | }; 34 | 35 | export default { 36 | postDate, 37 | getDeployDate, 38 | }; 39 | -------------------------------------------------------------------------------- /11ty/filters/index.js: -------------------------------------------------------------------------------- 1 | import dates from "./dates.js"; 2 | import text from "./text.js"; 3 | import pluralize from "../pluralize.js"; 4 | import meta from "../../src/_data/meta.js"; 5 | 6 | const getVarFromString = (varName) => { 7 | return this.getVariables()[varName]; 8 | }; 9 | 10 | const getIcon = (name) => { 11 | return `/src/assets/icons/${name}.svg`; 12 | }; 13 | 14 | const limit = (array, limit) => array.slice(0, limit); 15 | 16 | const obfuscate = (str) => { 17 | const chars = []; 18 | for (var i = str.length - 1; i >= 0; i--) { 19 | chars.unshift(["&#", str[i].charCodeAt(), ";"].join("")); 20 | } 21 | return chars.join(""); 22 | }; 23 | 24 | const isExternalUrl = (str) => { 25 | try { 26 | let url = new URL(str); 27 | return url.host !== meta.domain; 28 | } catch (err) { 29 | return false; 30 | } 31 | }; 32 | 33 | const readingTime = (content) => { 34 | const arr = content.split(" "); 35 | const count = arr.length; 36 | const wpm = 225; 37 | const time = Math.ceil(count / wpm); 38 | 39 | return pluralize(time, "minute", "minutes"); 40 | }; 41 | 42 | const removeHttp = (url) => url.replace(/^https?:\/\//, ""); 43 | 44 | const truncateAfterWord = (str, max) => { 45 | if (str.length <= max) return str; 46 | return str.substr(0, str.lastIndexOf(" ", max)) + "..."; 47 | }; 48 | 49 | const filters = { 50 | ...dates, 51 | ...text, 52 | getVarFromString, 53 | getIcon, 54 | isExternalUrl, 55 | limit, 56 | obfuscate, 57 | readingTime, 58 | removeHttp, 59 | truncateAfterWord, 60 | }; 61 | 62 | export default (eleventyConfig) => { 63 | return Object.keys(filters).forEach((filter) => { 64 | eleventyConfig.addFilter(filter, filters[filter]); 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /11ty/filters/text.js: -------------------------------------------------------------------------------- 1 | const textWrap = (text, index) => 2 | ``; 3 | 4 | const splitLetters = (text) => [...text].map(textWrap).join(""); 5 | 6 | const splitWords = (text) => text.split(" ").map(textWrap).join(""); 7 | 8 | export default { 9 | splitLetters, 10 | splitWords, 11 | }; 12 | -------------------------------------------------------------------------------- /11ty/image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Eleventy Image configuration 3 | * {@link https://www.11ty.dev/docs/plugins/image/} 4 | */ 5 | export default { 6 | formats: ["webp", "jpeg"], 7 | urlPath: "/images/", 8 | defaultAttributes: { 9 | loading: "lazy", 10 | decoding: "async", 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /11ty/markdown.js: -------------------------------------------------------------------------------- 1 | import markdown from "markdown-it"; 2 | import markdownAnchor from "markdown-it-anchor"; 3 | import markdownAttrs from "markdown-it-attrs"; 4 | import markdownContainer from "markdown-it-container"; 5 | import slugify from "@sindresorhus/slugify"; 6 | 7 | export default (eleventyConfig) => { 8 | eleventyConfig.setLibrary( 9 | "md", 10 | markdown({ 11 | html: true, 12 | breaks: true, 13 | linkify: true, 14 | }) 15 | ); 16 | 17 | eleventyConfig.amendLibrary("md", (library) => { 18 | library.use(markdownAttrs); 19 | library.use(markdownAnchor, { 20 | level: 2, 21 | tabIndex: false, 22 | slugify: (s) => slugify(s), 23 | }); 24 | library.use(markdownContainer, "callout", { 25 | render: function (tokens, i) { 26 | return tokens[i].nesting === 1 ? ``; 27 | }, 28 | }); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /11ty/pluralize.js: -------------------------------------------------------------------------------- 1 | const pluralRules = new Intl.PluralRules("en-US"); 2 | 3 | const pluralize = (count, singular, plural) => { 4 | const grammaticalNumber = pluralRules.select(count); 5 | switch (grammaticalNumber) { 6 | case "one": 7 | return count + " " + singular; 8 | case "other": 9 | return count + " " + plural; 10 | default: 11 | throw new Error("Unknown: " + grammaticalNumber); 12 | } 13 | }; 14 | 15 | export default pluralize; 16 | -------------------------------------------------------------------------------- /11ty/shortcodes/codepen.js: -------------------------------------------------------------------------------- 1 | const codepen = (url, height = 600, defaultTab = "result", preview = false) => { 2 | const pathname = new URL(url).pathname.split("/"); 3 | const user = pathname[1]; 4 | const hash = pathname[pathname.length - 1]; 5 | 6 | return ` 7 |

8 | 9 | 10 | Open CodePen demo 11 | 12 |

13 | 14 | `; 15 | }; 16 | 17 | export default codepen; 18 | -------------------------------------------------------------------------------- /11ty/shortcodes/index.js: -------------------------------------------------------------------------------- 1 | import codepen from "./codepen.js"; 2 | import { image, video } from "./media.js"; 3 | import meta from "../../src/_data/meta.js"; 4 | 5 | const metaTitle = (title) => title || meta.title; 6 | const metaDescription = (description) => description || meta.description; 7 | const metaOGImage = (source) => meta.url + (source || meta.ogImage); 8 | 9 | const mailToPath = (subject) => { 10 | if (!subject) { 11 | subject = "You are wonderful and I had to tell you"; 12 | } 13 | return `mailto:h%65y@%72%79%61%6E%6D%75%6Clig%61n.dev?subject=${subject}`; 14 | }; 15 | 16 | const shortcodes = { 17 | codepen, 18 | image, 19 | video, 20 | metaTitle, 21 | metaDescription, 22 | metaOGImage, 23 | mailToPath, 24 | }; 25 | 26 | export default (eleventyConfig) => { 27 | return Object.keys(shortcodes).forEach((shortcode) => { 28 | eleventyConfig.addShortcode(shortcode, shortcodes[shortcode]); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /11ty/shortcodes/media.js: -------------------------------------------------------------------------------- 1 | import Image, { generateHTML } from "@11ty/eleventy-img"; 2 | import config from "../image.js"; 3 | 4 | const image = async ( 5 | src, 6 | alt, 7 | caption, 8 | attrs = {}, 9 | widths = [100, 400, 800, 1280], 10 | formats = ["webp", "jpeg"], 11 | sizes = "100vw" 12 | ) => { 13 | let metadata = await Image(src, { 14 | ...config, 15 | widths, 16 | formats, 17 | outputDir: "_site/images", 18 | urlPath: "/images", 19 | }); 20 | 21 | let imageAttributes = { 22 | alt, 23 | sizes, 24 | ...attrs, 25 | }; 26 | 27 | function wrapFigure(output, caption) { 28 | return `
${output}
${caption}
`; 29 | } 30 | 31 | const pictureOutput = generateHTML(metadata, imageAttributes); 32 | 33 | return caption ? wrapFigure(pictureOutput, caption) : pictureOutput; 34 | }; 35 | 36 | const video = (src, caption) => { 37 | const html = ` 38 | 43 | `; 44 | 45 | if (caption) { 46 | return `
${html}
${caption}
`; 47 | } 48 | 49 | return html; 50 | }; 51 | 52 | export { image, video }; 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ryan Mulligan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Personal Website 2 | 3 | [![Netlify Status](https://api.netlify.com/api/v1/badges/1bb399ed-b1fc-4359-b744-5d9fd2d2da52/deploy-status)](https://app.netlify.com/sites/ryanmulligan/deploys) 4 | 5 | Now it's personal 👀 [ryanmulligan.dev](https://ryanmulligan.dev) 6 | 7 | Static site built with 11ty + WebC and deployed on Netlify ❤️ 8 | -------------------------------------------------------------------------------- /eleventy.config.js: -------------------------------------------------------------------------------- 1 | import pluginBundler from "@11ty/eleventy-plugin-bundle"; 2 | import pluginRss from "@11ty/eleventy-plugin-rss"; 3 | import pluginSyntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; 4 | import pluginWebc from "@11ty/eleventy-plugin-webc"; 5 | import { eleventyImagePlugin } from "@11ty/eleventy-img"; 6 | import embeds from "eleventy-plugin-embed-everything"; 7 | 8 | /** Plugin configuration */ 9 | import bundler from "./11ty/bundler.js"; 10 | import image from "./11ty/image.js"; 11 | import css from "./11ty/css/index.js"; 12 | import filters from "./11ty/filters/index.js"; 13 | import markdown from "./11ty/markdown.js"; 14 | import shortcodes from "./11ty/shortcodes/index.js"; 15 | 16 | export default function (eleventyConfig) { 17 | eleventyConfig.addPlugin(pluginBundler, bundler); 18 | eleventyConfig.addPlugin(eleventyImagePlugin, image); 19 | eleventyConfig.addPlugin(pluginRss); 20 | eleventyConfig.addPlugin(pluginSyntaxHighlight); 21 | eleventyConfig.addPlugin(pluginWebc, { 22 | components: ["src/_includes/**/*.webc", "npm:@11ty/eleventy-img/*.webc"], 23 | }); 24 | eleventyConfig.addPlugin(css); 25 | eleventyConfig.addPlugin(filters); 26 | eleventyConfig.addPlugin(markdown); 27 | eleventyConfig.addPlugin(shortcodes); 28 | eleventyConfig.addPlugin(embeds); 29 | 30 | eleventyConfig.addLayoutAlias("base", "base.webc"); 31 | eleventyConfig.addLayoutAlias("post", "post.webc"); 32 | 33 | eleventyConfig.addPassthroughCopy({ public: "/" }); 34 | 35 | eleventyConfig.setServerOptions({ 36 | showAllHosts: false, 37 | }); 38 | 39 | return { 40 | dir: { 41 | input: "src", 42 | output: "_site", 43 | data: "_data", 44 | includes: "_includes", 45 | layouts: "_layouts", 46 | }, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[headers]] 2 | for = "/*" 3 | [headers.values] 4 | Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' *.codepen.io *.codepenassets.com *.withcabin.com *.netlify.app; connect-src 'self' *.withcabin.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; frame-src codepen.io *.codepen.io *.codepenassets.com *.netlify.app app.netlify.com *.youtube.com *.youtube-nocookie.com; base-uri 'none'; manifest-src 'self' data:" 5 | 6 | [build] 7 | command = "npm run build" 8 | 9 | [functions] 10 | node_bundler = "esbuild" 11 | 12 | [[redirects]] 13 | from = "/blog/feed.xml" 14 | to = "/feed.xml" 15 | status = 301 16 | force = true 17 | 18 | [[plugins]] 19 | package = "netlify-plugin-cache" 20 | 21 | [plugins.inputs] 22 | paths = [".cache"] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ryan-mulligan-dev", 3 | "version": "3.0.0", 4 | "type": "module", 5 | "description": "Personal website", 6 | "scripts": { 7 | "build": "eleventy", 8 | "clean": "rm -rf _site && rm -rf .cache", 9 | "start": "eleventy --serve --quiet" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hexagoncircle/ryan-mulligan-dev.git" 14 | }, 15 | "author": { 16 | "name": "Ryan Mulligan", 17 | "email": "hey@ryanmulligan.dev", 18 | "url": "https://ryanmulligan.dev" 19 | }, 20 | "license": "ISC", 21 | "dependencies": { 22 | "@11ty/eleventy": "^3.0.0", 23 | "@11ty/eleventy-fetch": "^4.0.1", 24 | "@11ty/eleventy-img": "^4.0.2", 25 | "@11ty/eleventy-plugin-bundle": "^2.0.2", 26 | "@11ty/eleventy-plugin-rss": "^2.0.2", 27 | "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", 28 | "@11ty/eleventy-plugin-webc": "^0.11.2", 29 | "browserslist": "^4.23.3", 30 | "click-spark": "^2.1.0", 31 | "dotenv": "^16.4.5", 32 | "eleventy-plugin-embed-everything": "^1.18.2", 33 | "lightningcss": "^1.26.0", 34 | "lodash": "^4.17.21", 35 | "markdown-it": "^14.1.0", 36 | "markdown-it-anchor": "^9.0.1", 37 | "markdown-it-attrs": "^4.1.6", 38 | "markdown-it-container": "^4.0.0", 39 | "rss-parser": "^3.13.0", 40 | "sanitize-html": "^2.13.0", 41 | "svgo": "^3.3.2", 42 | "terser": "^5.31.5" 43 | }, 44 | "devDependencies": { 45 | "netlify-plugin-cache": "^1.0.3" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #b91d47 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/favicon/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/maskable_icon.png -------------------------------------------------------------------------------- /public/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /public/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /public/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /public/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /public/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/fonts/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/fonts/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/Tanker-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/fonts/Tanker-Regular.woff2 -------------------------------------------------------------------------------- /public/icons/arrow-square-out.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/bluesky.svg: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /public/icons/browsers.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/buymeacoffee.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 25 | 36 | 47 | 55 | 63 | 64 | -------------------------------------------------------------------------------- /public/icons/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/codepen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/cursor-click.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/dropper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/icons/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/icons/linkedin-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/map-pin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/mastodon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/monitor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/music-notes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/snowflake.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/spotify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/sun.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/timer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/center-align-top-row-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/center-align-top-row-grid.png -------------------------------------------------------------------------------- /public/images/codepen/BavjjGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/BavjjGE.png -------------------------------------------------------------------------------- /public/images/codepen/ExjwZZg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/ExjwZZg.png -------------------------------------------------------------------------------- /public/images/codepen/GRBJLwE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/GRBJLwE.png -------------------------------------------------------------------------------- /public/images/codepen/GRJPXLa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/GRJPXLa.png -------------------------------------------------------------------------------- /public/images/codepen/JjOaabp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/JjOaabp.png -------------------------------------------------------------------------------- /public/images/codepen/JjRYaZw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/JjRYaZw.png -------------------------------------------------------------------------------- /public/images/codepen/JjYLLNR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/JjYLLNR.png -------------------------------------------------------------------------------- /public/images/codepen/LYNNYyQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/LYNNYyQ.png -------------------------------------------------------------------------------- /public/images/codepen/MWJoYxv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/MWJoYxv.png -------------------------------------------------------------------------------- /public/images/codepen/MWgbqON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/MWgbqON.png -------------------------------------------------------------------------------- /public/images/codepen/NWmEMmJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/NWmEMmJ.png -------------------------------------------------------------------------------- /public/images/codepen/OJLMgYY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/OJLMgYY.png -------------------------------------------------------------------------------- /public/images/codepen/OJRLBYJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/OJRLBYJ.png -------------------------------------------------------------------------------- /public/images/codepen/OJrJZqR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/OJrJZqR.png -------------------------------------------------------------------------------- /public/images/codepen/PoOberB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/PoOberB.png -------------------------------------------------------------------------------- /public/images/codepen/PoPpKKg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/PoPpKKg.png -------------------------------------------------------------------------------- /public/images/codepen/PoxMPzM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/PoxMPzM.png -------------------------------------------------------------------------------- /public/images/codepen/VoExWp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/VoExWp.png -------------------------------------------------------------------------------- /public/images/codepen/VwLKbWJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/VwLKbWJ.png -------------------------------------------------------------------------------- /public/images/codepen/WRqEaV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/WRqEaV.png -------------------------------------------------------------------------------- /public/images/codepen/XWJGQqy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/XWJGQqy.png -------------------------------------------------------------------------------- /public/images/codepen/XWbWKwL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/XWbWKwL.png -------------------------------------------------------------------------------- /public/images/codepen/XWxZgqx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/XWxZgqx.png -------------------------------------------------------------------------------- /public/images/codepen/YzPmyjx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/YzPmyjx.png -------------------------------------------------------------------------------- /public/images/codepen/abJEeXL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/abJEeXL.png -------------------------------------------------------------------------------- /public/images/codepen/abmyQBN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/abmyQBN.png -------------------------------------------------------------------------------- /public/images/codepen/bGWmExK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/bGWmExK.png -------------------------------------------------------------------------------- /public/images/codepen/bGqVyxm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/bGqVyxm.png -------------------------------------------------------------------------------- /public/images/codepen/dwreYm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/dwreYm.png -------------------------------------------------------------------------------- /public/images/codepen/dyNQJpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/dyNQJpp.png -------------------------------------------------------------------------------- /public/images/codepen/gJNPON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/gJNPON.png -------------------------------------------------------------------------------- /public/images/codepen/gORYwrg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/gORYwrg.png -------------------------------------------------------------------------------- /public/images/codepen/gORePQx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/gORePQx.png -------------------------------------------------------------------------------- /public/images/codepen/gOWjwme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/gOWjwme.png -------------------------------------------------------------------------------- /public/images/codepen/jOWXzrw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/jOWXzrw.png -------------------------------------------------------------------------------- /public/images/codepen/jOWyepg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/jOWyepg.png -------------------------------------------------------------------------------- /public/images/codepen/jOzybYa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/jOzybYa.png -------------------------------------------------------------------------------- /public/images/codepen/jgGxKR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/jgGxKR.png -------------------------------------------------------------------------------- /public/images/codepen/joqYEj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/joqYEj.png -------------------------------------------------------------------------------- /public/images/codepen/oNLZmvV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/oNLZmvV.png -------------------------------------------------------------------------------- /public/images/codepen/poNGaGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/poNGaGO.png -------------------------------------------------------------------------------- /public/images/codepen/vYJWVvy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/vYJWVvy.png -------------------------------------------------------------------------------- /public/images/codepen/vYwKNzR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/vYwKNzR.png -------------------------------------------------------------------------------- /public/images/codepen/wvJPjQa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/wvJPjQa.png -------------------------------------------------------------------------------- /public/images/codepen/wvmjomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/wvmjomb.png -------------------------------------------------------------------------------- /public/images/codepen/xxRNRwP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/xxRNRwP.png -------------------------------------------------------------------------------- /public/images/codepen/xxYrpzP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/xxYrpzP.png -------------------------------------------------------------------------------- /public/images/codepen/xxwBLMy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/xxwBLMy.png -------------------------------------------------------------------------------- /public/images/codepen/yLeGbgJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/yLeGbgJ.png -------------------------------------------------------------------------------- /public/images/codepen/zYqYvEV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/zYqYvEV.png -------------------------------------------------------------------------------- /public/images/codepen/zYxzQqa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/codepen/zYxzQqa.png -------------------------------------------------------------------------------- /public/images/gallery-grid-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/gallery-grid-lines.png -------------------------------------------------------------------------------- /public/images/github-template-chooser-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/github-template-chooser-example.jpg -------------------------------------------------------------------------------- /public/images/headshot-bw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/headshot-bw.jpg -------------------------------------------------------------------------------- /public/images/headshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/headshot.jpg -------------------------------------------------------------------------------- /public/images/projects-list-focus-voiceover-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/images/projects-list-focus-voiceover-text.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: GPTBot 2 | Disallow: / -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ryan Mulligan | ryanmulligan.dev", 3 | "short_name": "ryanmulligan.dev", 4 | "description": "Front-end web developer and UI engineer", 5 | "lang": "en", 6 | "dir": "ltr", 7 | "icons": [ 8 | { 9 | "src": "./favicon/android-chrome-192x192.png", 10 | "type": "image/png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "./favicon/android-chrome-512x512.png", 15 | "type": "image/png", 16 | "sizes": "512x512" 17 | }, 18 | { 19 | "src": "./favicon/maskable_icon.png", 20 | "sizes": "192x192", 21 | "type": "image/png", 22 | "purpose": "maskable" 23 | } 24 | ], 25 | "start_url": "/index.html", 26 | "display": "standalone", 27 | "orientation": "natural" 28 | } 29 | -------------------------------------------------------------------------------- /public/social/50-50-overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/50-50-overflow.png -------------------------------------------------------------------------------- /public/social/a-horizontal-scroll-list-and-custom-keyboard-navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/a-horizontal-scroll-list-and-custom-keyboard-navigation.png -------------------------------------------------------------------------------- /public/social/animating-with-the-flip-plugin-for-gsap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/animating-with-the-flip-plugin-for-gsap.png -------------------------------------------------------------------------------- /public/social/blog-posts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/blog-posts.png -------------------------------------------------------------------------------- /public/social/blog-questions-challenge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/blog-questions-challenge.png -------------------------------------------------------------------------------- /public/social/click-spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/click-spark.png -------------------------------------------------------------------------------- /public/social/creating-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/creating-time.png -------------------------------------------------------------------------------- /public/social/css-custom-property-fallbacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/css-custom-property-fallbacks.png -------------------------------------------------------------------------------- /public/social/css-property-new-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/css-property-new-style.png -------------------------------------------------------------------------------- /public/social/detect-js-support-in-css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/detect-js-support-in-css.png -------------------------------------------------------------------------------- /public/social/external-links-issue-template-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/external-links-issue-template-options.png -------------------------------------------------------------------------------- /public/social/full-bleed-table-scrolling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/full-bleed-table-scrolling.png -------------------------------------------------------------------------------- /public/social/grid-gap-behavior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/grid-gap-behavior.png -------------------------------------------------------------------------------- /public/social/grid-stacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/grid-stacks.png -------------------------------------------------------------------------------- /public/social/handling-events-web-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/handling-events-web-components.png -------------------------------------------------------------------------------- /public/social/horizontal-scrolling-in-a-centered-max-width-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/horizontal-scrolling-in-a-centered-max-width-container.png -------------------------------------------------------------------------------- /public/social/inverted-media-queries-and-breakpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/inverted-media-queries-and-breakpoints.png -------------------------------------------------------------------------------- /public/social/layout-breakouts-css-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/layout-breakouts-css-grid.png -------------------------------------------------------------------------------- /public/social/migrating-to-eleventy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/migrating-to-eleventy.png -------------------------------------------------------------------------------- /public/social/password-input-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/password-input-components.png -------------------------------------------------------------------------------- /public/social/penguin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/penguin.jpg -------------------------------------------------------------------------------- /public/social/pixel-canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/pixel-canvas.png -------------------------------------------------------------------------------- /public/social/ryan-mulligan-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/ryan-mulligan-dev.png -------------------------------------------------------------------------------- /public/social/scroll-driven-animations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/scroll-driven-animations.png -------------------------------------------------------------------------------- /public/social/scroll-triggered-animations-style-queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/scroll-triggered-animations-style-queries.png -------------------------------------------------------------------------------- /public/social/scrolling-rails-and-button-controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/scrolling-rails-and-button-controls.png -------------------------------------------------------------------------------- /public/social/scrollspy-nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/scrollspy-nav.png -------------------------------------------------------------------------------- /public/social/site-rebuild-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/site-rebuild-v3.png -------------------------------------------------------------------------------- /public/social/some-things-about-keyframes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/some-things-about-keyframes.png -------------------------------------------------------------------------------- /public/social/someone-great.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/someone-great.png -------------------------------------------------------------------------------- /public/social/sticky-page-header-shadow-scroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/sticky-page-header-shadow-scroll.png -------------------------------------------------------------------------------- /public/social/style-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/style-review.png -------------------------------------------------------------------------------- /public/social/target-toggler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/target-toggler.png -------------------------------------------------------------------------------- /public/social/the-infinite-marquee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/the-infinite-marquee.png -------------------------------------------------------------------------------- /public/social/the-shape-of-runs-to-come.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/the-shape-of-runs-to-come.png -------------------------------------------------------------------------------- /public/social/we-can-has-it-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/we-can-has-it-all.png -------------------------------------------------------------------------------- /public/social/website-themes-and-color-schemes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/social/website-themes-and-color-schemes.png -------------------------------------------------------------------------------- /public/videos/codepen/BavjjGE.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/BavjjGE.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/BavjjGE.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/BavjjGE.webm -------------------------------------------------------------------------------- /public/videos/codepen/ExjwZZg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/ExjwZZg.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/ExjwZZg.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/ExjwZZg.webm -------------------------------------------------------------------------------- /public/videos/codepen/GRBJLwE.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/GRBJLwE.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/GRBJLwE.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/GRBJLwE.webm -------------------------------------------------------------------------------- /public/videos/codepen/GRJPXLa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/GRJPXLa.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/GRJPXLa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/GRJPXLa.webm -------------------------------------------------------------------------------- /public/videos/codepen/JjOaabp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/JjOaabp.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/JjOaabp.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/JjOaabp.webm -------------------------------------------------------------------------------- /public/videos/codepen/JjYLLNR.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/JjYLLNR.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/JjYLLNR.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/JjYLLNR.webm -------------------------------------------------------------------------------- /public/videos/codepen/LYNNYyQ.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/LYNNYyQ.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/LYNNYyQ.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/LYNNYyQ.webm -------------------------------------------------------------------------------- /public/videos/codepen/MKaVzM.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MKaVzM.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/MKaVzM.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MKaVzM.webm -------------------------------------------------------------------------------- /public/videos/codepen/MWJoYxv.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MWJoYxv.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/MWJoYxv.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MWJoYxv.webm -------------------------------------------------------------------------------- /public/videos/codepen/MWgbqON.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MWgbqON.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/MWgbqON.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/MWgbqON.webm -------------------------------------------------------------------------------- /public/videos/codepen/NWmEMmJ.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/NWmEMmJ.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/NWmEMmJ.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/NWmEMmJ.webm -------------------------------------------------------------------------------- /public/videos/codepen/OJLMgYY.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJLMgYY.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/OJLMgYY.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJLMgYY.webm -------------------------------------------------------------------------------- /public/videos/codepen/OJRLBYJ.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJRLBYJ.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/OJRLBYJ.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJRLBYJ.webm -------------------------------------------------------------------------------- /public/videos/codepen/OJrJZqR.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJrJZqR.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/OJrJZqR.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/OJrJZqR.webm -------------------------------------------------------------------------------- /public/videos/codepen/PoOberB.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoOberB.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/PoOberB.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoOberB.webm -------------------------------------------------------------------------------- /public/videos/codepen/PoPpKKg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoPpKKg.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/PoPpKKg.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoPpKKg.webm -------------------------------------------------------------------------------- /public/videos/codepen/PoxMPzM.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoxMPzM.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/PoxMPzM.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/PoxMPzM.webm -------------------------------------------------------------------------------- /public/videos/codepen/VoExWp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/VoExWp.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/VoExWp.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/VoExWp.webm -------------------------------------------------------------------------------- /public/videos/codepen/VwLKbWJ.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/VwLKbWJ.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/VwLKbWJ.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/VwLKbWJ.webm -------------------------------------------------------------------------------- /public/videos/codepen/WRqEaV.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/WRqEaV.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/WRqEaV.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/WRqEaV.webm -------------------------------------------------------------------------------- /public/videos/codepen/XWJGQqy.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWJGQqy.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/XWJGQqy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWJGQqy.webm -------------------------------------------------------------------------------- /public/videos/codepen/XWbWKwL.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWbWKwL.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/XWbWKwL.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWbWKwL.webm -------------------------------------------------------------------------------- /public/videos/codepen/XWxZgqx.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWxZgqx.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/XWxZgqx.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/XWxZgqx.webm -------------------------------------------------------------------------------- /public/videos/codepen/abJEeXL.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/abJEeXL.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/abJEeXL.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/abJEeXL.webm -------------------------------------------------------------------------------- /public/videos/codepen/abmyQBN.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/abmyQBN.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/abmyQBN.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/abmyQBN.webm -------------------------------------------------------------------------------- /public/videos/codepen/bGqVyxm.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/bGqVyxm.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/bGqVyxm.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/bGqVyxm.webm -------------------------------------------------------------------------------- /public/videos/codepen/dwreYm.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/dwreYm.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/dwreYm.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/dwreYm.webm -------------------------------------------------------------------------------- /public/videos/codepen/dyNQJpp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/dyNQJpp.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/dyNQJpp.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/dyNQJpp.webm -------------------------------------------------------------------------------- /public/videos/codepen/gJNPON.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gJNPON.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/gJNPON.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gJNPON.webm -------------------------------------------------------------------------------- /public/videos/codepen/gORYwrg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gORYwrg.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/gORYwrg.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gORYwrg.webm -------------------------------------------------------------------------------- /public/videos/codepen/gORePQx.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gORePQx.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/gORePQx.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/gORePQx.webm -------------------------------------------------------------------------------- /public/videos/codepen/jOWXzrw.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOWXzrw.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/jOWXzrw.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOWXzrw.webm -------------------------------------------------------------------------------- /public/videos/codepen/jOWyepg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOWyepg.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/jOWyepg.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOWyepg.webm -------------------------------------------------------------------------------- /public/videos/codepen/jOzybYa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOzybYa.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/jOzybYa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jOzybYa.webm -------------------------------------------------------------------------------- /public/videos/codepen/jgGxKR.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jgGxKR.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/jgGxKR.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/jgGxKR.webm -------------------------------------------------------------------------------- /public/videos/codepen/joqYEj.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/joqYEj.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/joqYEj.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/joqYEj.webm -------------------------------------------------------------------------------- /public/videos/codepen/poNGaGO.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/poNGaGO.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/poNGaGO.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/poNGaGO.webm -------------------------------------------------------------------------------- /public/videos/codepen/vYJWVvy.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/vYJWVvy.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/vYJWVvy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/vYJWVvy.webm -------------------------------------------------------------------------------- /public/videos/codepen/vYwKNzR.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/vYwKNzR.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/vYwKNzR.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/vYwKNzR.webm -------------------------------------------------------------------------------- /public/videos/codepen/wvJPjQa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/wvJPjQa.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/wvJPjQa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/wvJPjQa.webm -------------------------------------------------------------------------------- /public/videos/codepen/wvmjomb.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/wvmjomb.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/wvmjomb.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/wvmjomb.webm -------------------------------------------------------------------------------- /public/videos/codepen/xxRNRwP.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxRNRwP.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/xxRNRwP.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxRNRwP.webm -------------------------------------------------------------------------------- /public/videos/codepen/xxYrpzP.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxYrpzP.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/xxYrpzP.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxYrpzP.webm -------------------------------------------------------------------------------- /public/videos/codepen/xxwBLMy.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxwBLMy.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/xxwBLMy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/xxwBLMy.webm -------------------------------------------------------------------------------- /public/videos/codepen/zYqYvEV.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/zYqYvEV.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/zYqYvEV.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/zYqYvEV.webm -------------------------------------------------------------------------------- /public/videos/codepen/zYxzQqa.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/zYxzQqa.mp4 -------------------------------------------------------------------------------- /public/videos/codepen/zYxzQqa.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/codepen/zYxzQqa.webm -------------------------------------------------------------------------------- /public/videos/detect-js-support-in-css.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/detect-js-support-in-css.jpg -------------------------------------------------------------------------------- /public/videos/detect-js-support-in-css.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/detect-js-support-in-css.mp4 -------------------------------------------------------------------------------- /public/videos/detect-js-support-in-css.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/detect-js-support-in-css.webm -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-1.mp4 -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-1.png -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-1.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-1.webm -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-2.mp4 -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-2.png -------------------------------------------------------------------------------- /public/videos/scroll-driven-animations-2.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/scroll-driven-animations-2.webm -------------------------------------------------------------------------------- /public/videos/shiny-cta-angle-offset.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/shiny-cta-angle-offset.mp4 -------------------------------------------------------------------------------- /public/videos/shiny-cta-angle-offset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/shiny-cta-angle-offset.png -------------------------------------------------------------------------------- /public/videos/shiny-cta-angle-offset.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hexagoncircle/ryan-mulligan-dev/a32824e08cbf4c7d2a298392482d952d4b071748/public/videos/shiny-cta-angle-offset.webm -------------------------------------------------------------------------------- /src/_data/articles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Positioning Overlay Content with CSS Grid", 4 | "url": "https://css-tricks.com/positioning-overlay-content-with-css-grid/", 5 | "source": "CSS-Tricks", 6 | "date": "2021-06-28" 7 | }, 8 | { 9 | "title": "A Dynamically-Sized Sticky Sidebar with HTML and CSS", 10 | "url": "https://css-tricks.com/a-dynamically-sized-sticky-sidebar-with-html-and-css/", 11 | "source": "CSS-Tricks", 12 | "date": "2020-11-20" 13 | }, 14 | { 15 | "title": "React Slider with Parallax Hover Effects", 16 | "url": "https://tympanus.net/codrops/2019/08/20/react-slider-with-parallax-hover-effects/", 17 | "source": "Codrops", 18 | "date": "2019-08-20" 19 | }, 20 | { 21 | "title": "CodePen Radio 357: Ryan Mulligan", 22 | "url": "https://blog.codepen.io/2022/03/02/357-ryan-mulligan/", 23 | "source": "CodePen", 24 | "date": "2022-03-02" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /src/_data/meta.js: -------------------------------------------------------------------------------- 1 | export default { 2 | lang: "en", 3 | title: "Ryan Mulligan", 4 | description: 5 | "Passenger through space and time, front-end web builder & bittersweet songs enthusiast", 6 | domain: "ryanmulligan.dev", 7 | url: 8 | (process.env.CONTEXT === "production" ? process.env.URL : process.env.DEPLOY_PRIME_URL) || 9 | "http://localhost:8080", 10 | ogImage: "/social/ryan-mulligan-dev.png", 11 | author: { 12 | name: "Ryan Mulligan", 13 | email: "hey@ryanmulligan.dev", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/_data/navigation.json: -------------------------------------------------------------------------------- 1 | { 2 | "appearances": [ 3 | { 4 | "id": "system", 5 | "icon": "monitor", 6 | "label": "System", 7 | "value": "system" 8 | }, 9 | { 10 | "id": "light", 11 | "icon": "sun", 12 | "label": "Light", 13 | "value": "light" 14 | }, 15 | { 16 | "id": "dark", 17 | "icon": "moon", 18 | "label": "Dark", 19 | "value": "dark" 20 | } 21 | ], 22 | "menu": [ 23 | { 24 | "id": "home", 25 | "label": "Home", 26 | "url": "/" 27 | }, 28 | { 29 | "id": "blog", 30 | "label": "Blog", 31 | "url": "/blog/" 32 | }, 33 | { 34 | "id": "rss", 35 | "label": "RSS", 36 | "url": "/feed.xml" 37 | }, 38 | { 39 | "id": "blogroll", 40 | "label": "Blogroll", 41 | "url": "/blogroll/" 42 | }, 43 | { 44 | "id": "issue", 45 | "label": "Report an issue", 46 | "url": "https://github.com/hexagoncircle/ryan-mulligan-dev/issues/new", 47 | "target": "_blank" 48 | } 49 | ], 50 | "social": [ 51 | { 52 | "id": "mastodon", 53 | "label": "Mastodon", 54 | "url": "https://fosstodon.org/@hexagoncircle" 55 | }, 56 | { 57 | "id": "codepen", 58 | "label": "CodePen", 59 | "url": "https://codepen.io/hexagoncircle" 60 | }, 61 | { 62 | "id": "github", 63 | "label": "GitHub", 64 | "url": "https://github.com/hexagoncircle" 65 | }, 66 | { 67 | "id": "bluesky", 68 | "label": "Bluesky", 69 | "url": "https://bsky.app/profile/ryanmulligan.dev" 70 | }, 71 | { 72 | "id": "linkedin-fill", 73 | "label": "LinkedIn", 74 | "url": "https://www.linkedin.com/in/mulliganryan/" 75 | }, 76 | { 77 | "id": "buymeacoffee", 78 | "label": "Buy me a coffee", 79 | "url": "https://buymeacoffee.com/ryanmulligan" 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/_data/spotify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Some helpful resources to get this functionality cooking 3 | * {@link https://luigicruz.dev/blog/using-spotify-api} 4 | * {@link https://henry.codes/writing/spotify-now-playing/} 5 | */ 6 | import EleventyFetch from "@11ty/eleventy-fetch"; 7 | import { config } from "dotenv"; 8 | 9 | config(); 10 | 11 | const TOKEN_ENDPOINT = "https://accounts.spotify.com/api/token"; 12 | const RECENTLY_PLAYED_ENDPOINT = "https://api.spotify.com/v1/me/player/recently-played"; 13 | 14 | const clientId = process.env.SPOTIFY_CLIENT_ID; 15 | const clientSecret = process.env.SPOTIFY_CLIENT_SECRET; 16 | const refreshToken = process.env.SPOTIFY_REFRESH_TOKEN; 17 | const scope = "user-read-recently-played"; 18 | 19 | const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64"); 20 | 21 | const getAccessToken = async () => { 22 | let response = await EleventyFetch(TOKEN_ENDPOINT, { 23 | duration: "10m", 24 | type: "json", 25 | fetchOptions: { 26 | method: "POST", 27 | headers: { 28 | Authorization: `Basic ${auth}`, 29 | "Content-Type": "application/x-www-form-urlencoded", 30 | }, 31 | body: `grant_type=refresh_token&refresh_token=${refreshToken}&scope=${scope}`, 32 | }, 33 | }); 34 | 35 | return response; 36 | }; 37 | 38 | export default async () => { 39 | let { access_token } = await getAccessToken(); 40 | 41 | let data = await EleventyFetch(RECENTLY_PLAYED_ENDPOINT + "?limit=10", { 42 | duration: "10m", 43 | type: "json", 44 | fetchOptions: { 45 | headers: { 46 | Authorization: `Bearer ${access_token}`, 47 | }, 48 | }, 49 | }); 50 | 51 | let { artists, name, external_urls } = data.items[0].track; 52 | 53 | return { 54 | artist: artists[0].name, 55 | track: { 56 | name, 57 | url: external_urls.spotify, 58 | }, 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /src/_data/themes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "default", 4 | "light": { 5 | "bg": "#fffefd", 6 | "bg-accent": "#f7f5f5", 7 | "bg-code": "#0f172a", 8 | "text": "#020617", 9 | "text-accent": "#334155", 10 | "text-code": "#f1f5f9", 11 | "link": "#2563eb", 12 | "link-hover": "#ec4899", 13 | "theme": "#ffedd5", 14 | "theme-muted": "#fff5e7", 15 | "theme-accent": "#fed7aa", 16 | "theme-offset": "#ef4444" 17 | }, 18 | "dark": { 19 | "bg": "#020617", 20 | "bg-accent": "#0f172a", 21 | "bg-code": "#0f172a", 22 | "text": "#f8fafc", 23 | "text-accent": "#f1f5f9", 24 | "link": "#7ab6ff", 25 | "link-hover": "#93c5fd", 26 | "theme": "#172554", 27 | "theme-muted": "#0e1737", 28 | "theme-accent": "#1e3a8a", 29 | "theme-offset": "#0ea5e9" 30 | } 31 | }, 32 | { 33 | "name": "pink", 34 | "light": { 35 | "theme": "#fce7f3", 36 | "theme-muted": "#fff5fb", 37 | "theme-accent": " #f9a8d4", 38 | "theme-offset": "#ec4899" 39 | }, 40 | "dark": { 41 | "bg": "#0c0508", 42 | "bg-accent": "#240813", 43 | "bg-code": "#240813", 44 | "theme": "#4f0c27", 45 | "theme-muted": "#3b0a1e", 46 | "theme-accent": "#9d174d", 47 | "theme-offset": "#f9a8d4" 48 | } 49 | }, 50 | { 51 | "name": "blue", 52 | "light": { 53 | "theme": "#dbeafe", 54 | "theme-muted": "#edf4ff", 55 | "theme-accent": "#93c5fd", 56 | "theme-offset": "#3b82f6" 57 | }, 58 | "dark": { 59 | "theme": "#082f49", 60 | "theme-muted": "#072437", 61 | "theme-accent": "#0284c7", 62 | "theme-offset": "#38bdf8" 63 | } 64 | }, 65 | { 66 | "name": "green", 67 | "light": { 68 | "theme": "#d1fae5", 69 | "theme-muted": "#f0fff7", 70 | "theme-accent": "#6ee7b7", 71 | "theme-offset": "#10b981" 72 | }, 73 | "dark": { 74 | "bg": "#0d1110", 75 | "bg-accent": "#121e1b", 76 | "bg-code": "#121e1b", 77 | "theme": "#064e3b", 78 | "theme-muted": "#0b4032", 79 | "theme-accent": "#047857", 80 | "theme-offset": "#34d399" 81 | } 82 | }, 83 | { 84 | "name": "purple", 85 | "light": { 86 | "theme": "#f3e8ff", 87 | "theme-muted": "#f9f4ff", 88 | "theme-accent": "#d8b4fe", 89 | "theme-offset": "#9333ea" 90 | }, 91 | "dark": { 92 | "bg": "#0f031a", 93 | "bg-accent": "#1f0733", 94 | "bg-code": "#1f0733", 95 | "theme": "#581c87", 96 | "theme-muted": "#491f6a", 97 | "theme-accent": "#9333ea", 98 | "theme-offset": "#c084fc" 99 | } 100 | }, 101 | { 102 | "name": "slate", 103 | "light": { 104 | "theme": "#f1f5f9", 105 | "theme-muted": "#e6edf2", 106 | "theme-accent": "#cbd5e1", 107 | "theme-offset": "#475569" 108 | }, 109 | "dark": { 110 | "bg": "#020617", 111 | "theme": "#1e293b", 112 | "theme-muted": "#1c232e", 113 | "theme-accent": "#334155", 114 | "theme-offset": "#9faec5" 115 | } 116 | } 117 | ] 118 | -------------------------------------------------------------------------------- /src/_data/weather.js: -------------------------------------------------------------------------------- 1 | import EleventyFetch from "@11ty/eleventy-fetch"; 2 | 3 | const url = new URL("https://api.open-meteo.com/v1/forecast"); 4 | 5 | const params = { 6 | latitude: 33.767, 7 | longitude: -118.1892, 8 | current: "temperature_2m,weather_code", 9 | temperature_unit: "fahrenheit", 10 | }; 11 | 12 | for (let prop in params) { 13 | url.searchParams.append(prop, params[prop]); 14 | } 15 | 16 | const weatherCodes = [ 17 | { 18 | label: "sunny", 19 | codes: [0, 1], 20 | }, 21 | { 22 | label: "partly cloudy", 23 | codes: [2], 24 | }, 25 | { 26 | label: "overcast", 27 | codes: [3], 28 | }, 29 | { 30 | label: "foggy", 31 | codes: [45, 48], 32 | }, 33 | { 34 | label: "rainy", 35 | codes: [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82], 36 | }, 37 | { 38 | label: "snowing", 39 | codes: [71, 73, 75, 77, 85, 86], 40 | }, 41 | { 42 | label: "stormy", 43 | codes: [95, 96, 99], 44 | }, 45 | ]; 46 | 47 | const getWeatherStatus = (code) => { 48 | let status = weatherCodes.find((obj) => obj.codes.includes(code)); 49 | 50 | if (!status) return; 51 | 52 | return status.label; 53 | }; 54 | 55 | export default async () => { 56 | let res = await EleventyFetch(url.toString(), { 57 | duration: "1h", 58 | type: "json", 59 | }); 60 | 61 | let temp = Math.round(res.current.temperature_2m); 62 | let unit = res.current_units.temperature_2m; 63 | let code = res.current.weather_code; 64 | 65 | return { 66 | temperature: temp + unit, 67 | status: getWeatherStatus(code), 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /src/_includes/article-list.webc: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | 51 | -------------------------------------------------------------------------------- /src/_includes/info-cta.webc: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 45 | -------------------------------------------------------------------------------- /src/_includes/inline-svg.webc: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /src/_includes/last-played-track.webc: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 34 | -------------------------------------------------------------------------------- /src/_includes/say-hey-hey.webc: -------------------------------------------------------------------------------- 1 | Say hello anytime!  8 | 9 | 22 | -------------------------------------------------------------------------------- /src/_includes/say-my-name.webc: -------------------------------------------------------------------------------- 1 | 24 | 25 | Ryan Mulligan 26 | 27 | 28 | 99 | -------------------------------------------------------------------------------- /src/_includes/site-footer.webc: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 10 |

11 | Assembled with a boundless affinity for the web 12 | 13 |

14 |
    15 |
  • 16 | 22 |
  • 23 |
24 |
25 | 26 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /src/_includes/site-header.webc: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 20 |
21 | 22 | 75 | -------------------------------------------------------------------------------- /src/_includes/target-toggler.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | 71 | -------------------------------------------------------------------------------- /src/_layouts/base.webc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/_layouts/post.webc: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "base" 3 | classnames: "prose" 4 | --- 5 | 6 | 7 |

8 | 9 |

10 |
11 |
12 | 13 | Posted on 14 |
15 |
16 | 17 | Takes about to read 18 |
19 |
20 | 21 |

22 | Back to all blog posts 23 |

24 | 25 | 61 | -------------------------------------------------------------------------------- /src/blog/blog.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "post", 3 | "tags": "posts" 4 | } 5 | -------------------------------------------------------------------------------- /src/blog/click-spark.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Click Spark 3 | description: A Web Component that adds sparks of joy when clicking or tapping on the page. 4 | ogImage: /social/click-spark.png 5 | date: 2024-01-02 6 | --- 7 | 8 | Last week I had made this fun little experiment. When clicking or tapping on the page, sparks (of joy) fly out from the mouse cursor/tap position. It started with me just messing around a bit in CodePen, but after sharing and getting a few friendly nudges on [my Mastodon post](https://mastodon.social/@hexagoncircle@fosstodon.org/111659424760546483), this fun little experiment evolved into the `` Web Component which is now available in a [GitHub repo](https://github.com/hexagoncircle/click-spark). 9 | 10 | Try it out in the CodePen demo below. 11 | 12 | {% codepen "https://codepen.io/hexagoncircle/pen/bGZdWyw", 350 %} 13 | 14 | The spark color can be modified by setting a color value to the `--click-spark-color` custom property: 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | **Updated on July 26th, 2024** — The update below was a bit silly of me in retrospect. I'm going to leave it for the sake of keeping a rich history. I've pushed a _new_ release that will instead contain the click sparks to the parent element. I believe this is a much cleaner implementation than what I initially attempted. Check out [pull request #3](https://github.com/hexagoncircle/click-spark/pull/3) if you'd like to dig into the details. 21 | 22 | **Updated on January 5th, 2023** — I had been thinking about a case where I'd like to have click sparks, but only when clicking on particular elements. I've updated the [code](https://github.com/hexagoncircle/click-spark) so that an `active-on` attribute can be set on the custom element to target a comma-separated list of selectors. If any of the selectors match, let the sparks fly. Here's a [CodePen demo](https://codepen.io/hexagoncircle/pen/rNReOPd) of the following example. 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | Have your sparks your way. ✨ 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/blog/creating-time.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Creating Time 3 | description: Personal goals for becoming a more creative me in the new year. 4 | ogImage: /social/creating-time.png 5 | date: 2023-01-20 6 | --- 7 | 8 | For my birthday, my partner put together the sweetest, most thoughtful surprise I could ever imagine. She recognized that I had been in a complete creative rut the previous year. I'd complain that I work too much, play too little. I'm too tired. Way too busy. This, that, and the other thing is blocking my time. Sometimes I would straight up conclude that I'm just not _good_ at anything—a collaboration between imposter syndrome and plateauing. 9 | 10 | I'd often sink into the couch after a long day instead of logging time towards hobbies or creative projects. All day screen time had frazzled my mind. But then I'd follow up with more of it, doomscrolling on my phone or gazing at the television. Not a lot was being accomplished outside of work hours. 11 | 12 | While I did play my guitar a good amount, I never completed any song arrangements. Last year I convinced myself to blog more, my goal set on writing a post every month or at least six articles in 2022. I acheived the latter, which I'm proud of, but maybe, _maybe_, I could have put more effort into my original milestone of twelve posts. 13 | 14 | Anyway, returning to where I began: On my birthday, my partner reveals that she booked a house where we would be staying with some close friends and family. To sprinkle on even more excitement, the property she booked includes a music studio with a drumkit ready for rockin’. I haven't been able to sit behind the drums for several years (blame the pandemic, me moving around a bunch, drums simply being too loud to play in a condo, etc.) so this news was absolutely thrilling. 15 | 16 | The people brought together for this getaway were selected based on a few magical moments throughout my life's musical journey. I have reminisced about week-long writing/recording sessions, staying up all night arranging tracks, playing take after take to get it right, and never letting exhaustion from the work day stop me. My partner understands how music lifts my spirit. Her gathering these folks for three nights of jamming was the greatest way to kick off a new year. 17 | 18 | I don't necessarily desire to chase new year resolutions. However, this music trip felt like a solid step towards—forgive me for saying it—changing my tune. I'm going to refocus my time and play more. I'll complete that song arrangement I've left fragmented. I'll write brand new ones. I'll make things out of clay, build with legos, draw, write, and take better care of my creative brain. All the while, I will remind myself that it's okay to be in a creative rut from time to time. 19 | 20 | Along with this week's jam sessions, I've also started reading _The Artist’s Way_ by Julia Cameron. It's only week one, but I've been writing three pages each morning, pouring out stream of consciousness via pen on paper. The idea is to connect the mind and body, writing longhand to declutter brain space before starting the day. Doesn't matter what I'm jotting down. Just write. 21 | 22 | Once I really embraced these morning pages, I discovered that I feel accomplished, calm, and clear-headed. Other discoveries: My hand cramps up very quickly and my handwriting looks like garbage. 23 | 24 | It doesn't matter. 25 | 26 | This is not for anyone to read. It's not even something I plan to reflect back on. I'm looking forward to filling every page in that notebook and then promptly tossing it in the trash, moving on to the next one. 27 | 28 | So what's next? Here's are some things I would like to be doing more, starting right now: 29 | 30 | - Finish song arrangements and record that music. 31 | - Explore the amazing realm of guitar pedals. 32 | - Make [stop-motion claymation videos](https://youtu.be/2jqKiVHS6x4). 33 | - Write the music for those videos. 34 | - Sketch while I watch television. 35 | - Read more books—physical copies preferred. 36 | - Stretch when I wake up. Like for _at least_ five minutes. 37 | - Discover a new hobby. Something different to awaken my creative brain. 38 | - Dance. If I want to. -------------------------------------------------------------------------------- /src/blog/external-links-issue-template-options.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Using External Links as GitHub Issue Template Options 4 | description: Customize the GitHub issue template chooser to handle new bug reports or feature requests in a team's preferred task management app. 5 | ogImage: /social/external-links-issue-template-options.png 6 | date: 2024-01-18 7 | --- 8 | 9 | I've been down the road of [configuring custom issue templates](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository) on GitHub repos before. It even seems like there have been some nice improvements to help make creating them even easier. Thanks for setting me up with a reasonable bug report template to start from so I don't have to build one from scratch. 🐛 10 | 11 | However, teams may rely on a variety of other tools—Jira, Asana, Linear, an Excel spreadsheet (Kidding! But maybe?)—to manage a backlog of tasks. I'd prefer teammates not add new issues on the repo only to then recreate those items in some other workflow. It would be great if folks could be guided to the right place and avoid double entry. 12 | 13 | There is a solution for this! It's new to me, so in the spirit of "today I learned", here's a quick tip for us to share. 14 | 15 | ## Creating custom config 16 | 17 | We can [configure the template chooser](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser) with external links and even lock down the ability to open new issues on the repo. To do this, we create a `config.yml` file in the repo's `.github/ISSUE_TEMPLATE` directory. 18 | 19 | The `.github/ISSUE_TEMPLATE` directory may not exist yet if templates have not been configured for the repo. If it needs to be manually created, the path should start at the root of the project. 20 | {.callout} 21 | 22 | ```yaml 23 | blank_issues_enabled: false 24 | contact_links: 25 | - name: 🐛 Bug Report 26 | url: [link to create bug report] 27 | about: Please file a bug report in our team's app of choice. 28 | - name: 💡 Feature Request 29 | url: [link to create feature request] 30 | about: Have some great ideas to improve our site or this codebase? Open a new feature request in our team's app of choice. 31 | ``` 32 | 33 | The screenshot below shows how these items will render on the "issue chooser" page. The config explicitly sets only the two options, both linking to their respective places where new tasks can be added to the team's backlog. 34 | 35 | {% image "./public/images/github-template-chooser-example.jpg", "Screenshot of custom external options added to the GitHub issue selection interface." %} 36 | 37 | The `blank_issues_enabled: false` line in the config code hides the "open a blank issue" hyperlink that normally appears below these custom options. Without this line, folks would still have the ability to add a new issue on the repo. 38 | 39 | With all of this in order, the repo remains free of user-entered issues, reducing some friction and redundancy. As an aside: I'm not 100% certain, but I'd wager that automated bot issues would still appear in the repo's issue queue. 40 | -------------------------------------------------------------------------------- /src/blog/full-bleed-table-scrolling.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Full-bleed Table Scrolling on Narrow Viewports 3 | description: Set an offset that matches the inline page gutter so the table can overflow the edge of the window. 4 | ogImage: /social/full-bleed-table-scrolling.png 5 | date: 2023-05-20 6 | --- 7 | 8 | I found the following to be a rather decent solution for having HTML tables overflow the inline edges of smaller/tighter/narrow viewports. Try resizing the width of the browser window if viewing this page on a larger screen. 9 | 10 | {% codepen "https://codepen.io/hexagoncircle/pen/ZEqjzKw" %} 11 | 12 | Notice that the table overflows beyond the edge of the window. This can be acheived by wrapping the `table` element with another element. 13 | 14 | ```html 15 |
16 | 17 | 18 |
19 |
20 | ``` 21 | 22 | On this wrapper, a combination of inline padding and negative margins create an offset that matches the page gutter size—that space to the left and right of the main content area. Here's a simplified example of those styles: 23 | 24 | 25 | ```css 26 | body { 27 | --page-gutter: clamp(1rem, 4vw, 2rem); 28 | padding-inline: var(--page-gutter); 29 | } 30 | 31 | .wrapper { 32 | margin-inline: calc(var(--page-gutter) * -1); 33 | padding-inline: var(--page-gutter); 34 | } 35 | ``` 36 | 37 | `clamp()` is used to create fluid padding. The gutter size shrinks as the viewport gets narrower. Unfamiliar with how this CSS function works? Check out the [docs on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp). 38 | {.callout} 39 | 40 | The inline margin will pull the table wrapper to the viewport edge. Then inline padding pushes the table back into position so that it's once again aligned with the page content. Here's all the CSS necessary for horizontal scrolling and wrapper repositioning: 41 | 42 | ```css 43 | .wrapper { 44 | display: flex; 45 | overscroll-behavior-x: contain; 46 | overflow-x: auto; 47 | margin-inline: calc(var(--page-gutter) * -1); 48 | padding-inline: var(--page-gutter); 49 | } 50 | ``` 51 | 52 | Setting `display: flex` on the wrapper element fixes a tiny issue in Safari (version 16.4 at the time of writing) where the inline padding at the end appears collapsed. 53 | {.callout} 54 | 55 | That's it! There are a handful of ways to display tables on smaller screens. I like that this solution requires very little code and doesn't rely on breakpoint changes. How might you solve this differently? [Let's discuss!](https://fosstodon.org/@hexagoncircle) 56 | 57 | -------------------------------------------------------------------------------- /src/blog/handling-events-web-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Notes on handling events in Web Components 4 | description: Sharing my learnings after receiving solid feedback on some web component code. 5 | ogImage: /social/handling-events-web-components.png 6 | date: 2024-07-27 7 | --- 8 | 9 | My [`click-spark` web component](/blog/click-spark/) was a fun, silly project at best. Yet I've seen it's had some love since being shared. So why not publish it as an [npm package](https://www.npmjs.com/package/click-spark)? No better time than the present, some say. 10 | 11 | I had done a major refactor before publishing, the most notable was that the spark would now be contained to the custom element's parent node. After announcing the updates in a [Mastodon post](https://fosstodon.org/@hexagoncircle/112855152216537788), I soon received a [PR](https://github.com/hexagoncircle/click-spark/pull/7#discussion_r1693933865) with some quality feedback including a more advantageous way to handle the parent click event using the `handleEvent()` method. 12 | 13 | ## The click-spark click 14 | 15 | Let's dive into a "before and after" for handling the click event on this component. 16 | 17 | In both examples, notice that the parent node is being stored in a variable when `connectedCallback` runs. This ensures that the click event is properly removed from the parent since it's not available by the time `disconnectedCallback` is invoked as [FND's comment](https://github.com/hexagoncircle/click-spark/pull/7#discussion_r1693936577) explains. 18 | {.callout} 19 | 20 | In the "before" approach, the event handler is stored in a variable so that it's cleared from the parent node whenever the `click-spark` element is removed from the DOM. 21 | 22 | ```js 23 | constructor() { 24 | this.clickEvent = this.handleClick.bind(this); 25 | } 26 | 27 | connectedCallback() { 28 | this._parent = this.parentNode; 29 | this._parent.addEventListener("click", this.clickEvent); 30 | } 31 | 32 | disconnectedCallback() { 33 | this._parent.removeEventListener("click", this.clickEvent); 34 | delete this._parent; 35 | } 36 | 37 | handleClick() { 38 | // Run code on click 39 | } 40 | ``` 41 | 42 | Switching to `handleEvent()` removes the need to store the event handler. Passing `this` into the event listener will run the component's `handleEvent()` method every time we click. 43 | 44 | ```js 45 | constructor() { 46 | // No longer need to store the callback 47 | } 48 | 49 | connectedCallback() { 50 | this._parent = this.parentNode; 51 | this._parent.addEventListener("click", this); 52 | } 53 | 54 | disconnectedCallback() { 55 | this._parent.removeEventListener("click", this); 56 | delete this._parent; 57 | } 58 | 59 | handleEvent(e) { 60 | // Run code on click 61 | } 62 | ``` 63 | 64 | ## Helpful resources 65 | 66 | Chris Ferdinandi's article, [The handleEvent() method is the absolute best way to handle events in Web Components](https://gomakethings.com/the-handleevent-method-is-the-absolute-best-way-to-handle-events-in-web-components/) is an excellent read on why `handleEvent()` is a top choice for architecting event listeners in Web Components. He also shares insight from Andrea Giammarchi's [DOM handleEvent: a cross-platform standard since year 2000](https://webreflection.medium.com/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38) article which contains solid techniques for handling multiple event types. 67 | 68 | Rather than me regurgitating, I recommend jumping into both of those articles to get a proper grasp on `handleEvent()`. 69 | 70 | **Updated on July 29th, 2024** — FND published [Garbage Collection for Event Listeners](https://prepitaph.org/articles/event-garbage/) about event listener cleanup after an element is removed from the DOM. The included demo is a nice touch. One interesting footnote that stood out to me: moving the element to a new location in the DOM will invoke `disconnectedCallback` before invoking `connectedCallback` again. 71 | -------------------------------------------------------------------------------- /src/blog/migrating-to-11ty.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Migrating to Eleventy 3 | description: Quick brain dump around my experience with Eleventy.js, a static site generator. 4 | ogImage: /social/migrating-to-eleventy.png 5 | date: 2021-11-08 6 | --- 7 | 8 | ## Hello, world! 9 | 10 | The time has come. I've finally decided to explore the wonderful world of [11ty](https://www.11ty.dev/)! 11 | 12 | The migration process for my personal website was dead simple. Not that moving a tiny one-pager over to a static site generator is really a big deal, but getting started was just so easy. [Build An Eleventy (11ty) Site From Scratch](https://egghead.io/courses/build-an-eleventy-11ty-site-from-scratch-bfd3) by [Stephanie Eckles](https://www.11ty.dev/authors/5t3ph/) was an incredible introduction to helping me understand the basics. 13 | 14 | ## Why choose Eleventy? 15 | 16 | Most of my curiosity around 11ty stems from the amount of positive feedback and love I see for this project around the web. It's decoupled from the rest of my tech stack and tooling. It works with multiple template engines (using markdown and nunjucks here). Spinning up dynamic pages from external data is no sweat. There are _no_ client-side javascript dependencies. Instead of me going on about it, check out the [11ty Docs](https://www.11ty.dev/docs/) or amazing resources like [11ty.rocks](https://11ty.rocks/) and [11ty.recipes](https://11ty.recipes/). 17 | 18 | On top of all that, the community is made up of really stellar people that I admire. People that care about a performant and inclusive web. I'll always be sold on that. 19 | 20 | ## What's next then? 21 | 22 | This only marks the beginning of this website's evolution. It will probably be a slow burn. Which is fine. Getting set up on 11ty gives me the opportunity to spin up content quickly. 23 | 24 | I have often wanted to write small articles around front-end techniques that have been helpful for me and share them with the community. It will also be nice to have a space where I can look up solutions I've since forgotten. I haven't been compelled to set up that space until now. The desire to mess around with 11ty gave me the push I needed. 25 | 26 | Be on the lookout for small snippets and chunks of thoughts. Looking forward to sharing with you all and getting feedback on improvements or alternatives. 27 | -------------------------------------------------------------------------------- /src/blog/penguin.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Penguin! 4 | description: The epic three-part claymation saga. 5 | ogImage: /social/penguin.jpg 6 | date: 2024-05-06 7 | --- 8 | 9 | My partner and I finally finished our epic three-part claymation saga. It's our first attempt at creating stop-motion videos with clay and it truly is _a lot_ of work for such a short result. [That claymation episode of Parks and Recreation](https://www.youtube.com/watch?v=LCUze7kuNas) is a little too real. I won't compare our movie to Avatar though. 10 | 11 | Regardless, we had a blast doing it. Making art with our hands brings a different style of satisfaction. Anyway, I'll keep this post short and get to it. Enjoy! 12 | 13 | https://www.youtube.com/watch?v=3mUdINzprdI -------------------------------------------------------------------------------- /src/blog/pixel-canvas.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: The Pixel Canvas Shimmer Effect 4 | description: The one about a Web Component that reveals a shimmering pixel background when its parent element is hovered. 5 | ogImage: /social/pixel-canvas.png 6 | date: 2024-12-03 7 | --- 8 | 9 | I recently stumbled on a super cool, well-executed hover effect from the [clerk.com](https://clerk.com/) website where a bloom of tiny pixels light up, their glow staggering from the center to the edges of its container. With some available free time over this Thanksgiving break, I hacked together my own version of a pixel canvas background shimmer. It quickly evolved into a `pixel-canvas` Web Component that can be enjoyed in the demo below. The component script and demo code have also been pushed up to a [GitHub repo](https://github.com/hexagoncircle/pixel-canvas). 10 | 11 | {% codepen "https://codepen.io/hexagoncircle/pen/KwPpdBZ" %} 12 | 13 | ## Usage 14 | 15 | Include the component script and then insert a `pixel-canvas` custom element inside the container it should fill. 16 | 17 | ```html 18 | 19 | 20 |
21 | 22 | 23 |
24 | ``` 25 | 26 | The `pixel-canvas` stretches to the edges of the parent container. When the parent is hovered, glimmering pixel fun ensues. 27 | 28 | ## Options 29 | 30 | The custom element has a few optional attributes available to customize the effect. Check out the CodePen demo's html panel to see how each variation is made. 31 | 32 | - `data-colors` takes a comma separated list of color values. 33 | - `data-gap` sets the amount of space between each pixel. 34 | - `data-speed` controls the general duration of the shimmer. This value is slightly randomized on each pixel that, in my opinion, adds a little more character. 35 | - `data-no-focus` is a boolean attribute that tells the Web Component to not run its animation whenever sibling elements are focused. The animation runs on sibling focus by default. 36 | 37 | There's likely more testing and tweaking necessary before I'd consider using this anywhere, but my goal was to run with this inspiration simply for the joy of coding. What a mesmerizing concept. I tip my hat to the creative engineers over at Clerk. -------------------------------------------------------------------------------- /src/blog/site-rebuild.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Site Rebuild, Here We Go! 3 | description: Celebrating the beautiful milestone of rebuilding my website from the ground up. 4 | ogImage: /social/site-rebuild-v3.png 5 | date: 2023-11-22 6 | --- 7 | 8 | There are still a few bits to work out, but why wait any longer? The latest version of my site is here and it has been rebuilt from the ground up. I'm feeling pretty good about it and invite you all to celebrate the magic with me! ✨ 9 | 10 | ## Inspiration 11 | 12 | While playing Super Mario Wonder, I found myself intrigued by the title screen transitions before each level. The skewed text atop its grid background pattern looked sleek yet playful. I began tinkering with some ideas and eventually came up with the blog post heading style seen above. At that point, I decided it was time to go for a full site rebuild. 13 | 14 | ## Some more details 15 | 16 | I plan to dive into some site features in future blog posts, but here's a quick list of all the exciting parts. Feel free to dig around [the site repo](https://github.com/hexagoncircle/ryan-mulligan-dev) as well. 17 | 18 | - I rebuilt from scratch. I stuck with 11ty but this time leaned 100% into [WebC](https://www.11ty.dev/docs/languages/webc/) for templating. 19 | - There are some pretty neat (to me!) web components to be discovered in this project. Both of the following were built to progressively enhance the experience. 20 | - The `` extends the homepage CodePen collection's keyboard interactions, adds video previews that can be disabled, introduces input range slider control, and the ability to toggle the skew. 21 | - The `` is what you might guess: A total package for changing the site appearance and adjusting theme colors. 22 | - The homepage includes a few dynamic stats about the latest site deployment date, what the weather was like during that time, and the latest track I had been jamming to on Spotify. 23 | - I've been messing around with [scroll-driven animations in CSS](/blog/scroll-driven-animations/) a lot and why not be a little extra? When using a supported browser on a wide enough viewport, notice that a progress timer appears on blog posts. The progress ring fills itself up as the page is scrolled. 24 | - My [eleventy config](https://github.com/hexagoncircle/ryan-mulligan-dev/blob/main/eleventy.config.js) is feeling more organized than ever. It was very much inspired by Lene Saile's [Organizing the Eleventy config file](https://www.lenesaile.com/en/blog/organizing-the-eleventy-config-file/) article. 25 | 26 | It's very early in the morning as I write this. I know I'm forgetting so many of the finer details but I couldn't wait to launch. There is still much for me to clean up and tinker on, but the time feels right to go public. [Toot at me on Mastodon](https://fosstodon.org/@hexagoncircle) and tell me what you think! 27 | 28 | ..."Toot at me" may not be the best way to word that. -------------------------------------------------------------------------------- /src/blog/someone-great.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Someone Great 4 | description: Processing through grief and celebrating someone great 5 | ogImage: /social/someone-great.png 6 | date: 2024-03-13 7 | --- 8 | 9 | Where to start? The grief is heavy and far too real. I barely slept last night. The brain fog is thick but finding the words and placing them here may provide some catharsis. 10 | 11 | I lost someone great. Oscar was so much bigger than most folks will ever understand. His love, not just for my partner and I, but for all walks of life was unconditional. His level of patience and kindness is aspirational. I've never met someone like him. I reflect fondly on the memories of him, some of my best years being filled with his presence every single day. 12 | 13 | Oscar was an avid US traveler, bouncing back and forth between the coasts. He spent much of his life right outside NYC being a friend of the people. I often dubbed him the mayor of downtown Jersey City. Countless times when Oscar and I went out for a stroll together passersby would gush with joy, interacting with Oscar as if nobody else existed. He walked proud, eyes beaming, perpetually present in the moment. 14 | 15 | It was also in Jersey City where my partner and I became great friends. It was where we fell in love. Oscar was integral to our story. We'd trek all over the city with him, hanging around the local coffee shop, strolling along the waterfront, sprawling out in the grass of Liberty State Park. Our conversations were natural, meaningful, and incredibly deep. Oscar was there with us, always attentive, reacting with his calming stare and gentle grin. At the beginning of our relationship, his approval meant everything. Oscar made me feel accepted. 16 | 17 | When the pandemic lockdowns started, Oscar insisted we get outdoors for socially-distanced walks, leading us around an exceptionally quiet city. Being with him helped maintain our sanity during such a difficult experience. We spent a majority of our time quarantined in a small studio apartment, but Oscar was there urging us to keep our cool, letting us know it was going to be alright. His warm aura would fill the room. We felt it. In that challenging time, we felt at peace. We felt joy. 18 | 19 | When travel began to return, lockdown restrictions being lifted, we made a decision to spend our days on the opposite coast. Once we got there, Oscar needed no time to adjust. He immediately embraced his golden years, escaping the harsh Northeast winters and heavy springtime rain for the endless sunny beaches of southern California. It brought my partner and I so much delight seeing his excitement. The new sites, sounds, the smells—everything was so fresh and welcome. We'd head down to the beach where Oscar would stretch across the sand and soak up the sun. He would smile back at us. Everything felt right. 20 | 21 | At home, Oscar would watch intently as we cooked meals. When the doorbell rang, he'd jump up to greet guests with glee. I'd play the acoustic guitar and Oscar would settle nearby to listen, often drifting off to sleep. He never said it, but I'm pretty confident he enjoyed hearing me play. I tell myself that anyway. 22 | 23 | Late at night, the three of us would snuggle up on the couch and watch television. Oscar was typically first to leave for bed. If it were getting too late, he'd sometimes peek his head back into the room. His stare seemed to say, "It's bedtime, you two, come along!" Then he'd stalk off as quickly as he appeared. In retrospect, he was probably right as we woke up groggy the next morning. 24 | 25 | I'd like to continue but I've reached a breaking point emotionally. My eyes are starting to swell. There is so much wonder around Oscar, so many stories to share. He elevated the mood of anyone that met him. On a personal level, he made me feel important and loved in times I felt small or invisible. There was not and never will be anyone that matches his unique disposition. He was our emotional companion and a beautiful friend. 26 | 27 | I love you, Oscar. You are forever missed. -------------------------------------------------------------------------------- /src/blog/sticky-header-scroll-shadow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sticky Page Header Shadow on Scroll 3 | description: Applying a shadow to a sticky page header when scrolling using the Intersection Observer API. 4 | ogImage: /social/sticky-page-header-shadow-scroll.png 5 | date: 2023-04-02 6 | --- 7 | 8 | We've seen it plenty of times around the web where a website's page header follows us as we scroll down the page. CSS makes doing this a breeze with [sticky positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky_positioning): 9 | 10 | ```css 11 | .page-header { 12 | position: sticky; 13 | top: 0; 14 | } 15 | ``` 16 | 17 | What if we desired something a little bit extra, like applying a `box-shadow` to the sticky header as soon as the page is scrolled? I thought it was worth sharing one solution that has worked well for me to accomplish this goal. Check out the following CodePen demo. As soon as the page is scrolled, a shadow fades in below the header. 18 | 19 | {% codepen "https://codepen.io/hexagoncircle/pen/qBMeWqo", 400 %} 20 | 21 | An element that I've decidedly dubbed an "intercept"—naming is hard and this felt right in the moment—is created and inserted above the page header at the top of the page. If we open the browser dev tools and inspect the DOM, we'll find: 22 | 23 | ```html 24 |
25 | 28 | ``` 29 | 30 | The [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) is being used to observe when the intercept is no longer appearing in the visible viewport area which happens as soon as the page scrolls. So when the intercept is _not_ intersecting, a class is applied to the header element. 31 | 32 | ```js 33 | const observer = new IntersectionObserver(([entry]) => { 34 | header.classList.toggle("active", !entry.isIntersecting); 35 | }); 36 | 37 | observer.observe(intercept); 38 | ``` 39 | 40 | Inspecting the DOM again, we'll catch the `active` class name on the page header element toggling on and off as we scroll down and back up. 41 | 42 | ## Delay that shadow 43 | 44 | It's also possible to wait on when the shadow should appear by offsetting the intercept element. Try editing the above demo on CodePen. In the CSS panel add the following ruleset: 45 | 46 | ```css 47 | [data-observer-intercept] { 48 | position: absolute; 49 | top: 300px; 50 | } 51 | ``` 52 | 53 | This will push the intercept down from the top of the page by 300 pixels. When scrolling the page again, notice that the shadow doesn't appear right away, waiting until the page has been scrolled passed the offset value. 54 | 55 | ## CSS scroll-driven animations 56 | 57 | **Updated on October 20th, 2023:** Here's another [CodePen demo](https://codepen.io/hexagoncircle/pen/LYMweej) that leans into CSS scroll-driven animations. Try it out in a browser that supports this feature. 58 | 59 | {% codepen "https://codepen.io/hexagoncircle/pen/LYMweej", 400 %} 60 | 61 | I've been justifiably excited about browsers beginning to adopt this API, which I had written about in [this blog post](/blog/scroll-driven-animations/). It's _not quite_ the same as using an intersection observer: The observer toggles a class selector that triggers an animation for the declared duration of time whereas this version links the fade progress to the page scroll position. I find that the latter feels more natural. If a browser doesn't yet support the feature, the styles gracefully degrade to a persistent static shadow. 62 | 63 | Have questions? Other ways to handle this? I'd love to hear about it! Reach out to me on [Mastodon](https://fosstodon.org/@hexagoncircle) with your ideas. -------------------------------------------------------------------------------- /src/blog/style-review.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Style Review 3 | description: This post is reserved for reviewing the styles of common page elements. 4 | subtitle: This post is reserved for reviewing the styles of common page elements 5 | ogImage: /social/style-review.png 6 | date: 2021-11-05 7 | --- 8 | 9 | ## Donut dessert lollipop dragée pudding marzipan jelly 10 | 11 | Danish icing cake _sugar plum chocolate bar_ candy canes macaroon pie. 12 | 13 | Bear claw bear claw biscuit fruitcake icing brownie. Jelly-o pudding tart cake ice cream jelly-o. Danish sugar plum chocolate cake wafer cake pudding sweet roll sesame snaps tiramisu. Carrot cake soufflé chocolate bar biscuit ice cream donut bear claw muffin marzipan. 14 | 15 | ### Carrot cake? 16 | 17 | Toffee wafer bonbon dessert dragée topping. Jelly tiramisu ~~gingerbread pie~~ toffee chocolate **chocolate** cake caramels. Donut gummi bears oat cake sugar plum cake marzipan marzipan. Cake gingerbread fruitcake tart chupa chups. 18 | 19 | ### Chocolate cake! Danish toffee! 20 | 21 | Danish sugar plum chocolate cake wafer: 22 | 23 | {% image "https://images.unsplash.com/photo-1578985545062-69928b1d9587?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzNjUxNjE3MA&ixlib=rb-1.2.1&q=80&w=800", "Stock photo of a fancy chocolate cake", "The fanciest of the chocolate cakes" %} 24 | 25 | Pastry jelly tootsie roll biscuit sesame snaps sesame snaps cotton candy sweet. Muffin tart cupcake jelly marzipan jelly beans liquorice pudding. Croissant powder marshmallow donut candy canes cupcake. 26 | 27 | ::: callout 28 | Cake gummies powder chocolate cake gummi bears bear claw chocolate sugar plum apple pie muffin. 29 | ::: 30 | 31 | Tiramisu apple pie muffin fruitcake wafer powder macaroon muffin caramels. 32 | 33 | 1. Pie sugar sweet roll jujubes cake 34 | 2. Tart sweet pudding caramels candy candy marzipan carrot cake soufflé chocolate bar biscuit. 35 | 3. Dessert toffee donut jelly 36 | 4. Powder gummies cheesecake brownie 37 | 38 | ## Biscuit wafer danish sweet roll wafer 39 | 40 | Toffee jelly beans fruitcake cake candy canes liquorice gingerbread: 41 | 42 | - Tart 43 | - fruitcake 44 | - shortbread 45 | - chupa chups 46 | - chocolate cake 47 | - cheesecake 48 | - gingerbread 49 | {.multi-column} 50 | 51 | Jujubes chocolate sesame snaps donut topping pie. Cake gummies powder chocolate cake cookie sesame snaps chocolate cake. Gummi bears bear claw chocolate sugar plum chupa chups. 52 | 53 | Pudding lollipop cake gummi bears oat cake bear claw muffin powder chupa chups. Tiramisu apple pie muffin fruitcake wafer powder macaroon muffin caramels. 54 | 55 | > Chupa chups bear claw biscuit cookie! Sweet biscuit powder... ice cream cupcake danish? Cake gummies powder chocolate cake cookie. 56 | 57 | Sweet chocolate cake oat cake dragée candy apple pie. Oat cake soufflé brownie toffee gummi bears marzipan chocolate bar. Try `getChocolate('cake')` or fruitcake pie chocolate bar shortbread. 58 | 59 | Chocolate cake sweet roll jelly beans. Cake lollipop apple pie lollipop jelly beans cookie. 60 | 61 | ```js 62 | const yum = document.querySelector(".yum"); 63 | 64 | function handleClick() { 65 | // Log something sweet 66 | console.log("Chocolate!"); 67 | } 68 | 69 | yum.addEventListener("click", handleClick); 70 | ``` 71 | 72 | 1. Chocolate cake sweet roll jelly beans. 73 | 2. Cake lollipop apple pie. 74 | 3. Lollipop jelly beans cookie! 75 | 76 | Jelly-o gummi bears cake. Dragée chocolate cake danish toffee cupcake brownie cheesecake oat cake topping. Carrot cake chupa chups ice cream tart soufflé gummi bears gummies danish. 77 | 78 | Donut. 79 | -------------------------------------------------------------------------------- /src/blog/target-toggler.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Web Component 3 | description: A web component to toggle the appearance of page elements. 4 | ogImage: /social/target-toggler.png 5 | date: 2023-12-22 6 | --- 7 | 8 | There are very rare occasions that I want `
` element disclosure widget-style funtionality but would like to have the `` element detached or live outside of it's related `
` container. This commonly stems from designs that may, for example, expect a toggle button to appear inline with other controls or content. Here's my attempt at a Web Component to handle that pattern. 9 | 10 | - [Source code](https://github.com/hexagoncircle/target-toggler) 11 | - [Demo](https://hexagoncircle.github.io/target-toggler/demo.html) 12 | 13 | The gist of this component is to enhance an HTML ` 20 | 21 | 22 |
A special announcement
23 | 24 |
25 | 26 |
27 | ``` 28 | 29 | In the above example, a `hidden` attribute will be added to the targeted `more-info` element. Now the button toggle can control the visibility of that piece of content. Want that content to be visible by default? Add a `target-visible` attribute. 30 | 31 | ```html 32 | 33 | 34 | 35 | ``` 36 | 37 | Be sure to check out [the demo page](https://hexagoncircle.github.io/target-toggler/demo.html) for examples of this component in action. 38 | 39 | ## Improvements 40 | 41 | Want to weigh in? [Add a new issue](https://github.com/hexagoncircle/target-toggler/issues/new) to the repo and share your ideas! I highly value any community feedback on how to improve this implementation. 42 | 43 | ## Helpful resources 44 | 45 | - [Open UI: Invoker Buttons](https://open-ui.org/components/invokers.explainer/) 46 | - [12 days of Web: Web Components](https://12daysofweb.dev/2023/web-components/) -------------------------------------------------------------------------------- /src/blog/the-shape-of-runs-to-come.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: The Shape of Runs to Come 4 | description: Reflections on the soundtrack that fueled a Sunday morning run. 5 | ogImage: /social/the-shape-of-runs-to-come.png 6 | date: 2024-12-08 7 | --- 8 | 9 | Over the last few months or so, I have been fairly consistent with getting outside for Sunday morning runs. A series of lower body issues had prevented me from doing so for many years, but it was an exercise I had enjoyed back then. It took time to rebuild that habit and muscle but I finally bested the behavior of doing so begrudgingly. 10 | 11 | Back in the day (what a weird phrase to say, how old am I?) I would purchase digital copies of full albums. I'd use my run time to digest the songs in the order the artist intended. Admittedly, I've become a lazy listener now, relying on streaming services to surface playlists that I mindlessly select to get going. I want to be better than that, but that's a story for another time. 12 | 13 | These days, my mood for music on runs can vary: Some sessions I'll pop in headphones and throw on some tunes, other times I head out free of devices (besides a watch to track all those sweet, sweaty workout stats) and simply take in the city noise. 14 | 15 | Before I headed out for my journey this morning, a friend shared a track from [an album of song covers](https://refused.bandcamp.com/album/the-shape-of-punk-to-come-obliterated) in tribute to The Refused's _[The Shape Of Punk To Come](https://refused.bandcamp.com/album/the-shape-of-punk-to-come)_. The original is a treasured classic, a staple LP from my younger years, and I can still remember the feeling of the first time it struck my ears. Its magic is reconjured every time I hear it. When that reverb-soaked feedback starts on [Worms of the Senses / Faculties of the Skull](https://refused.bandcamp.com/track/worms-of-the-senses-faculties-of-the-skull), my heart rate begins to ascend. The anticipation builds, my entire body well aware of the explosion of sound imminent. As my run began, I wasn't sure if I had goosebumps from the morning chill or the wall of noise about to ensue. My legs were already pumping. I was fully present, listening intently, ready for the blast. The sound abruptly detonated sending me rocketing down the street towards the rising sun. 16 | 17 | My current running goal is _4-in-40_, traversing four miles under forty minutes. I'm certainly no Prefontaine, but it's a fair enough objective for my age and ability. I'll typically finish my journey in that duration or slightly spill over the forty-minute mark. Today was different. Listening to _The Shape Of Punk To Come_ sent me cruising an extra quarter mile beyond the four before my workout ended. The unstoppable energy from that album is truly pure runner's fuel. 18 | 19 | There's certainly some layer of nostalgia, my younger spirit awakened and reignited by thrashing guitars and frantic rhythms, but many elements and themes on this record were so innovative at the time it was released. [New Noise](https://refused.bandcamp.com/track/new-noise) is a prime example that executes the following feeling flawlessly: Build anticipation, increase the energy level, and then right as the song seems prepped to blast off, switch to something unexpected. In this case, the guitars drop out to make way for some syncopated celestial synths layered over a soft drum rhythm. The energy sits in a holding pattern, unsure whether it should burst or cool down, when suddenly— 20 | 21 | > Can I scream?! 22 | 23 | Oh my goodness, yes. Yes you can. I quickly morphed into a runner decades younger. I had erupted, my entire being barreling full speed ahead. The midpoint of this track pulls out the same sequence of build up, drop off, and teasing just long enough before unleashing another loud burst of noise, driving to its explosive outro. As the song wraps up, "The New Beat!" is howled repeatedly to a cheering crowd that, I would imagine, had not been standing still. 24 | 25 | I definitely needed a long stretch after this run. -------------------------------------------------------------------------------- /src/blog/we-can-has-it-all.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: We can :has it all 3 | description: The functional :has() CSS pseudo class is available in all evergreen browsers. 4 | ogImage: /social/we-can-has-it-all.png 5 | date: 2023-12-19 6 | --- 7 | 8 | [The functional `:has()` CSS pseudo class](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) is now shipping in all evergreen browsers! 🎉 9 | 10 | With [the release of Firefox 121.0](https://www.mozilla.org/en-US/firefox/121.0/releasenotes/), I'm excited to see that my semi-dusty `:has()` demos are finally realizing their full potential in Firefox. The amount of opportunity unlocked with this selector seems nearly infinite. It can simplify some of the more complex CSS selectors and hacks used in the past. It also opens the door to replacing JavaScript solutions that weren't yet possible to achieve with only CSS. 11 | 12 | This post is merely a celebration of `:has()` browser support and shares a quick dive into some of my previous experiments. At the end of this article are [helpful resources](#helpful-resources) that do an amazing job explaining how the selector works and the unbelieveable power it gives us. 13 | 14 | ## Themes, layouts, and filters 15 | 16 | This first demo showcases how `:has()` can be used to set a dark mode, change the layout, and toggle the visibility of elements. All of it can be achieved through a combination of the `:has()` and [`:checked`](https://developer.mozilla.org/en-US/docs/Web/CSS/:checked) selectors. 17 | 18 | {% codepen "https://codepen.io/hexagoncircle/full/KKBBXQO" %} 19 | 20 | To pull off this primitive filtering technique, each card has a `data-category` attribute. When a filter option is selected, only the cards with that particular category will remain visible. Check out the following HTML example: 21 | 22 | ```html 23 |
24 | 25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 | ``` 33 | 34 | If the `bakery` filter option is selected, then the second and third cards would be hidden. Here's the CSS that hides all non-bakery cards: 35 | 36 | ```scss 37 | body:has([name="filter"][value="bakery"]:checked) .card:not([data-category="bakery"]) { 38 | display: none; 39 | } 40 | ``` 41 | 42 | `[name="filter"]` can be omitted from the selector above given the current circumstances. As things get more complex, however, there could be value overlaps that cause unintended results. This explicitness does raise [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity), but it can be reduced by using a [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) selector if preferred. Semi-related: [The truth about CSS selector performance](https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/) is a good read! 43 | {.callout} 44 | 45 | Using `:has()` to alter layout and filter collections of elements is incredibly powerful. Although, let's understand that there are important accessibility considerations to make here. Don't do something like this in production without ensuring all folks are enabled with a proper experience. 46 | 47 | ## Skate or theme! 48 | 49 | In the next demo, the select dropdown acts as a progressive enhancement. The skateboard's theme will update based on the option selected. The following is a simplified example: 50 | 51 | ```scss 52 | :root { 53 | --color: black; 54 | } 55 | 56 | body:has([value="lightning"]:checked) { 57 | --color: yellow; 58 | } 59 | 60 | body:has([value="holiday"]:checked) { 61 | --color: green; 62 | } 63 | ``` 64 | 65 | {% codepen "https://codepen.io/hexagoncircle/full/GRBJLwE" %} 66 | 67 | This project has always been one of my personal favorites. It brings me a fair amount of joy seeing it working fully in Firefox. 68 | 69 | 70 | ## Helpful resources 71 | 72 | - [Level Up Your CSS Skills With The :has() Selector](https://www.smashingmagazine.com/2023/01/level-up-css-skills-has-selector/) 73 | - [CSS :has Parent Selector](https://ishadeed.com/article/css-has-parent-selector/) 74 | - [Using :has() as a CSS Parent Selector and much more](https://webkit.org/blog/13096/css-has-pseudo-class/) 75 | - [Selecting previous siblings with CSS :has()](https://tobiasahlin.com/blog/previous-sibling-css-has/) 76 | - [The CSS :has() selector is way more than a “Parent Selector”](https://www.bram.us/2021/12/21/the-css-has-selector-is-way-more-than-a-parent-selector/) -------------------------------------------------------------------------------- /src/css/_base.css: -------------------------------------------------------------------------------- 1 | @media (prefers-reduced-motion: no-preference) { 2 | html { 3 | scroll-behavior: smooth; 4 | } 5 | } 6 | 7 | body { 8 | background-color: var(--color-bg); 9 | color: var(--color-text); 10 | font-family: var(--font-base); 11 | font-size: var(--step-0); 12 | font-weight: var(--font-normal); 13 | } 14 | 15 | .font-base { 16 | font-family: var(--font-base); 17 | } 18 | 19 | :where(a, .link) { 20 | color: var(--color-link); 21 | text-decoration: underline; 22 | text-underline-offset: 2px; 23 | text-decoration-skip-ink: auto; 24 | scroll-margin-block: var(--space-xl); 25 | } 26 | 27 | :where(a, .link):where(:hover, :focus-visible) { 28 | color: var(--color-theme-offset); 29 | } 30 | 31 | :where(a, .link):focus-visible { 32 | outline: var(--focus-outline); 33 | outline-offset: var(--focus-outline-offset); 34 | } 35 | 36 | :is(strong, .font-bold) { 37 | font-weight: var(--font-bold); 38 | letter-spacing: -0.01em; 39 | } 40 | 41 | .font-semibold { 42 | font-weight: var(--font-semibold); 43 | } 44 | 45 | :is(h1, h2, h3, h4) { 46 | font-family: var(--font-display); 47 | font-weight: normal; 48 | text-wrap: balance; 49 | } 50 | 51 | :is(h1, .text-5) { 52 | font-size: var(--step-5); 53 | line-height: 1.1; 54 | } 55 | 56 | :is(.text-4) { 57 | font-size: var(--step-4); 58 | line-height: 1.1; 59 | } 60 | 61 | :is(h2, .text-3) { 62 | font-size: var(--step-3); 63 | line-height: 1.1; 64 | } 65 | 66 | :is(h3, .text-2) { 67 | font-size: var(--step-2); 68 | line-height: 1.3; 69 | } 70 | 71 | :is(h4, .text-1) { 72 | font-size: var(--step-1); 73 | line-height: 1.4; 74 | } 75 | 76 | .text-reset { 77 | font: inherit; 78 | line-height: inherit; 79 | text-shadow: unset; 80 | text-wrap: initial; 81 | } 82 | 83 | .text-label { 84 | font-size: 0.6em; 85 | font-weight: var(--font-semibold); 86 | text-transform: uppercase; 87 | } 88 | 89 | figcaption { 90 | text-align: center; 91 | font-style: italic; 92 | font-size: 0.85em; 93 | line-height: 1.3; 94 | margin-inline-start: 0.2rem; 95 | margin-block-start: var(--space-2xs); 96 | } 97 | 98 | summary { 99 | display: inline; 100 | list-style: none; 101 | } 102 | 103 | summary:focus-visible { 104 | outline: var(--focus-outline); 105 | outline-offset: var(--focus-outline-offset); 106 | } 107 | 108 | summary::-webkit-details-marker { 109 | display: none; 110 | } 111 | 112 | summary::before { 113 | content: "+"; 114 | margin-inline-end: 0.2em; 115 | } 116 | 117 | details[open] summary::before { 118 | content: "−"; 119 | } 120 | 121 | fieldset { 122 | border: 0; 123 | padding: 0; 124 | margin: 0; 125 | min-width: 0; 126 | } 127 | 128 | .chip { 129 | --icon-size: 1.2em; 130 | 131 | display: inline-flex; 132 | align-items: center; 133 | gap: 0.2em; 134 | padding: 0.1rem var(--space-3xs); 135 | font-size: 0.7em; 136 | font-family: var(--font-mono); 137 | color: var(--color-theme-offset); 138 | border-radius: var(--radius-m); 139 | background: var(--color-theme-muted); 140 | border: 1px solid var(--color-theme); 141 | text-decoration: none; 142 | } 143 | 144 | a.chip:where(:hover, :focus-visible) { 145 | outline-offset: 1px; 146 | color: var(--color-text); 147 | border-color: var(--color-theme-accent); 148 | } 149 | 150 | .prose img { 151 | width: 100%; 152 | height: auto; 153 | border-radius: var(--radius-l); 154 | } 155 | 156 | .prose * + :is(h2, h3, h4) { 157 | --flow-space: var(--space-xl); 158 | } 159 | 160 | .prose pre[class*="language-"], 161 | .prose figure { 162 | margin-block: var(--space-l) var(--space-s); 163 | grid-column: popout; 164 | } 165 | 166 | .prose .eleventy-plugin-youtube-embed { 167 | --flow-space: var(--space-l); 168 | 169 | grid-column: popout; 170 | } 171 | -------------------------------------------------------------------------------- /src/css/_box.css: -------------------------------------------------------------------------------- 1 | .box { 2 | border-radius: var(--radius-m); 3 | border: 1px solid var(--color-theme-accent); 4 | background: linear-gradient(60deg, var(--color-bg), var(--color-theme)); 5 | } 6 | -------------------------------------------------------------------------------- /src/css/_callout.css: -------------------------------------------------------------------------------- 1 | .callout { 2 | grid-column: popout; 3 | position: relative; 4 | margin-block: var(--space-l) var(--space-s); 5 | padding: var(--space-s); 6 | border-radius: var(--radius-l); 7 | background-color: var(--color-theme); 8 | font-size: 0.875em; 9 | isolation: isolate; 10 | } 11 | 12 | .callout::before, 13 | .callout::after { 14 | --_offset-x: 20%; 15 | --_offset-y: 40%; 16 | font-family: var(--font-mono); 17 | font-size: 0.5em; 18 | position: absolute; 19 | color: var(--color-theme-offset); 20 | background: var(--color-bg); 21 | border-radius: var(--radius-m); 22 | padding: 0.2em 0.5em; 23 | } 24 | 25 | .callout::before { 26 | content: "" / ""; 36 | bottom: 0; 37 | right: 0; 38 | transform: translate(var(--_offset-x), var(--_offset-y)); 39 | } 40 | 41 | .callout code { 42 | color: var(--color-text); 43 | background-color: var(--color-theme-muted); 44 | } 45 | -------------------------------------------------------------------------------- /src/css/_code.css: -------------------------------------------------------------------------------- 1 | code, 2 | pre { 3 | font-size: 0.9em; 4 | line-height: 1.4; 5 | font-family: var(--font-mono); 6 | border-radius: var(--radius-s); 7 | } 8 | 9 | pre[class*="language-"] { 10 | padding: var(--space-xs); 11 | color: var(--color-text-code); 12 | background-color: var(--color-bg-code); 13 | border-radius: var(--radius-m); 14 | } 15 | 16 | pre[class*="language-"] { 17 | text-align: left; 18 | white-space: pre; 19 | word-spacing: normal; 20 | word-break: normal; 21 | word-wrap: normal; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | :where(:not(pre)) > code { 34 | position: relative; 35 | top: -0.025em; 36 | background-color: var(--color-bg-accent); 37 | padding: 0.02em 0.3em 0.04em; 38 | font-size: 0.85em; 39 | } 40 | 41 | :where(:not(a, pre, blockquote)) > code { 42 | color: var(--color-text-accent); 43 | } 44 | 45 | pre[class*="language-"] { 46 | overflow: auto; 47 | position: relative; 48 | } 49 | 50 | .language-css > code, 51 | .language-sass > code, 52 | .language-scss > code { 53 | color: #fd9170; 54 | } 55 | 56 | [class*="language-"] .namespace { 57 | opacity: 0.7; 58 | } 59 | 60 | .token.atrule { 61 | color: #c792ea; 62 | } 63 | 64 | .token.attr-name { 65 | color: #ffcb6b; 66 | } 67 | 68 | .token.attr-value { 69 | color: #c3e88d; 70 | } 71 | 72 | .token.attribute { 73 | color: #c3e88d; 74 | } 75 | 76 | .token.boolean { 77 | color: #c792ea; 78 | } 79 | 80 | .token.builtin { 81 | color: #ffcb6b; 82 | } 83 | 84 | .token.cdata { 85 | color: #80cbc4; 86 | } 87 | 88 | .token.char { 89 | color: #80cbc4; 90 | } 91 | 92 | .token.class { 93 | color: #ffcb6b; 94 | } 95 | 96 | .token.class-name { 97 | color: #f2ff00; 98 | } 99 | 100 | .token.color { 101 | color: #f2ff00; 102 | } 103 | 104 | .token.comment { 105 | color: #779daf; 106 | } 107 | 108 | .token.constant { 109 | color: #c792ea; 110 | } 111 | 112 | .token.deleted { 113 | color: #f07178; 114 | } 115 | 116 | .token.doctype { 117 | color: #546e7a; 118 | } 119 | 120 | .token.entity { 121 | color: #f07178; 122 | } 123 | 124 | .token.function { 125 | color: #c792ea; 126 | } 127 | 128 | .token.hexcode { 129 | color: #f2ff00; 130 | } 131 | 132 | .token.id { 133 | color: #c792ea; 134 | font-weight: bold; 135 | } 136 | 137 | .token.important { 138 | color: #c792ea; 139 | font-weight: bold; 140 | } 141 | 142 | .token.inserted { 143 | color: #80cbc4; 144 | } 145 | 146 | .token.keyword { 147 | color: #c792ea; 148 | font-style: italic; 149 | } 150 | 151 | .token.number { 152 | color: #fd9170; 153 | } 154 | 155 | .token.operator { 156 | color: #89ddff; 157 | } 158 | 159 | .token.prolog { 160 | color: #546e7a; 161 | } 162 | 163 | .token.property { 164 | color: #80cbc4; 165 | } 166 | 167 | .token.pseudo-class { 168 | color: #c3e88d; 169 | } 170 | 171 | .token.pseudo-element { 172 | color: #c3e88d; 173 | } 174 | 175 | .token.punctuation { 176 | color: #89ddff; 177 | } 178 | 179 | .token.regex { 180 | color: #f2ff00; 181 | } 182 | 183 | .token.selector { 184 | color: #f07178; 185 | } 186 | 187 | .token.string { 188 | color: #c3e88d; 189 | } 190 | 191 | .token.symbol { 192 | color: #c792ea; 193 | } 194 | 195 | .token.tag { 196 | color: #f07178; 197 | } 198 | 199 | .token.unit { 200 | color: #f07178; 201 | } 202 | 203 | .token.url { 204 | color: #fd9170; 205 | } 206 | 207 | .token.variable { 208 | color: #f07178; 209 | } 210 | 211 | /* CodePen iframe */ 212 | .codepen a { 213 | --icon-size: 1.2em; 214 | 215 | display: flex; 216 | align-items: center; 217 | gap: var(--space-3xs); 218 | } 219 | 220 | .prose .cp_embed_wrapper, 221 | .prose .cp_embed_wrapper + script + *:not(h2) { 222 | --flow-space: var(--space-l); 223 | } 224 | 225 | .cp_embed_wrapper { 226 | grid-column: popout; 227 | position: relative; 228 | overflow: auto; 229 | display: grid; 230 | place-items: center; 231 | grid-template-areas: "container"; 232 | resize: horizontal; 233 | } 234 | 235 | .cp_embed_wrapper iframe { 236 | grid-area: container; 237 | width: 100%; 238 | } 239 | -------------------------------------------------------------------------------- /src/css/_cta.css: -------------------------------------------------------------------------------- 1 | .cta::before, 2 | .cta::after { 3 | display: inline-block; 4 | pointer-events: none; 5 | } 6 | 7 | .cta.arrow-start::before { 8 | content: "←"; 9 | } 10 | 11 | .cta.arrow-end::after { 12 | content: "→"; 13 | } 14 | 15 | .cta.arrow-start:has(:hover, :focus-visible)::before, 16 | .cta.arrow-end:has(:hover, :focus-visible)::after { 17 | color: var(--color-theme-offset); 18 | animation: cta 400ms ease-out infinite; 19 | } 20 | 21 | @keyframes cta { 22 | 50% { 23 | transform: translateX(2px); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/css/_dimensions.css: -------------------------------------------------------------------------------- 1 | /* @link https://utopia.fyi/space/calculator?c=320,16,1.2,1240,18,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|5,s-l&g=s,l,xl,12 */ 2 | 3 | :root { 4 | --space-3xs: clamp(0.25rem, 0.2283rem + 0.1087vw, 0.3125rem); 5 | --space-2xs: clamp(0.5rem, 0.4783rem + 0.1087vw, 0.5625rem); 6 | --space-xs: clamp(0.75rem, 0.7065rem + 0.2174vw, 0.875rem); 7 | --space-s: clamp(1rem, 0.9565rem + 0.2174vw, 1.125rem); 8 | --space-m: clamp(1.5rem, 1.4348rem + 0.3261vw, 1.6875rem); 9 | --space-l: clamp(2rem, 1.913rem + 0.4348vw, 2.25rem); 10 | --space-xl: clamp(3rem, 2.8696rem + 0.6522vw, 3.375rem); 11 | --space-2xl: clamp(4rem, 3.8261rem + 0.8696vw, 4.5rem); 12 | --space-3xl: clamp(5rem, 4.7826rem + 1.087vw, 5.625rem); 13 | 14 | --radius-s: 0.125rem; 15 | --radius-m: 0.25rem; 16 | --radius-l: 0.5rem; 17 | --radius-pill: 360px; 18 | --radius-round: 50%; 19 | 20 | --page-gutters: clamp(var(--space-m), 3vw, var(--space-xl)); 21 | --page-max: 44rem; 22 | } 23 | -------------------------------------------------------------------------------- /src/css/_input.css: -------------------------------------------------------------------------------- 1 | input:focus-visible { 2 | outline: var(--focus-outline); 3 | } 4 | 5 | input[type="checkbox"] { 6 | --_size: 1rem; 7 | 8 | font: inherit; 9 | outline-offset: 3px; 10 | inline-size: var(--_size); 11 | block-size: var(--_size); 12 | border: 1px solid var(--color-theme-offset); 13 | flex-shrink: 0; 14 | } 15 | 16 | button.link { 17 | display: inline; 18 | cursor: pointer; 19 | padding: 0; 20 | border-width: 0; 21 | background-color: transparent; 22 | font: inherit; 23 | line-height: inherit; 24 | } 25 | 26 | @mixin range-track { 27 | background-color: var(--color-track); 28 | border-radius: var(--radius-track); 29 | height: var(--height-track); 30 | width: 100%; 31 | } 32 | 33 | @mixin range-thumb { 34 | appearance: none; 35 | margin-top: calc((var(--height-thumb) / 2 - var(--height-track) / 2) * -1); 36 | background-color: var(--color-thumb); 37 | border: none; 38 | border-radius: var(--radius-thumb); 39 | height: var(--height-thumb); 40 | width: var(--width-thumb); 41 | box-shadow: var(--color-bg) 0 0 0 4px; 42 | 43 | @media (forced-colors: active) { 44 | background-color: CanvasText; 45 | box-shadow: Canvas 0 0 0 4px; 46 | } 47 | } 48 | 49 | @mixin range-thumb-focus { 50 | outline: var(--focus-outline); 51 | outline-offset: var(--focus-outline-offset); 52 | } 53 | 54 | input[type="range"] { 55 | --color-track: var(--color-theme); 56 | --color-thumb: var(--color-theme-offset); 57 | --radius-track: var(--radius-pill); 58 | --radius-thumb: var(--radius-s); 59 | --height-track: 0.3rem; 60 | --height-thumb: 1rem; 61 | --width-thumb: calc(var(--height-thumb) * 2); 62 | 63 | appearance: none; 64 | outline: none; 65 | padding-block: var(--space-3xs); 66 | background: transparent; 67 | cursor: grab; 68 | width: 100%; 69 | } 70 | 71 | @media (forced-colors: active) { 72 | input[type="range"] { 73 | forced-color-adjust: none; 74 | } 75 | } 76 | 77 | input[type="range"]:active { 78 | cursor: grabbing; 79 | } 80 | 81 | input[type="range"]::-webkit-slider-runnable-track { 82 | @apply range-track; 83 | } 84 | 85 | input[type="range"]::-moz-range-track { 86 | @apply range-track; 87 | } 88 | 89 | input[type="range"]::-webkit-slider-thumb { 90 | @apply range-thumb; 91 | } 92 | 93 | input[type="range"]::-moz-range-thumb { 94 | @apply range-thumb; 95 | } 96 | 97 | input[type="range"]:focus-visible::-webkit-slider-thumb { 98 | @apply range-thumb-focus; 99 | } 100 | 101 | input[type="range"]:focus-visible::-moz-range-thumb { 102 | @apply range-thumb-focus; 103 | } 104 | -------------------------------------------------------------------------------- /src/css/_layout.css: -------------------------------------------------------------------------------- 1 | .breakout { 2 | --gutter-max: calc(1rem + 10vw); 3 | --gap: var(--space-m); 4 | --content: min(var(--page-max), 100% - var(--gap) * 2); 5 | --gutter: minmax(var(--gap), var(--gutter-max)); 6 | --popout: minmax(0px, 1rem); 7 | 8 | display: grid; 9 | grid-template-columns: 10 | [full-start] 0px 11 | [gutter-start] var(--gutter) 12 | [popout-start] var(--popout) 13 | [content-start] var(--content) [content-end] 14 | var(--popout) [popout-end] 15 | var(--gutter) [gutter-end] 16 | 1fr [full-end]; 17 | } 18 | 19 | :where(.breakout) > * { 20 | grid-column: content-start / content-end; 21 | } 22 | 23 | .center { 24 | margin-inline: auto; 25 | padding-inline: var(--gutters, var(--page-gutters)); 26 | inline-size: min(var(--max, var(--page-max)), 100%); 27 | } 28 | 29 | .cluster { 30 | display: flex; 31 | flex-wrap: wrap; 32 | gap: var(--row-gap, var(--gap, var(--space-m))) var(--column-gap, var(--gap, var(--space-m))); 33 | align-items: var(--align, center); 34 | justify-content: var(--justify, flex-start); 35 | } 36 | 37 | .flow > * + * { 38 | margin-block-start: var(--flow-space, 1em); 39 | } 40 | 41 | .grid { 42 | display: grid; 43 | gap: var(--gap, var(--row-gap, var(--space-m))) var(--gap, var(--column-gap, var(--space-m))); 44 | grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--min)), 1fr)); 45 | } 46 | 47 | .flex-grid { 48 | display: flex; 49 | flex-wrap: wrap; 50 | gap: var(--gap, var(--row-gap, var(--space-m))) var(--gap, var(--column-gap, var(--space-m))); 51 | } 52 | 53 | .flex-grid > * { 54 | flex: 1 1 var(--min, auto); 55 | } 56 | 57 | .stack { 58 | display: grid; 59 | grid-template-areas: "stack"; 60 | 61 | > *, 62 | &::before, 63 | &::after { 64 | grid-area: stack; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/css/_motion.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ease-out: cubic-bezier(0.33, 1, 0.68, 1); 3 | --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); 4 | } 5 | 6 | @keyframes fade-in { 7 | from { 8 | opacity: 0; 9 | } 10 | to { 11 | opacity: 1; 12 | } 13 | } 14 | 15 | @keyframes fade-in-scale-back { 16 | from { 17 | opacity: 0; 18 | scale: 1.04; 19 | } 20 | to { 21 | opacity: 1; 22 | scale: 1; 23 | } 24 | } 25 | 26 | @keyframes fade-in-scale-up { 27 | from { 28 | opacity: 0.01; 29 | scale: 0.8; 30 | } 31 | to { 32 | opacity: 1; 33 | scale: 1; 34 | } 35 | } 36 | 37 | @keyframes scale-back { 38 | from { 39 | scale: 1.16; 40 | } 41 | 42 | to { 43 | scale: 1.001; 44 | } 45 | } 46 | 47 | @keyframes slide-up-right { 48 | from { 49 | translate: -12px 12px; 50 | } 51 | to { 52 | translate: 0 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/css/_quote.css: -------------------------------------------------------------------------------- 1 | blockquote { 2 | grid-column: popout; 3 | display: grid; 4 | grid-template-areas: 5 | "mark quote" 6 | "line quote"; 7 | grid-template-columns: auto 1fr; 8 | grid-template-rows: auto 1fr; 9 | column-gap: 0.2em; 10 | padding-block: var(--space-xs); 11 | padding-inline-end: var(--space-l); 12 | color: var(--color-theme-offset); 13 | 14 | font-size: var(--step-1); 15 | line-height: 1.4; 16 | } 17 | 18 | blockquote > * { 19 | grid-area: quote; 20 | text-indent: 0.4em; 21 | } 22 | 23 | blockquote code { 24 | color: var(--color-theme-offset); 25 | background-color: var(--color-theme-muted); 26 | } 27 | 28 | blockquote::before { 29 | grid-area: mark; 30 | place-self: start; 31 | content: "“"; 32 | font-family: Georgia, serif; 33 | font-size: 3.4em; 34 | line-height: 1; 35 | height: 0.5em; 36 | color: var(--color-theme-offset); 37 | transform: translateY(-0.025em); 38 | } 39 | 40 | blockquote::after { 41 | grid-area: line; 42 | place-self: stretch center; 43 | content: ""; 44 | width: 0.2em; 45 | height: 100%; 46 | border-radius: var(--radius-pill); 47 | background-color: var(--color-theme-offset); 48 | transform: translateY(-0.1em); 49 | } 50 | -------------------------------------------------------------------------------- /src/css/_reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | A (more) Modern CSS Reset 3 | {@link https://andy-bell.co.uk/a-more-modern-css-reset/} 4 | */ 5 | 6 | /* Box sizing rules */ 7 | *, 8 | *::before, 9 | *::after { 10 | box-sizing: border-box; 11 | } 12 | 13 | /* Prevent font size inflation */ 14 | html { 15 | -moz-text-size-adjust: none; 16 | -webkit-text-size-adjust: none; 17 | text-size-adjust: none; 18 | } 19 | 20 | /* Remove default margin in favour of better control in authored CSS */ 21 | body, 22 | h1, 23 | h2, 24 | h3, 25 | h4, 26 | p, 27 | figure, 28 | blockquote, 29 | dl, 30 | ul, 31 | ol, 32 | dd { 33 | margin: 0; 34 | } 35 | 36 | /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */ 37 | :where(ul[role="list"], ol[role="list"]) { 38 | list-style: none; 39 | padding: 0; 40 | margin: 0; 41 | } 42 | 43 | :where(ul, ol):not([role="list"]) { 44 | padding-inline-start: 1.2em; 45 | } 46 | 47 | /* Set core body defaults */ 48 | body { 49 | min-height: 100vh; 50 | line-height: 1.6; 51 | } 52 | 53 | /* Set shorter line heights on headings and interactive elements */ 54 | h1, 55 | h2, 56 | h3, 57 | h4, 58 | button, 59 | input, 60 | label { 61 | line-height: 1.1; 62 | } 63 | 64 | /* Balance text wrapping on headings */ 65 | h1, 66 | h2, 67 | h3, 68 | h4 { 69 | text-wrap: balance; 70 | } 71 | 72 | /* Make media easier to work with */ 73 | img, 74 | picture, 75 | video, 76 | iframe { 77 | max-width: 100%; 78 | display: block; 79 | } 80 | 81 | /* Inherit fonts for inputs and buttons */ 82 | input, 83 | button, 84 | textarea, 85 | select { 86 | font: inherit; 87 | } 88 | 89 | /* Make sure textareas without a rows attribute are not tiny */ 90 | textarea:not([rows]) { 91 | min-height: 10em; 92 | } 93 | 94 | /* Anything that has been anchored to should have extra scroll margin */ 95 | :target { 96 | scroll-margin-block: 5ex; 97 | } 98 | -------------------------------------------------------------------------------- /src/css/_type.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Tanker Regular"; 3 | src: url("/fonts/Tanker-Regular.woff2") format("woff2"); 4 | font-weight: 400; 5 | font-display: block; 6 | font-style: normal; 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, 8 | U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 9 | } 10 | 11 | @font-face { 12 | font-family: "Jet Brains Mono"; 13 | font-style: normal; 14 | font-weight: 400; 15 | font-display: swap; 16 | src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2"); 17 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, 18 | U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 19 | } 20 | 21 | /* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1240,18,1.333,5,2,&s=0.75|0.5,1.5|2|3|4,s-l&g=s,l,xl,12 */ 22 | :root { 23 | --step--2: clamp(0.6331rem, 0.7157rem + -0.1065vw, 0.6944rem); 24 | --step--1: clamp(0.8331rem, 0.8294rem + 0.0185vw, 0.8438rem); 25 | --step-0: clamp(1rem, 0.9565rem + 0.4174vw, 1.25rem); 26 | --step-1: clamp(1.2rem, 1.0959rem + 0.5207vw, 1.4994rem); 27 | --step-2: clamp(1.44rem, 1.2457rem + 0.9717vw, 1.9988rem); 28 | --step-3: clamp(1.7281rem, 1.4025rem + 1.6283vw, 2.6644rem); 29 | --step-4: clamp(2.0738rem, 1.5596rem + 2.5707vw, 3.5519rem); 30 | --step-5: clamp(2.4881rem, 1.7066rem + 3.9076vw, 4.735rem); 31 | 32 | --font-base: system-ui, sans-serif; 33 | --font-display: "Tanker Regular", var(--font-base); 34 | --font-mono: "Jet Brains Mono", monospace; 35 | 36 | --font-bold: 700; 37 | --font-semibold: 600; 38 | --font-normal: 400; 39 | 40 | --focus-outline: 2px solid var(--color-link); 41 | --focus-outline-offset: 3px; 42 | } 43 | -------------------------------------------------------------------------------- /src/css/_utils.css: -------------------------------------------------------------------------------- 1 | .skewer { 2 | --angle: -3deg; 3 | --unskew: rotate(calc(var(--angle) * -1)) skew(calc(var(--angle) * -1)); 4 | transform: rotate(var(--angle)) skew(var(--angle)); 5 | } 6 | 7 | .unskew { 8 | transform: var(--unskew); 9 | } 10 | 11 | .pseudo-gradient { 12 | position: relative; 13 | isolation: isolate; 14 | 15 | &::before { 16 | content: ""; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | border-radius: var(--radius-m); 23 | background: linear-gradient(to right, transparent 20%, var(--color-theme)); 24 | mask-image: linear-gradient(70deg, transparent 25%, black); 25 | z-index: -1; 26 | } 27 | } 28 | 29 | .checkerboard-pattern { 30 | background-image: conic-gradient( 31 | var(--color-1, white) 90deg, 32 | var(--color-2, black) 0 180deg, 33 | var(--color-1, white) 0 270deg, 34 | var(--color-2, black) 0 35 | ); 36 | background-size: var(--bg-size, 36px) var(--bg-size, 36px); 37 | } 38 | 39 | .icon { 40 | width: var(--icon-size, 1.75rem); 41 | height: var(--icon-size, 1.75rem); 42 | } 43 | 44 | .multi-column { 45 | column-width: var(--column-width, 250px); 46 | column-gap: var(--column-gap, var(--space-m)); 47 | } 48 | 49 | .scroll-x { 50 | --scrollbar-offset: 4px; 51 | max-width: 100%; 52 | overflow-x: scroll; 53 | overscroll-behavior-x: contain; 54 | scrollbar-width: none; 55 | } 56 | 57 | @media (prefers-reduced-motion: no-preference) { 58 | .scroll-x:focus-within { 59 | scroll-behavior: smooth; 60 | } 61 | } 62 | 63 | .scroll-x::-webkit-scrollbar { 64 | display: none; 65 | } 66 | 67 | .scroll-x::-webkit-scrollbar { 68 | height: 12px; 69 | background-color: transparent; 70 | } 71 | 72 | .scroll-x::-webkit-scrollbar-thumb { 73 | background-color: transparent; 74 | } 75 | 76 | .scroll-x:hover::-webkit-scrollbar, 77 | .scroll-x::-webkit-scrollbar:active { 78 | background-color: var(--color-text); 79 | box-shadow: inset var(--color-bg) 0 0 0 var(--scrollbar-offset); 80 | } 81 | 82 | .scroll-x:hover::-webkit-scrollbar-thumb, 83 | .scroll-x::-webkit-scrollbar-thumb:active { 84 | background-color: var(--color-text); 85 | border-left: var(--scrollbar-offset) solid var(--color-bg); 86 | border-right: var(--scrollbar-offset) solid var(--color-bg); 87 | } 88 | 89 | .visually-hidden { 90 | clip: rect(0 0 0 0); 91 | clip-path: inset(50%); 92 | height: 1px; 93 | overflow: hidden; 94 | position: absolute; 95 | white-space: nowrap; 96 | width: 1px; 97 | } 98 | -------------------------------------------------------------------------------- /src/css/css.json: -------------------------------------------------------------------------------- 1 | { 2 | "eleventyExcludeFromCollections": true 3 | } 4 | -------------------------------------------------------------------------------- /src/css/styles.css: -------------------------------------------------------------------------------- 1 | @import "_reset.css"; 2 | 3 | /* Variables */ 4 | @import "_dimensions.css"; 5 | @import "_motion.css"; 6 | @import "_type.css"; 7 | 8 | /* Composition */ 9 | @import "_base.css"; 10 | @import "_layout.css"; 11 | 12 | /* Blocks */ 13 | @import "_box.css"; 14 | @import "_callout.css"; 15 | @import "_code.css"; 16 | @import "_cta.css"; 17 | @import "_input.css"; 18 | @import "_quote.css"; 19 | 20 | /* Utilities */ 21 | @import "_utils.css"; 22 | -------------------------------------------------------------------------------- /src/css/themes.11ty.js: -------------------------------------------------------------------------------- 1 | import { transform } from "lightningcss"; 2 | import lightningcssConfig from "../../11ty/css/config.js"; 3 | import { createRequire } from "module"; 4 | 5 | /** 6 | * Leverage the cjs require function to import json files 7 | * {@link https://pawelgrzybek.com/all-you-need-to-know-to-move-from-commonjs-to-ecmascript-modules-esm-in-node-js/#importing-json} 8 | * {@link https://www.stefanjudis.com/snippets/how-to-import-json-files-in-es-modules-node-js/} 9 | */ 10 | const require = createRequire(import.meta.url); 11 | const themes = require("../_data/themes.json"); 12 | 13 | const getThemeVars = (variant) => { 14 | let result = ""; 15 | 16 | function outputVars(theme) { 17 | let vars = ""; 18 | 19 | for (let prop in theme[variant]) { 20 | vars += `--color-${prop}: ${theme[variant][prop]};`; 21 | } 22 | 23 | return vars; 24 | } 25 | 26 | function outputThemeVars(theme) { 27 | return `[data-theme="${theme.name}"] { 28 | ${outputVars(theme)} 29 | }`; 30 | } 31 | 32 | themes.map((theme, index) => { 33 | if (index === 0) { 34 | result += outputVars(theme); 35 | result += outputThemeVars(theme); 36 | } else { 37 | result += outputThemeVars(theme); 38 | } 39 | }); 40 | 41 | return result; 42 | }; 43 | 44 | let css = ` 45 | :root { 46 | ${getThemeVars("light")} 47 | } 48 | 49 | @media (prefers-color-scheme: dark) { 50 | :root:not([data-appearance]) { 51 | ${getThemeVars("dark")} 52 | } 53 | } 54 | 55 | @media not print { 56 | [data-appearance="dark"] { 57 | ${getThemeVars("dark")} 58 | } 59 | } 60 | `; 61 | 62 | let { code } = transform({ 63 | code: Buffer.from(css), 64 | ...lightningcssConfig, 65 | }); 66 | 67 | class Themes { 68 | data() { 69 | return { 70 | permalink: "/css/themes.css", 71 | code, 72 | }; 73 | } 74 | 75 | render({ code }) { 76 | return code; 77 | } 78 | } 79 | 80 | export default Themes; 81 | -------------------------------------------------------------------------------- /src/feed.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: "feed.xml" 3 | eleventyExcludeFromCollections: true 4 | --- 5 | 6 | 7 | {{ meta.title }} 8 | Blogging general thoughts and rambles, code snippets, and front-end 9 | web dev discoveries 10 | 11 | 12 | {{ collections.posts | getNewestCollectionItemDate | dateToRfc3339 }} 13 | {{ meta.url }} 14 | 15 | {{ meta.author.name }} 16 | {{ meta.author.email }} 17 | 18 | {%- for post in collections.posts %} 19 | {%- set absolutePostUrl = post.url | absoluteUrl(meta.url) %} 20 | 21 | {{ post.data.title }} 22 | 23 | {{ post.date | dateToRfc3339 }} 24 | {{ absolutePostUrl }} 25 | {{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }} 26 | 27 | {%- endfor %} 28 | -------------------------------------------------------------------------------- /src/pages/404.webc: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: 404.html 3 | title: This page is missing 4 | description: Head back to the home page or check out the blog 5 | --- 6 | 7 |

And you may ask yourself, "Well, how did I get here?"

8 | 9 |

10 | This is a 404 page. It means that this page no longer exists. There's a 11 | possibility that it never did. Same as it ever was. 12 |

13 |

14 | So what now? Head back home or check out the 15 | blog for that content you crave. Or, if you've now got "Once in a Lifetime" 16 | stuck in your head, maybe roll with that and pop on 👀 17 |

18 |
19 | 20 |
-------------------------------------------------------------------------------- /src/pages/blog.webc: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: "blog/" 3 | title: "Blog posts by Ryan Mulligan" 4 | description: "Blogging my general thoughts and rambles, code snippets, and front-end 5 | web dev discoveries" 6 | --- 7 | 8 |

The Blog

9 |

10 | Sometimes general thoughts and rambles but more often these posts follow my experimentation, 11 | learning, and front-end development discoveries that were worth a share with the world wide web. 12 | All feedback is always welcome. Below is the whole collection, starting with the most recent 13 | stuff. Follow my 14 | RSS feed to stay in the loop when new content gets published. 15 |

16 | 17 | -------------------------------------------------------------------------------- /src/pages/blogroll/blogroll.11tydata.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All credit goes to Ben Myers 3 | * {@link https://benmyers.dev/blog/eleventy-blogroll/} 4 | */ 5 | 6 | import { AssetCache } from "@11ty/eleventy-fetch"; 7 | import RssParser from "rss-parser"; 8 | 9 | const rssParser = new RssParser({ timeout: 5000 }); 10 | /** Sorter function for an array of feed items with dates */ 11 | function sortByDateDescending(feedItemA, feedItemB) { 12 | const itemADate = new Date(feedItemA.isoDate); 13 | const itemBDate = new Date(feedItemB.isoDate); 14 | return itemBDate - itemADate; 15 | } 16 | 17 | /** Fetch RSS feed at a given URL and return its latest post (or get it from cache, if possible) */ 18 | async function getLatestPost(feedUrl) { 19 | const asset = new AssetCache(feedUrl); 20 | // If cache exists, happy day! Use that. 21 | if (asset.isCacheValid("1d")) { 22 | const cachedValue = await asset.getCachedValue(); 23 | return cachedValue; 24 | } 25 | const rssPost = await rssParser 26 | .parseURL(feedUrl) 27 | .catch((err) => { 28 | console.error(feedUrl, err); 29 | return null; 30 | }) 31 | .then((feed) => { 32 | if (!feed || !feed.items || !feed.items.length) { 33 | return null; 34 | } 35 | const [latest] = [...feed.items].sort(sortByDateDescending); 36 | if (!latest.title || !latest.link) { 37 | return null; 38 | } 39 | return { title: latest.title, url: latest.link }; 40 | }); 41 | await asset.save(rssPost, "json"); 42 | return rssPost; 43 | } 44 | 45 | export const eleventyComputed = { 46 | /** Augments blog info with fetched information from the actual blogs */ 47 | async blogData({ blogs }) { 48 | const augmentedBlogInfo = await Promise.all( 49 | blogs.map(async (rawBlogInfo) => { 50 | return { 51 | ...rawBlogInfo, 52 | latestPost: rawBlogInfo.feed ? await getLatestPost(rawBlogInfo.feed) : null, 53 | }; 54 | }) 55 | ); 56 | return augmentedBlogInfo; 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/pages/blogroll/blogroll.webc: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: "blogroll/" 3 | title: "Blogroll" 4 | description: "An evolving collection of blogs that I enjoy." 5 | classnames: "flow" 6 | --- 7 | 8 | 17 | 18 |

Blogroll, please!

19 |

20 | Inspired by all the blogroll greatness across the web, I present this endlessly evolving 21 | collection of blogs that I enjoy. Their latest posts were gathered together on 22 | . 23 |

24 | 43 | -------------------------------------------------------------------------------- /src/pages/index.webc: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: "/" 3 | --- 4 | 5 | 8 | 9 |

Ryan Mulligan

10 | 11 |
12 |

13 | This is a website made by me, Ryan Mulligan, a front-end builder of the web and 14 | fellow passenger through space and time. 15 |

16 |
    17 |
  • 18 | The latest site build was deployed in the 19 | on . 20 |
  • 21 |
  • 22 | During deployment, the weather over here was 23 | . 27 |
  • 28 |
  • I was listening to
  • 29 |
30 |

Most recent articles

31 | 32 |

33 | Read more articles 34 |

35 |

The bit about myself

36 |

37 | I'm a front-end engineer and creative developer with over a decade of experience building for 38 | the web. I've established an extensive, versatile skillset servicing both product and marketing 39 | teams. My back catalog of roles range from individual contribution to management, team building, 40 | and leadership. 41 |

42 |

43 | I perpetually advocate for modern web standards, embrace the web platform, and believe in the 44 | rule of least power—use the simplest language to do the job well, then scale as it becomes 45 | necessary. My priorities are influenced by empathy: lift up those around me so we may succeed 46 | together, always care for the end user, and deliver a performant, inclusive, and accessible web. 47 |

48 |

49 | Currently, I build for the web with a lovely team at Netlify. When I'm not coding, you can catch 50 | me noodling on my acoustic guitar or blasting out rhythms if there's a drum kit ready for a 51 | lefty nearby. 52 |

53 |

Want to know more?

54 |

Me around the web

55 |

56 | Other virtual networks where you can find me if you're feeling adventurous. Let's connect and 57 | fork and toot or whatever. 58 |

59 | 71 | 72 |

Some personal joy

73 |
    74 |
  • All things CSS
  • 75 |
  • Finding the perfect custom cubic Bézier curve
  • 76 |
  • Building an inclusive web
  • 77 |
  • Any beach, any ocean
  • 78 |
  • Early morning coffee
  • 79 |
  • Nick Drake's Pink Moon in its entirety
  • 80 |
  • Cool side of the pillow
  • 81 |
  • Guitar noodling
  • 82 |
  • Trashing a drum kit
  • 83 |
84 |
85 | 86 | 127 | -------------------------------------------------------------------------------- /src/pages/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "base" 3 | } 4 | --------------------------------------------------------------------------------