diff --git a/404.html b/404.html new file mode 100644 index 0000000..c646f43 --- /dev/null +++ b/404.html @@ -0,0 +1,79 @@ + + + + 404 | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + +
+ +

404

+ + +

Page Not Found

+
+ + +
+ + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..10f6d08 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Site Build + - Page Count: 11 + - Build Date: 2024-07-23 09:20:42 + \ No newline at end of file diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..458c9d8 Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..0b7a35b Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..8297815 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/bg-paper-light.webp b/bg-paper-light.webp new file mode 100644 index 0000000..b574a10 Binary files /dev/null and b/bg-paper-light.webp differ diff --git a/cv.css b/cv.css new file mode 100644 index 0000000..306cd3d --- /dev/null +++ b/cv.css @@ -0,0 +1,105 @@ +:root { + --spacer: calc(var(--r) / 4); +} + +main { + padding-top: var(--rh5); + padding-right: var(--rh4); + padding-bottom: var(--rh6); + padding-left: var(--rh8); +} + +#cv-footer { + display:flex; + justify-content: end; + padding: var(--r) 0; + gap: var(--r); +} + +#cv-footer a { + box-shadow: none; +} + +h1 { + --r-count: 2; + --r-factor: 0.75; +} + +h2 { + --r-count: 1.5; + --r-factor: 0.75; +} + +h3 { + --r-count: 1; +} + +ul { + position: absolute; + margin-top: calc(-1 * var(--rh6)); + right: 0; + text-align: right; +} + +[data-id="Education"]~h3, [data-id="Education"]~h4 { + display: inline-block; + margin: 0 var(--spacer) var(--spacer) 0; +} + + +h2+h3 { + display: inline-block; + margin-right: var(--spacer); +} + +h3+h4 { + display: inline-block; + font-weight: normal; +} + +p > strong { + font-weight: normal; +} + + +@media print { + * { + box-shadow: none !important; + page-break-before: avoid; + page-break-after: avoid; + break-inside: avoid; + } + h2:has(a):nth-of-type(5) { + page-break-before: always; + } + h2 { + page-break-before: auto; + break-inside: auto; + } + :root, body { + background: none; + } + :root::after, body::after { + content: none !important; + } + nav, footer, .post-script { + display: none; + } + main { + padding: 0; + } + :root { + --r: 22px; + } + body { + padding: 0; + } + .content { + max-width: none; + } + +} + +@page { + margin: 2cm 1.5cm 1.5cm 2.5cm; +} \ No newline at end of file diff --git a/cv/index.html b/cv/index.html new file mode 100644 index 0000000..d462709 --- /dev/null +++ b/cv/index.html @@ -0,0 +1,128 @@ + + + + CV 2024-07-20 + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Robert Starbuck

+

Web Developer

+

Born 1983

+ +

Frontend focussed developer and designer. With a background in design, I have a good eye for what works. Expansive experience of libs and frameworks including Lit, React and Angular with Typescript, CSS et al. along with UX Development and Graphics Suites. I'm a firm believer in "using the platform".

+

Experience

+

Rippl - Incentives Platform

+

Frontend Developer

+

Jul 2022 - Feb 2024, Remote

+

UI development with web-components built in lit.dev. Building a bespoke component library and implementing those components accross Rippl sites and with the help of a brilliant team. Later joining a separate team working on modernising an AngularJS project.

+

Polecat - Reputation Management

+

Frontend Developer

+

Apr 2017 - Dec 2021, Bristol

+

UI development with React in Typescript, incorporating functional programming with RamdaJS, occasionally contributing as a graphic designer. My contributions were various, and included visualisations with D3 and helping to refactor our sites to use React. The team was very gifted and I learnt a great deal. We strived to a fault for best practices and would always make time for stand-ups, retros and hackathons.

+

Buddi - GPS Solutions

+

UI Developer

+

Oct 2015 - Aug 2016, London

+

Full stack role building UIs and a new company website with Angular, Shopify and a Sagepay. The site was originally written in Jekyll and later rewritten in Hugo.

+

web.cv - Personal Start-up

+

Founder / Fullstack

+

Sep 2014 - Sep 2015, London

+

Unsuccessful start-up venture with a team of one. I built the front­end of web.cv with AngularJS with the hope of partnering with a backend engineer. Working by myself was good for discipline, though I certainly lacked the feedback I needed to either move the project forward or understand when to stop.

+

Merchant Cantos - Marketing Agency

+

Fullstack Developer

+

Feb 2014 - Jun 2014, London

+

Contract work as a front-end dev. Clients included several big names from ebay to facebook. Deadlines were tight and I was hard­worked but fully enjoyed my time with the team.

+

Y.CO - The Yacht Company

+

Fullstack

+

Nov 2010 - Aug 2013, Monaco

+

In-house developer for a yacht company based in Monaco. My role within the company was diverse. I mostly worked autonomously, managing projects and building tools for charter destinations and another for charter yacht selections. Whilst at the company I learnt to speak French. Voilà.

+

Acquire Services - Procurement

+

UI Developer

+

Aug 2007 - Oct 2010, Skipton

+

Sky Sports

+

ActionScript Developer

+

June 2006 - Aug 2007, Harrogate

+

Education

+

BA in Electronic Media Design

+

2001 - 2004, University of Sunderland

+

BTEC Design & A level Photography

+

1999 - 2001, Harrogate Art College

+

9 GCSEs (Maths,English)

+

1994 - 1999, Harrogate Grammar School

+ +
+
+
+ + + Markdown + +
+ + + + \ No newline at end of file diff --git a/documents/cv/2024-03-19.pdf b/documents/cv/2024-03-19.pdf new file mode 100644 index 0000000..129f304 Binary files /dev/null and b/documents/cv/2024-03-19.pdf differ diff --git a/documents/cv/CV 2024-07-20.pdf b/documents/cv/CV 2024-07-20.pdf new file mode 100644 index 0000000..8e89396 Binary files /dev/null and b/documents/cv/CV 2024-07-20.pdf differ diff --git a/documents/cv/archive/2022-02-16.pdf b/documents/cv/archive/2022-02-16.pdf new file mode 100644 index 0000000..090165c Binary files /dev/null and b/documents/cv/archive/2022-02-16.pdf differ diff --git a/documents/cv/archive/2022-05-23.pdf b/documents/cv/archive/2022-05-23.pdf new file mode 100644 index 0000000..aa59ba4 Binary files /dev/null and b/documents/cv/archive/2022-05-23.pdf differ diff --git a/documents/cv/archive/2022-06-15.pdf b/documents/cv/archive/2022-06-15.pdf new file mode 100644 index 0000000..7d5bca6 Binary files /dev/null and b/documents/cv/archive/2022-06-15.pdf differ diff --git a/documents/cv/archive/2022-10-01.pdf b/documents/cv/archive/2022-10-01.pdf new file mode 100644 index 0000000..bba4225 Binary files /dev/null and b/documents/cv/archive/2022-10-01.pdf differ diff --git a/documents/cv/archive/2024-01-22.pdf b/documents/cv/archive/2024-01-22.pdf new file mode 100644 index 0000000..8c291b0 Binary files /dev/null and b/documents/cv/archive/2024-01-22.pdf differ diff --git a/documents/cv/archive/2024-02-04.pdf b/documents/cv/archive/2024-02-04.pdf new file mode 100644 index 0000000..459ab63 Binary files /dev/null and b/documents/cv/archive/2024-02-04.pdf differ diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..f48e373 Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..89ee99f Binary files /dev/null and b/favicon-32x32.png differ diff --git a/font.css b/font.css new file mode 100644 index 0000000..453e129 --- /dev/null +++ b/font.css @@ -0,0 +1,27 @@ +/* https://rsms.me/inter/ */ +html, button { + font-family: "Inter", sans-serif; +} + +@supports (font-variation-settings: normal) { + html, button { + font-family: "Inter var", sans-serif; + } +} + +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap'); + +code { + font-family: "JetBrains Mono", monospace; +} + +pre code { + border-radius: calc(var(--r) * 0.25); + display: block; + padding: calc(var(--pd) * 0.5); + color: var(--highlight-color); + background: var(--highlight-bg); + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; +} \ No newline at end of file diff --git a/highlight-js/robstarbuck.uk.css b/highlight-js/robstarbuck.uk.css new file mode 100644 index 0000000..c5441ac --- /dev/null +++ b/highlight-js/robstarbuck.uk.css @@ -0,0 +1,136 @@ +/*! + https://github.com/highlightjs/highlight.js/blob/5bcb6c66133706ea84cfd3cfd7492b3e5321ca85/src/styles/stackoverflow-dark.css +*/ + +:root { + + --code-shade0: var(--shade0); + --code-shade1: var(--shade1); + --code-shade2: var(--shade2); + --code-shade3: var(--shade3); + --code-shade4: var(--shade4); + --code-shade5: var(--shade5); + --code-shade6: var(--shade6); + --code-shade7: var(--shade7); + --code-shade8: var(--shade8); + + /* https://gka.github.io/palettes/#/9|s|34806c,00ff8d,fffcf8|662483,00eaff|1|1 */ + --code-accent0: #004f4d; + --code-accent1: #146658; + --code-accent2: #297d64; + --code-accent3: #409470; + --code-accent4: #5bac7e; + --code-accent5: #7ac38e; + --code-accent6: #9fd99f; + --code-accent7: #caeeb4; + --code-accent8: #ffffcc; + + --code-attr: #88efff; + + --highlight-bg: var(--code-shade0); + --highlight-color: var(--code-shade7); + --highlight-comment: var(--code-shade3); + --highlight-keyword: var(--code-accent4); + --highlight-attribute: var(--code-accent7); + --highlight-symbol: #c59bc1; + --highlight-namespace: var(--code-accent6); + --highlight-variable: #ffef16; + --highlight-literal: var(--code-attr); + --highlight-punctuation: var(--code-shade2); + --highlight-deletion: #de7176; + --highlight-addition: var(--code-accent4); +} + + + +.hljs-subst { + color: var(--highlight-color); +} + +.hljs-comment { + color: var(--highlight-comment); +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-meta .hljs-keyword, +.hljs-doctag, +.hljs-section { + color: var(--highlight-keyword); +} + +.hljs-attr { + color: var(--highlight-attribute); +} + +.hljs-attribute { + color: var(--highlight-symbol); +} + +.hljs-name, +.hljs-type, +.hljs-number, +.hljs-selector-id, +.hljs-quote, +.hljs-template-tag { + color: var(--highlight-namespace); +} + +.hljs-selector-class { + color: var(--highlight-keyword); +} + +.hljs-string, +.hljs-regexp, +.hljs-symbol, +.hljs-variable, +.hljs-template-variable, +.hljs-link, +.hljs-selector-attr { + color: var(--highlight-variable); +} + +.hljs-meta, +.hljs-selector-pseudo { + color: var(--highlight-keyword); +} + +.hljs-built_in, +.hljs-title, +.hljs-literal { + color: var(--highlight-literal); +} + +.hljs-bullet, +.hljs-code { + color: var(--highlight-punctuation); +} + +.hljs-meta .hljs-string { + color: var(--highlight-variable); +} + +.hljs-deletion { + color: var(--highlight-deletion); +} + +.hljs-addition { + color: var(--highlight-addition); +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-formula, +.hljs-operator, +.hljs-params, +.hljs-property, +.hljs-punctuation, +.hljs-tag { + /* purposely ignored */ +} \ No newline at end of file diff --git a/highlight-js/tomorrow-night.css b/highlight-js/tomorrow-night.css new file mode 100644 index 0000000..3c7779e --- /dev/null +++ b/highlight-js/tomorrow-night.css @@ -0,0 +1,59 @@ +/* Tomorrow Night Theme */ +/* https://jmblog.github.io/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* https://jmblog.github.io/color-themes-for-google-code-highlightjs */ + +:root { + --color-comment: #969896; + --color-red: #cc6666; + --color-orange: #de935f; + --color-yellow: #f0c674; + --color-green: #b5bd68; + --color-aqua: #8abeb7; + --color-blue: #81a2be; + --color-purple: #b294bb; + --color-background: #1d1f21; /* Added for background */ + --color-text: #c5c8c6; /* Added for main text */ +} + +.tomorrow-comment, pre .comment, pre .title { + color: var(--color-comment); +} + +.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { + color: var(--color-red); +} + +.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant { + color: var(--color-orange); +} + +.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute { + color: var(--color-yellow); +} + +.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata { + color: var(--color-green); +} + +.tomorrow-aqua, pre .css .hexcolor { + color: var(--color-aqua); +} + +.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { + color: var(--color-blue); +} + +.tomorrow-purple, pre .keyword, pre .javascript .function { + color: var(--color-purple); +} + +pre code { + display: block; + background: var(--color-background); + color: var(--color-text); + font-family: Menlo, Monaco, Consolas, monospace; + line-height: 1.5; + border: 1px solid #ccc; + padding: 10px; +} diff --git a/index.css b/index.css new file mode 100644 index 0000000..3762183 --- /dev/null +++ b/index.css @@ -0,0 +1,257 @@ +/* https://gka.github.io/palettes/#/9|s|361d1e,fffcf8|662483,00eaff|1|1 */ +:root { + --shade0: #2b2031; + --shade1: #423746; + --shade2: #5a505d; + --shade3: #746a75; + --shade4: #8e858e; + --shade5: #a9a2a7; + --shade6: #c5bfc2; + --shade7: #e2dddc; + --shade8: #fffcf8; + + --shadow: 0px var(--rh1) var(--rq) calc(var(--rq) * -1) #2b203144; + + --width-p: 65ch; + --pd: var(--rh4); + --width-trunk: calc(var(--pd) + var(--width-p) + var(--pd)); + + --background-width: calc(3800px / 2.5); +} + +:root { + background-image: url('bg-paper-light.webp'); + background-blend-mode: normal; + background-color: var(--shade3); + background-size: var(--background-width); + background-position: bottom center; + background-attachment: fixed; + padding-bottom: var(--rh1); + + color: var(--shade0); +} + +:root:not(.breakout)::after { + z-index: -1; + content: ''; + display: block; + position: fixed; + inset: 0; + background: radial-gradient(circle at 50% -10vh, transparent 20vh, var(--shade0) 180vh); + background-attachment: fixed; + padding: 70px; +} + +:root:not(.breakout) body::after { + content: ''; + display: block; + background-image: url('bg-paper-light.webp'); + background-color: var(--shade4); + mix-blend-mode: darken; + height: calc(var(--r) * 10); + background-size: var(--background-width); + background-position: bottom center; + background-blend-mode: multiply; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: -1; +} + +:root.breakout { + padding: 0; +} +:root.full-page-svg svg, +:root.full-page-iframe iframe { + height: 100vh; + width: 100vw; +} + +.content { + position: relative; +} + +main { + position: relative; + margin-inline: auto; + max-width: var(--width-p); + background-color: var(--shade8); + background-blend-mode: multiply; + overflow: hidden; + padding: var(--pd); + box-sizing: content-box; + box-shadow: var(--shadow); + color: var(--shade0); +} + +nav, +footer, +.post-script { + max-width: var(--width-trunk); + margin-inline: auto; + height: var(--rh8); + display: flex; + place-items: center; + box-sizing: content-box; + padding: 0 var(--r); + gap: var(--r); +} + +.post-script { + height: var(--rh4); + box-sizing: border-box; + border-bottom: 1px solid var(--shade3); + padding: 0; +} + +footer a { + text-decoration: none; +} + +time { + white-space: nowrap; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p, +ol, +ul, +table, +pre, +figure { + margin: 0 0 var(--r); + padding: 0; +} + +hr { + margin: 20px 0; + border-collapse: collapse; + border: 0.5px solid var(--shade6); +} + +.svg, +.image, +.iframe, +.custom, +pre { + overflow: hidden; + position: relative; + border-radius: var(--rq); + margin-inline: calc(var(--r) * -1); + margin-bottom: var(--r); + border: 1px solid; + border-color: var(--shade7) var(--shade7) var(--shade6) var(--shade7); + background-color: var(--shade4); + box-shadow: var(--shadow); +} + +pre { + border-color: var(--shade0) var(--shade0) var(--shade2) var(--shade0); +} + +img, +iframe, +svg { + display: block; +} + +iframe { + width: 100%; + min-height: calc(var(--r) * 16); +} + +a { + color: inherit; + text-underline-offset: 3px; + text-decoration-color: var(--shade3); +} + +a:hover { + text-decoration-color: var(--shade2); +} + +main a { + text-decoration-color: var(--shade6); +} + +main a:hover { + text-decoration-color: var(--shade3); +} + +footer { + place-content: end; +} + +footer svg { + height: calc(var(--r) * 0.55); +} + +footer a { + display: flex; + place-items: baseline; + gap: var(--rh); +} + +::selection { + background-color: var(--shade6); +} + +main:not(.home, .cv) ol { + list-style-position: inside; +} +main:not(.home, .cv) ul { + list-style: disc; + list-style-position: inside; +} + +button.breakout-back, +a.breakout-link { + text-align: right; + position: absolute; + top: var(--rh2); + right: var(--rh2); + background: transparent; + cursor: pointer; + height: var(--rh3); + display: inline-flex; + place-items: center; + padding-left: var(--rh1); + padding-right: var(--rh1); + text-decoration: none; + border: 1px solid; + box-sizing: border-box; + color: var(--breakout-link-color, var(--shade3)); + border-radius: var(--rq);; + border-color: var(--breakout-link-color, var(--shade7)); +} + +button.history-back { + position: fixed; + top: var(--rh2); + right: var(--rh2); + line-height: var(--rh3); + background: transparent; + border: 0; + cursor: pointer; +} + +.colx2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--rh2); +} + +.text { + --r-count: 2; + --r-factor: 0.75; +} + +.text code { + color: var(--shade6); +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..aca538b --- /dev/null +++ b/index.html @@ -0,0 +1,76 @@ + + + + Posts | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ CV Jul 2024 +
+ + + + + + \ No newline at end of file diff --git a/minireset.css b/minireset.css new file mode 100644 index 0000000..f624777 --- /dev/null +++ b/minireset.css @@ -0,0 +1,75 @@ +/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */ +html, +body, +p, +ol, +ul, +li, +dl, +dt, +dd, +blockquote, +figure, +fieldset, +legend, +textarea, +pre, +iframe, +hr, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + padding: 0; +} + +/* h1, +h2, +h3, +h4, +h5, +h6 { + font-size: 100%; + font-weight: normal; +} */ + +ul { + list-style: none; +} + +button, +input, +select { + margin: 0; +} + +html { + box-sizing: border-box; +} + +*, *::before, *::after { + box-sizing: inherit; +} + +img, +video { + height: auto; + max-width: 100%; +} + +iframe { + border: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} \ No newline at end of file diff --git a/posts/count-to-1023-on-two-hands/index.html b/posts/count-to-1023-on-two-hands/index.html new file mode 100644 index 0000000..26021cf --- /dev/null +++ b/posts/count-to-1023-on-two-hands/index.html @@ -0,0 +1,169 @@ + + + + Count to 1023 on Two Hands | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Count to 1023 on Two Hands

+ +

If you can accept that ten binary digits 1111111111 represents 1023 in decimal, we can of course use our ten fingers to represent any number counting up to it.

+
+

This is a brief whistle-stop tour of a JS project that converts a number to it's handy binary representation. In building it I learnt a little about JS Bitwise Operators and their application. This post covers the utility of those operators and might interest any JS devs who are curious about their usage.

+

Let's start with a single closed fist as our 0, you can click the fingers or adjust the input to change the value.

+
+

I'm sure you're getting the gist. Wherever a finger is extended we count the digit in our calculation. Let's represent that most rock-and-roll of numbers, 18.

+
+

Translating the decimal value "18" to it's binary value "10010" looks like this:

+
Binary         1     0     0     1     0
+Bit Value      16    8     4     2     1
+Bit Position   5     4     3     2     1
+
+

These bit positions of course correspond to fingers. The number 18 has 1s in bit positions 2 and 5 represented by our pinky and index fingers.

+

We can prove our binary value equals 18 using parseInt and including a radix (or base) of 2 as our second argument.

+
parseInt("10010", 2);
+// 18
+
+

The radix tells parseInt we're dealing with a binary number (2 numbers, 1 and 0) and not a decimal (10 numbers, 0-9, parseInt's default value).

+
+

Anyone who ever mistakenly tried ["1","2","3"].map(parseInt) learnt the hard way about this second argument.

+
+

It is of course possible to derive our "18" value from an array of booleans:

+
[true, false, false, true, false] => 10010 => 18
+
+

But that's well covered ground and not what I'm exploring here. Instead we'll be deriving our boolean values (is a finger extended) from our number.

+

Calculating the max value for a given number of fingers

+

The maximum value that can be represented by 10 fingers is 2 to the power of 10 minus one. Or (2 ** 10) -1 in JS.

+

The following chart shows the calculation for 2 to the power n.

+
n        0    1    2    3    4    5    6    7    8    9    10
+2 ** n   1    2    4    8    16   32   64   128  256  512  1024
+Finger   1    2    3    4    5    6    7    8    9    10   11
+
+

So our tenth finger will represent the number 512 but this isn't our maximum as we've yet to count all the fingers that came before it and doing so gives us our maximum value 1023.

+
1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512
+// 1023
+
+

To avoid totalling these numbers it's easier to take our 11th value 1024 and minus one.

+

We can the parity of these values can be proven in JS.

+
(1023).toString(2);
+// "1111111111"
+(1023).toString(2).length;
+// 10
+(2 ** 10) -1 === 1023;
+// true
+parseInt("1111111111",2) === 1023
+// true
+
+

Each Fingers Value

+

Another thing we need to know is the value of each finger from right to left. Just as 2 ** 10 gave us the value of our 11th finger. We can use 2 ** n to calculate each finger's binary value.

+
Array(10)
+    .fill(null)
+    .map((_, i) => 2 ** i);
+    // [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 ]
+
+

Should a finger be extended

+

As we loop through the value of each finger we can use the AND (&) bitwise operator to determine if a value is included in our total and therefore the applicable finger extended. This is completely different to the logical AND represented by "&&". We'll explore this problem for our most knarliest of numbers 17.

+
+

To get our Result in the following examples we use our operator "&" against each finger's corresponding Value. In decimal this would look like:

+ +

Here are the same calculations visualised in binary.

+
Input                  00001   00010   00100   01000   10000
+Value (17)             10001   10001   10001   10001   10001
+Input & Value (Result) 00001   00000   00000   00000   10000
+Result === Input       TRUE    FALSE   FALSE   FALSE   TRUE
+
+

I hope you can see what's happening here, wherever a 1 appears in the same bit position for both the Input and the Value the result Input & Value gets a 1 in the same position. If the result matches the input we know that a finger should be extended. Let's run that calculation in JS to prove we're right.

+
[1,2,4,8,16].map(v => (v & 17) === v)
+// [true, false, false, false, true]
+
+

Closing and extending fingers

+

Our last task is closing and extending fingers or taking away and adding values. Let's say that we have a single hand with all of it's fingers extended, giving us the number 31 or 11111 in binary. Let's close that most British of digits, the pinky finger, represented by the number 16 or 10000.

+

Our operator in this case will be XOR (^), this returns a 1 for every bit position where the values differ.

+
Input (16)            10000
+Value (31)            11111
+Input ^ Value (15)    01111  
+
+

Because XOR is only interested in the difference between the two binary values, we can use the same operator to extend our pinky finger, adding it back to the hand.

+
Value (15)            01111
+Input (16)            10000
+Value XOR Input (31)  11111  
+
+

Summary

+

We've proven that an array of boolean values can equally be represented by a single number. Whilst Bitwise operators provide a powerful toolset for manipulating that number it is not without its limitations. The biggest limitation is the number of elements (or fingers) we can represent. Because of JS's implementation we are restricted to a maximum safe value of 9007199254740991, retrievable with the MAX_SAFE_INTEGER constant:

+
Number.MAX_SAFE_INTEGER
+// 9007199254740991
+// 11111111111111111111111111111111111111111111111111111
+
+

These 52 1s represent 53 elements in an array, meaning we can safely operate on no larger number.

+

Finally I'd be doubtful that anyone would thank you for the inclusion of bitwise operators in source code, their usage is certainly a little obscure but they've been fun to play around with.

+ +
+ + + + + + \ No newline at end of file diff --git a/posts/steganography-in-js/index.html b/posts/steganography-in-js/index.html new file mode 100644 index 0000000..e21dd65 --- /dev/null +++ b/posts/steganography-in-js/index.html @@ -0,0 +1,201 @@ + + + + Steganography with Zero Width Characters (Z-Chars) | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Steganography with Zero Width Characters (Z-Chars)

+ +
+

Steganography the practice of hiding a message in something that is not secret. Certain unicode characters are zero-width, meaning that they won't be printed and can't be seen. Using these character we can encode characters and hide messages.

+

In this post I'll provide a quick explanation of some of the key aspects of Z-Chars. The post might interest Javascript devs who are curious about unicode and the bidirectional algorithm.

+

POLITE NOTICE There's enough trash on the web without hidden characters gumming up our messages, please don't use this in the wild.

+

Hiding characters in text

+

Wherever text is represented digitally, it's likely to be encoded using Unicode. Some Unicode characters are intentionally invisible, a carriage return for example () can't be seen although we can see it's effect. Such characters are known as control characters.

+

Because not all languages read from left to right, some require special mark-up to ensure they run from right to left. Take the word "Egypt" as an example.

+
+
+
Egypt
+ +
Latin characters read left to right
+
+
+
مصر
+ +
Arabic characters read right to left
+
+
+

Computers need to know which direction these characters run in and one way to indicate this is with control characters.

+

There are nine such characters as specified in the Unicode Bidirectional Algorithm. The nine characters are:

+
U+2066   LEFT-TO-RIGHT ISOLATE
+U+2067   RIGHT-TO-LEFT ISOLATE
+U+2068   FIRST-STRONG ISOLATE
+U+202A   LEFT-TO-RIGHT EMBEDDING
+U+202B   RIGHT-TO-LEFT EMBEDDING
+U+202D   LEFT-TO-RIGHT OVERRIDE
+U+202E   RIGHT-TO-LEFT OVERRIDE
+
+U+202C   POP DIRECTIONAL FORMATTING
+U+2069   POP DIRECTIONAL ISOLATE
+
+

I'm not going to be digging much into the details but hopefully the names alone provide some indication of their use. Let's explore how they'll print in the browser. We'll be using the UTF-16 reference listed above. In JS we can access these characters with '\uCODE'. The letter "A" for example is '\u0041'.

+
'\u0041'
+// "A"
+
+'\u0041\u0042\u0043'
+// "ABC"
+
+'\u2066\u2067\u2068\u202A\u202B\u202D\u202E\u202C\u2069'
+// "" [Nothing is visible]
+
+

As you can see, our nine characters print as an empty string "". Now let's try to insert these characters between other letters.

+
'A' +
+'\u2066\u202A\u202D' +
+'B' +
+'\u2066\u202A\u202D' +
+'C'
+// "A⁦‪‭B⁦‪‭C"
+
+

Again the control characters between the letters can't be seen.

+

Because the assumed use for this is hiding characters in the latin alphabet, we know the text will always run left to right, to guarantee this it's safest to limit ourselves to the three left-to-right control characters:

+
U+2066   LEFT-TO-RIGHT ISOLATE
+U+202A   LEFT-TO-RIGHT EMBEDDING
+U+202D   LEFT-TO-RIGHT OVERRIDE
+
+

We now have the means to hide characters, all we need now is a way to encode them to other characters.

+

Encoding and Decoding

+

So we have our three characters with which we can encode our hidden letters and this is great because if you have an understanding of binary, you'll know that we only need two 0 and 1. Let's see how we can use these two characters to represent the letter "X".

+
"X".charCodeAt(0)
+// 88
+"X".charCodeAt(0).toString(2);
+// "1011000"
+
+

charCodeAt gives us the unicode character reference for our letter, toString(2) then converts this to a binary string or a number with a base of 2. To get back to our "X" we can use fromCodePoint to reverse the process.

+
parseInt("1011000",2)
+// 88
+String.fromCodePoint(88)
+// "X"
+String.fromCodePoint(parseInt("1011000",2))
+// "X"
+
+

Because we know we can represent "X" using 0 and 1, it's a short step to swapping these characters out to the control characters that can't be seen.

+
1 => U+2066
+0 => U+202A
+1 => U+2066
+1 => U+2066
+0 => U+202A
+0 => U+202A
+0 => U+202A
+
+

So an X hidden between A and B might look like this:

+
'A'+'\u2066\u202A\u2066\u2066\u202A\u202A\u202A'+'B'
+// A⁦‪⁦⁦‪⁦‪B
+
+

Because we have three characters however, it makes sense to use them all. This changes our base (or radix) to three. Encoding and decoding three characters look like this:

+
// ENCODING
+
+"X".charCodeAt(0)
+// 88
+"X".charCodeAt(0).toString(3);
+// "10021" [Comprises three characters 0,1 and 2]
+
+// DECODING
+
+parseInt("10021",3)
+// 88
+String.fromCodePoint(88)
+// "X"
+String.fromCodePoint(parseInt("10021",3))
+// "X"
+
+

Let's take another example by hiding "HIDDEN" within the word "VISIBLE". Because our z-chars fit between our visible letters, the number of characters we can encode is the number of visible letters minus one. So our visible letters demark our hidden ones.

+

Here's a visualisation of the encoding:

+
+
+ V‭‭⁦⁦I‭‭⁦‪S‭‪‪‭I‭‪‪‭B‭‪‭⁦L‭‭‭⁦E⁩ +
Characters represented by zero-width characters
+
+
+ V2200I2201S2112I2112B2120L2220E +
Characters represented by 0,1,2
+
+
+ VHIISDIDBELNE +
Characters decoded
+
+
+

Summary

+

Other than a few functions to split strings and interpolate the hidden characters, this all but covers the core concepts behind Z-Chars. Pray you never use them to this effect.

+

Please take a look through the source code if you're curiouser still.

+

Links

+ + +
+ + + + + + \ No newline at end of file diff --git a/posts/treact-recursive-components-in-react/basic-screenshot.png b/posts/treact-recursive-components-in-react/basic-screenshot.png new file mode 100644 index 0000000..be03c40 Binary files /dev/null and b/posts/treact-recursive-components-in-react/basic-screenshot.png differ diff --git a/posts/treact-recursive-components-in-react/employboy-demo.html b/posts/treact-recursive-components-in-react/employboy-demo.html new file mode 100644 index 0000000..ae6ac8f --- /dev/null +++ b/posts/treact-recursive-components-in-react/employboy-demo.html @@ -0,0 +1,26 @@ + + + Treact - Recursive Components in React | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/posts/treact-recursive-components-in-react/index.html b/posts/treact-recursive-components-in-react/index.html new file mode 100644 index 0000000..f340228 --- /dev/null +++ b/posts/treact-recursive-components-in-react/index.html @@ -0,0 +1,287 @@ + + + + Treact - Recursive Components in React | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Treact - Recursive Components in React

+ +

React is ideally suited to the task of creating trees, being at its core, a tree of functions that call one another. In this demo we’ll look at creating a tree using a data set containing trees (of the Kingdom Plantae). So the theme is trees.

+

Here’s the demo that we are working towards.

+
Open ↗
+

Flat vs Nested Data

+

As our subject is trees it’s their taxonomic rank that we’ll be exploring. There are many but we'll be using only a subset:

+
1. Kingdom
+2. Order
+3. Family
+4. Genus
+5. Species (or Common Name)
+
+

So an ash tree would come out as

+
1. Plantae
+	2. Lamiales
+		3. Oleaceae
+			4. Fraxinus
+				5. Fraxinus excelsior (Ash)
+
+

One common way to represent tree data is to nest the containing information.

+
[
+  {
+    "taxon": "Kingdom",
+    "value": "Plantae",
+    "children": [
+      {
+        "taxon": "Order",
+        "value": "Lamiales",
+        "children": [
+          {
+            "taxon": "Family",
+            "value": "Oleaceae",
+            "children": [
+              {
+                "taxon": "Genus",
+                "value": "Fraxinus",
+                "children": [
+                  {
+                    "taxon": "Species",
+                    "value": "Fraxinus excelsior",
+                    "children": []
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
+
+

Whilst this seems a natural fit it has many disadvantages.

+ +

A more flexible approach is to keep our data flat. In this instance we already know the levels of our tree (the taxonomic ranks we’ll be using), this might not be true for other trees. A family tree for example could span any number of generations. Here however our levels are known to us in advance (those being Kingdom, Order, Family, Genus, Species)

+

As such the Ash tree can be easily represented:

+
{
+  "Kingdom": "Plantae",
+  "Order": "Lamiales",
+  "Family": "Oleaceae",
+  "Genus": "Fraxinus",
+  "Species": "Fraxinus excelsior"
+}
+
+

Certainly much easier to grok from my point of view and I’ll demonstrate further advantages later on. With our data model agreed upon (well I’m happy with it), let’s render it out in React.

+

A Basic Implementation

+

We can get a basic implementation working in 50 or so (readable) lines of Typescript.

+

+import "./App.css";
+import { FC } from "react";
+import allTrees from "./data.json";
+
+// Types
+type Tree = typeof allTrees[number];
+type TaxonomicRank = keyof Tree;
+type TaxonomyProps = { children: Array<Tree>; rank: TaxonomicRank }
+
+// This is only a subset of - domain, kingdom, phylum, class, order, family, genus, species
+const taxonomicRanks: Array<TaxonomicRank> = [
+  "Kingdom",
+  "Order",
+  "Family",
+  "Genus",
+  "Species"
+];
+
+const renderTaxon = (taxon) => {
+
+  // We are only passing the taxa of this rank to the next Taxonomy
+  // Without this filter, the component will call itself indefinitely
+
+  const childrenOfTaxon = children.filter(
+    (t) => t[rank] === taxon[rank]
+  );
+
+  const subRank = taxonomicRanks.at(taxonomicRanks.indexOf(rank) + 1);
+
+  return (
+    <details open>
+      <summary>
+        {taxon[rank]}
+      </summary>
+
+      {/* Here the element now calls itself */}
+
+      {subRank && <Taxonomy rank={subRank}>{childrenOfTaxon}</Taxonomy>}
+    </details>
+  );
+}
+
+// Our recursive taxonomy which calls itself where child taxonomies exist
+const Taxonomy: FC<TaxonomyProps> = (props) => {
+  const { children, rank } = props;
+
+  // Find the unique taxa in our child taxonomies
+  // EG unique taxa with a key of "Genus"
+  const taxaInRank = children.reduce<Array<Tree>>((a, c) => {
+    if (a.find((v) => v[rank] === c[rank])) {
+      return a;
+    }
+    return [...a, c];
+  }, []);
+
+  return (
+    <div>
+      {/* Loop through the taxa in the rank */}
+      {taxaInRank.map(renderTaxon)}
+    </div>
+  );
+};
+
+function App() {
+  return (
+    <div className="App">
+      {/* Our initial call to our Taxonomy */}
+      <Taxonomy rank={taxonomicRanks[0]}>{allTrees}</Taxonomy>
+    </div>
+  );
+}
+
+export default App;
+
+

Here’s the same code on github.

+

With minimal CSS we already have our tree rendering every level of our taxa up to the tree itself, our leaf node.

+

Basic Screenshot

+

Improvements

+

Because we’ve kept our schema flat by abstracting the keys (in this case taxonomic ranks) we can modify which taxonomic ranks we view. Currently we are recursing through "Kingdom", "Order", "Family", "Genus", "Species”. If we only want to observe the “Species” though, this can easily be achieved by passing it in to our initial Taxonomy.

+
function App() {
+  const rank = "Species";
+  return (
+    <div className="App">
+      {/* Our initial call to our Taxonomy */}
+      <Taxonomy rank={rank}>{allTrees}</Taxonomy>
+    </div>
+  );
+}
+
+

With this in mind, it’s easy to see how rank may be stateful, allowing users to limit the ranks being viewed.

+

We can also opt to observe different keys of our trees, for instance Species is probably not all that useful to most people who know trees by their common names.

+
{
+    "Name": "Ash",
+    "Species": "Fraxinus excelsior"
+    // ...
+}
+
+

Again, this is easily toggled by switching between two different taxa.

+
const taxaByName: Array<TaxonomicRank> = [
+  "Kingdom",
+  "Order",
+  "Family",
+  "Genus",
+  "Name",
+];
+
+const taxaBySpecies: Array<TaxonomicRank> = [
+  "Kingdom",
+  "Order",
+  "Family",
+  "Genus",
+  "Species",
+];
+
+

The complete source code for this project can be found in the repo.

+

Summary

+

Even when everything in your repo is screaming “TREE!” keeping data flat can may prove more flexible. In this instance we knew our groups in advance, where this might not be the case extra keys might indicate relationships between nodes.

+
[
+    // ...
+    {
+        "id": "LETOTHEJUST",
+        "fatherId": "THEOLDDUKE",
+        "name": "Leto Attreides"
+    },
+    {
+        "id": "MUADDIB",
+        "fatherId": "LETOTHEJUST",
+        "name": "Paul Attreides"
+    },
+    {
+        "id": "STALIAOFTHEKNIFE",
+        "fatherId": "LETOTHEJUST",
+        "name": "Alia Attreides"
+    }
+]
+
+

Of course in an ordinary family tree there is more than one parent, I’ll skip over that.

+

Owing to React's nature Recursive Components are a good use case for the language and implementations that I’ve seen in Vue and Angular aren’t quite as comprehensive.

+

Links

+ + +
+ + + + + + \ No newline at end of file diff --git a/posts/treact-recursive-components-in-react/master-screenshot.png b/posts/treact-recursive-components-in-react/master-screenshot.png new file mode 100644 index 0000000..965d57e Binary files /dev/null and b/posts/treact-recursive-components-in-react/master-screenshot.png differ diff --git a/posts/web-components-and-slotted-element-interactions/employboy-demo.html b/posts/web-components-and-slotted-element-interactions/employboy-demo.html new file mode 100644 index 0000000..0932f8a --- /dev/null +++ b/posts/web-components-and-slotted-element-interactions/employboy-demo.html @@ -0,0 +1,52 @@ + + + Web-components and slotted element interactions | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/posts/web-components-and-slotted-element-interactions/index.html b/posts/web-components-and-slotted-element-interactions/index.html new file mode 100644 index 0000000..615e984 --- /dev/null +++ b/posts/web-components-and-slotted-element-interactions/index.html @@ -0,0 +1,246 @@ + + + + Web-components and slotted element interactions | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Web-components and slotted element interactions

+ +

Back in 2010 one of my first websites Employboy featured a parallax effect applied to a floating head. It used JS to explicitly set the position of the inner and outer shapes. Here I'm going to do a run-through of how I modernised this approach using web-components and SVGs.

+

First of all let's take a peek at what we're working towards, please welcome into the public domain our little friend "Mockey". Moving your mouse cursor over around the image should produce a parallax effect.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Open ↗

+

We're going to take a top-down look at how this effect has been produced, firstly let's introduce an SVG to the page.

+
+ + + +
+

The SVG is included directly in the markdown:

+
<svg viewBox="0 0 520 390" style="background:#fdfad2">
+    <circle id="outer" cx="260" cy="195" r="145" style="fill:#6bb46e;"/>
+    <circle id="inner" cx="260" cy="195" r="96.667" style="fill:#ffcc03;"/>
+    <circle id="center" cx="260" cy="195" r="48.333" style="fill:#fffdea;"/>
+</svg>
+
+

The three circles have ids of #outer, #inner and #center. By targeting those ids we can make the svg interactive with the use of a web-component name lib-mouse-parallax.

+

+ + + + + +

+

I see why Nurofen uses this as branding, it's headache inducing! Just take Ibuprofen, it's identical.

+

So how does our web-component make the SVG behave in this way, let's take a look at the mark-up.

+
<lib-mouse-parallax
+    speed="1"
+    foreground="#center"
+    middleground="#inner"
+    background="#outer">
+    <svg viewBox="0 0 520 390" style="background:#fdfad2">
+        <circle id="outer" cx="260" cy="195" r="145" style="fill:#C7574D;" />
+        <circle id="inner" cx="260" cy="195" r="96.667" style="fill:#ffcc03;" />
+        <circle id="center" cx="260" cy="195" r="48.333" style="fill:#fffdea;" />
+    </svg>
+</lib-mouse-parallax>
+
+

As mentioned our three properties "foreground", "middleground" and "background" are just css selectors. The circles could equally be targeted with nth-child(n):

+
foreground="circle:nth-child(3)"
+middleground="circle:nth-child(2)"
+background="circle:nth-child(1)"
+
+

Lastly, the component accepts a speed setting to determine how fast the foreground (top layer) travels in relation to the mouse. Here we have it set to 1 meaning the centre circle will track the mouse exactly.

+

The component acts in three stages.

+
    +
  1. Applies a css transform to svg elements matching a selector
  2. +
  3. On mousemove update the --mouse-x and --mouse-y variables
  4. +
  5. On mouseleave reset the position of the image
  6. +
+

Similar instruction occurs for touch events.

+

It's important to know that whilst web-components with a shadow-dom do encapsulate style there are some inherited properties. I believe the complete list is:

+ +

As well as these, css variables (--var-name: value;) are also inherited and that's something we'll be harnessing.

+

To move our shapes around we are using the css translate(x, y) function.

+
SELECTOR {
+    transform: translate(
+      calc(1px * var(--mouse-x, 0) * var(--speed)),
+      calc(1px * var(--mouse-y, 0) * var(--speed))
+    );
+}
+
+

Multiplying by 1px ensures our translation is applied in pixels. In the CSS we have included the value three times. Multiplying by 0.25px, 0.5px and 1px representing background, middleground and foreground respectively.

+

As we don't know what the css SELECTOR will be, we instead set the transform as a variable and apply it in JS later on.

+
:host {
+--set-foreground: translate(
+    calc(1px * var(--mouse-x, 0) * var(--speed)),
+    calc(1px * var(--mouse-y, 0) * var(--speed))
+);
+/* other properties... */
+}
+
+
element.style.setProperty("transform", "var(--set-foreground)");
+
+

All that's left now is to set --mouse-x and --mouse-y whenever mousemove fires within the component. We will also need to reset the values on mouseleave to restore the image to it's previous state.

+
svg.addEventListener("mousemove", this._handleMove);
+
+svg.addEventListener("mouseleave", this._resetPosition);
+
+

Things get a little more complicated from here but this is really where the walk-through stops. In brief the complicated elements are:

+

Transform Correction Because we're moving between absolute values (the position of our mouse) and relative values (the viewport of our SVG) we need to correct for how much we adjust our --mouse-x. Without this adjustment the parallax effect would vary when the size of the svg changes. Adjusting for the SVG's viewport corrects this.

+

Easing To avoid the position of elements snapping as when triggered, we are gradually easing in the effect on each movement.

+

The web-component itself was rolled in Google's Lit, and developed using Storybook as an environment. Lit is a joy to work with as I imagine are many of the libraries available for developing web-components.

+

What I like about Lit is it's goal to align the api with that of native web-components. Oftentimes I found the documentation I needed on MDN rather than in the Lit docs. I'm a real advocate of using the platform, developing in Lit doesn't feel like bending to someone else's model (eg "Thinking in React"). I'm yet to try out other libraries, though many seem to share the approach of class based declarations with decorators for added functionality. I imagine porting from one to another would be relatively pain-free.

+

Summary

+

In dedication to the Rob of 2010, here's that the floating head of employboy marked up by our web-component.

+

+ + + + + + + + + + + + +Open ↗

+

Web-components are yet to take off in the way that I'd hoped. Whilst big orgs are using them, frameworks and libraries like React and Angular already determine how components are created and creating native web-components in these environments has little value.

+

It appears libraries like Lit are yet convince devs that it offers a complete solution for developing a site. Whether that's down to a lack of awareness or simply because other frameworks are so firmly ensconced I couldn't say. I hope we see a shift soon.

+

Links

+ + +
+ + + + + + \ No newline at end of file diff --git a/posts/web-components-and-slotted-element-interactions/mockey-demo.html b/posts/web-components-and-slotted-element-interactions/mockey-demo.html new file mode 100644 index 0000000..f9daed7 --- /dev/null +++ b/posts/web-components-and-slotted-element-interactions/mockey-demo.html @@ -0,0 +1,68 @@ + + + Web-components and slotted element interactions | robstarbuck.uk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/posts/web-components-and-slotted-element-interactions/mockey.svg b/posts/web-components-and-slotted-element-interactions/mockey.svg new file mode 100644 index 0000000..2423a80 --- /dev/null +++ b/posts/web-components-and-slotted-element-interactions/mockey.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/back-button.js b/scripts/back-button.js new file mode 100644 index 0000000..b8734e4 --- /dev/null +++ b/scripts/back-button.js @@ -0,0 +1 @@ +var t=document.body;if(t&&document.documentElement.classList.contains("breakout")&&document.referrer&&window.location.href.includes(document.referrer)){let e=document.createElement("button");e.textContent="\u2199 Back",e.classList.add("breakout-back"),e.addEventListener("click",()=>history.back()),t.prepend(e)} diff --git a/vertical-rhythm.css b/vertical-rhythm.css new file mode 100644 index 0000000..d34b4d6 --- /dev/null +++ b/vertical-rhythm.css @@ -0,0 +1,45 @@ +:root { + /* Vertical Rhythm */ + --r: 20px; + /* One Quarter */ + --rq: calc(var(--r) * 0.25); + /* One Half */ + --rh: calc(var(--r) * 0.5); + + --rh1: calc(var(--r) * 0.5); + --rh2: calc(var(--r) * 1); + --rh3: calc(var(--r) * 1.5); + --rh4: calc(var(--r) * 2); + --rh5: calc(var(--r) * 2.5); + --rh6: calc(var(--r) * 3); + --rh7: calc(var(--r) * 3.5); + --rh8: calc(var(--r) * 4); + --rh9: calc(var(--r) * 4.5); +} + +* { + font-size: calc( + 0.655 * + var(--r) * + var(--r-count, 1) + ); + line-height: calc( + var(--r) * + var(--r-count, 1) * + var(--r-factor, 1) + ); +} + +h1 { + --r-count: 3; + --r-factor: 0.75; +} + +h2 { + --r-count: 2; + --r-factor: 0.75; +} + +h3 { + --r-count: 1; +} \ No newline at end of file