Compare commits

...

10 Commits

10 changed files with 692 additions and 48 deletions

View File

@ -1,52 +1,33 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (7.0.1) activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
addressable (2.8.0) addressable (2.8.1)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0) colorator (1.1.0)
concurrent-ruby (1.1.9) concurrent-ruby (1.1.10)
em-websocket (0.5.3) em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0) http_parser.rb (~> 0)
eventmachine (1.2.7) eventmachine (1.2.7)
faraday (1.9.3) faraday (2.6.0)
faraday-em_http (~> 1.0) faraday-net_http (>= 2.0, < 3.1)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0) faraday-net_http (3.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
ffi (1.15.5) ffi (1.15.5)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
gemoji (3.0.1) gemoji (3.0.1)
html-pipeline (2.14.0) html-pipeline (2.14.2)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
http_parser.rb (0.8.0) http_parser.rb (0.8.0)
i18n (1.8.11) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (4.2.1) jekyll (4.2.2)
addressable (~> 2.4) addressable (~> 2.4)
colorator (~> 1.0) colorator (~> 1.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
@ -72,7 +53,7 @@ GEM
jekyll-include-cache (0.2.1) jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-paginate (1.1.0) jekyll-paginate (1.1.0)
jekyll-sass-converter (2.1.0) jekyll-sass-converter (2.2.0)
sassc (> 2.0.1, < 3.0) sassc (> 2.0.1, < 3.0)
jekyll-sitemap (1.4.0) jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
@ -82,7 +63,7 @@ GEM
gemoji (~> 3.0) gemoji (~> 3.0)
html-pipeline (~> 2.2) html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0) jekyll (>= 3.0, < 5.0)
kramdown (2.3.1) kramdown (2.4.0)
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
@ -93,7 +74,7 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0) mercenary (0.4.0)
mini_portile2 (2.7.1) mini_portile2 (2.8.0)
minimal-mistakes-jekyll (4.24.0) minimal-mistakes-jekyll (4.24.0)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-feed (~> 0.1) jekyll-feed (~> 0.1)
@ -101,33 +82,32 @@ GEM
jekyll-include-cache (~> 0.1) jekyll-include-cache (~> 0.1)
jekyll-paginate (~> 1.1) jekyll-paginate (~> 1.1)
jekyll-sitemap (~> 1.3) jekyll-sitemap (~> 1.3)
minitest (5.15.0) minitest (5.16.3)
multipart-post (2.1.1) nokogiri (1.13.8)
nokogiri (1.13.1) mini_portile2 (~> 2.8.0)
mini_portile2 (~> 2.7.0)
racc (~> 1.4) racc (~> 1.4)
octokit (4.22.0) octokit (4.25.1)
faraday (>= 0.9) faraday (>= 1, < 3)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.9)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.6) public_suffix (5.0.0)
racc (1.6.0) racc (1.6.0)
rb-fsevent (0.11.0) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.2.5) rexml (3.2.5)
rouge (3.27.0) rouge (3.30.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sassc (2.4.0) sassc (2.4.0)
ffi (~> 1.9) ffi (~> 1.9)
sawyer (0.8.2) sawyer (0.9.2)
addressable (>= 2.3.5) addressable (>= 2.3.5)
faraday (> 0.8, < 2.0) faraday (>= 0.17.3, < 3)
terminal-table (2.0.0) terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
tzinfo (2.0.4) tzinfo (2.0.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (1.8.0) unicode-display_width (1.8.0)
webrick (1.7.0) webrick (1.7.0)
@ -148,4 +128,4 @@ DEPENDENCIES
webrick (~> 1.7) webrick (~> 1.7)
BUNDLED WITH BUNDLED WITH
2.3.5 2.3.23

View File

@ -19,3 +19,5 @@ No more easy life of not working and old looking after the kids and badly lookin
Ahead are a ton of decisions to be made like where to live, how to be there for the kids, how things will split, who gets the [Thermomix]() and so on. Ahead are a ton of decisions to be made like where to live, how to be there for the kids, how things will split, who gets the [Thermomix]() and so on.
Why? Because I had grown too far from Bob. I ... Why? Because I had grown too far from Bob. I ...
unfinished

221
_drafts/cashregister.md Normal file
View File

@ -0,0 +1,221 @@
---
title: Cash Register Challenge on freeCodeCamp
tags: [webdev, javascript, freecodecamp]
category: programming
---
I've been (slowly) working through the JavaScript module on [freeCodeCamp](https://freecodecamp.org) for a while now, and have recently been doing the certificate challenges. The last of which is the "Cash Register" challenge where you are to write a function that takes a price, a payment amount and an array that contains the cash in the drawer.
I found a couple of things strange about the challenge
| Currency Unit | Amount |
| ------------------- | ------------------ |
| Penny | $0.01 (PENNY) |
| Nickel | $0.05 (NICKEL) |
| Dime | $0.1 (DIME) |
| Quarter | $0.25 (QUARTER) |
| Dollar | $1 (ONE) |
| Five Dollars | $5 (FIVE) |
| Ten Dollars | $10 (TEN) |
| Twenty Dollars | $20 (TWENTY) |
| One-hundred Dollars | $100 (ONE HUNDRED) |
Example of the cash in drawer array:
```javascript
[
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100]
]
```
Sample input to function,
```javascript
checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]])
```
and the expected return object
```javascript
{status: "OPEN", change: [["TWENTY", 60], ["TEN", 20], ["FIVE", 15], ["ONE", 1], ["QUARTER", 0.5], ["DIME", 0.2], ["PENNY", 0.04]]}
```
Why is this weird? Well it was drilled into me back in college (1991/1992), to NEVER use floating point values for currency. Its inpercise (especially in JavaScript) and this challenge just doubles down on it.
I'd much rather see that there are 101 PENNIES that 1.01 in PENNIES. I find it faster to say give 4 pennies as change than 0.04 pennies.
Final **solution**
```javascript
function checkCashRegister(price, cash, cid) {
/*
First step, adjust amounts into pennies.
Learnt long ago to never use floats
for currency.
*/
let adjustmentAmount = 100;
let adjustedPrice = price * adjustmentAmount;
let adjustedCashGiven = cash * adjustmentAmount;
// Reverse cid, need to make new array as using = is just a reference
let cashInDrawer = Array.from(cid);
cashInDrawer.reverse();
// total of all the cash in drawer
let totalCashInDrawer = 0;
// in the array the denomination comes in an array with two parts, name and value
const availableCashInDrawer = cashInDrawer.map((denomination) => {
const adjustedAmount = Math.round(denomination[1] * adjustmentAmount);
totalCashInDrawer += adjustedAmount;
return [denomination[0], adjustedAmount];
});
// console.log(
// "Total Cash in Drawer",
// totalCashInDrawer,
// " -- $",
// totalCashInDrawer / adjustmentAmount
// );
let currencyValues = {
"ONE HUNDRED": 10000,
TWENTY: 2000,
TEN: 1000,
FIVE: 500,
ONE: 100,
QUARTER: 25,
DIME: 10,
NICKEL: 5,
PENNY: 1,
};
// console.log(currencyValue);
// Now how much change is required?
const changeRequired = adjustedCashGiven - adjustedPrice;
// console.log(`Change Required ${changeRequired}`);
// Two options, either set up the default object as
// change["status"] = "INSUFFICIENT_FUNDS";
// change["change"] = [];
// which would remove two if checks below, OR leave it empty
// and be explicit in the code
let change = {};
// Simplest case first.
// If no change required
if (changeRequired == 0) {
change["status"] = "CLOSED";
change["change"] = cid;
// if the change required is more than the available cash in the drawer
} else if (changeRequired > totalCashInDrawer) {
change["status"] = "INSUFFICIENT_FUNDS";
change["change"] = [];
} else {
let workingChange = changeRequired;
let changeWithCash = {};
availableCashInDrawer.forEach((denomination) => {
// console.log(denomination);
while (true) {
const denominationName = denomination[0];
const denominationValue = denomination[1];
let currencyVal = currencyValues[denominationName];
if (
workingChange >= currencyValues[denominationName] &&
denominationValue >= currencyVal
) {
denomination[1] -= currencyVal;
workingChange -= currencyVal;
let val = currencyVal / adjustmentAmount;
if (changeWithCash[denominationName]) {
changeWithCash[denominationName] += val;
} else {
changeWithCash[denominationName] = val;
}
} else {
break;
}
}
});
// If we have calculated the change correctly, and there was exactly that
// amount in the drawer, return closed and the cid (required for challenge)
if (workingChange === 0 && changeRequired == totalCashInDrawer) {
change["status"] = "CLOSED";
change["change"] = cid;
// otherwise if we have calculated change correctly, open the drawer and show what amount
// to give.
} else if (workingChange === 0) {
change["status"] = "OPEN";
change["change"] = Object.entries(changeWithCash);
// Otherwise we have enough money in the cash drawer but not the right denominations to
// give change, so return insufficent funds.
} else {
change["status"] = "INSUFFICIENT_FUNDS";
change["change"] = [];
}
}
console.log(change);
return change;
}
checkCashRegister(19.5, 20, [
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100],
]); // {status: "OPEN", change: [["QUARTER", 0.5]]}
checkCashRegister(20, 20, [
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100],
]); //{ status: 'CLOSED', change: [ [ 'PENNY', 1.01 ], [ 'NICKEL', 2.05 ], [ 'DIME', 3.1 ], [ 'QUARTER', 4.25 ], [ 'ONE', 90 ], [ 'FIVE', 55 ], [ 'TEN', 20 ], [ 'TWENTY', 60 ], [ 'ONE HUNDRED', 100 ] ] }
checkCashRegister(19.5, 20, [
["PENNY", 0.01],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
]); // {status: "INSUFFICIENT_FUNDS", change: []}
checkCashRegister(19.5, 20, [
["PENNY", 0.5],
["NICKEL", 0],
["DIME", 0],
["QUARTER", 0],
["ONE", 0],
["FIVE", 0],
["TEN", 0],
["TWENTY", 0],
["ONE HUNDRED", 0],
]);
```

24
_drafts/cssproperties.md Normal file
View File

@ -0,0 +1,24 @@
---
title: CSS Properties, or I have a problem.
tags: [webdev, css]
category: programming
---
Frontend Mentor
Ive be rattling off some Frontend Mentor challenges recently. Aiming to finish all the newbie challenges before moving onto Junior and onwards.
One thing Ive noticed is that Ive really embraced CSS properties, probably too much so :)
In my last couple of challenges Ive had upwards of 50 properties. They cover typography, Colors, positioning, styling and padding / margins.
For instance in this [Product] challenge …
The theory being that it makes it easy to retheme or to switch up details for the desktop version compared to the mobile version without having to redo X, Y or Z classes.
To me it makes sense. The code stays the same, youre just tweaking the variables passed in.

193
_drafts/femdeploy.md Normal file
View File

@ -0,0 +1,193 @@
---
title: What happens when I finish a Frontend Mentor Challenge
tags: [webdev, site, frontendmentor]
category: [programming, webdev]
---
I've been doing challenges from [Frontend Mentor](https://frontendmentor.io) as a means to practice frontend web development. Specifically working with plain HTML, CSS and JavaScript.
Rather than just take the simple route of a Git repo per challenge, I put them all in a single repo that is pushed to two[^1] servers ([Github](https://github.com/tarasis/tarasis.github.io) and a [Gitea](https://git.tarasis.net/tarasis/tarasis.github.io) instance).
The repo is actually a website built with [11ty](https://www.11ty.dev) and [Nunjucks](https://mozilla.github.io/nunjucks/) for templating. The challenges, and other pages I build are in the **projects** directory. They are simply copied over to **www* during the build.
When I do a `git push all`, on my server it runs a script that does the 11ty build. On Github I use a **Github Action**[^2] which builds the site and then a separate action that deploys the page. Vercel watches the main branch on Github for updates and does a similar build action and deployment.
That is then deployed to three different places: [Github Pages](https://tarasis.github.io), [Vercel](https://tarasis.vercel.app), and my own site at [rmcg.dev](https://rmcg.dev).
## Digging a little deeper
The front page of the deployed site acts as a gateway to all of the challenges, for instance this is the newbie section for Frontend Mentor challenges:
![Screenshot of the newbie section of the my portfolio website](/assets/images/portfolio.png)
Each section is generated from a json file I created, where I have the service name, location of an svg file, alt text for it, the difficulty level, then there is an array of challenges in that difficulty level.
```json
"services": [
{
"name": "Frontend Mentor",
"svgSource": "/svgs/frontendmentor.svg",
"svgAltText": "Frontend Mentor Logo",
"description": "Collection of challenges I completed for Frontend Mentor",
"cssClass": "",
"difficulty": [
{
"title": "Junior",
"cssClass": "frontEndMentorChallenges",
"challenges": [
{
"title": "Advice Generator App",
"url": "/FrontendMentor/junior/advice-generator-app/",
"description": "",
"techUsed": [
"html5",
"css3",
"js"
],
"screenshotURL": "/FrontendMentor/junior/advice-generator-app/screenshots/mobile.png",
"screenshotAltText": "Advice Generator App"
}
]
},
```
When the 11ty build occurs, it takes two nunjucks snippets then first takes the service section and fills out the details:
{% raw %}
```liquid
{% for service in webprojects.services %}
<div class="projectsDiv {{service.cssClass}}">
<div class="alignedHeader">
<h2 class="projectsSection__title">{{service.name}}</h2>
{% if service.svgSource%}
<img src="{{service.svgSource}}" alt="{{service.svgAltText}}"/>
{% endif %}
</div>
{% if service.description%}
<p>{{service.description}}</p>
{% endif %}
{% for difficultyLevel in service.difficulty %}
{% if difficultyLevel.challenges | length %}
<div class="{{difficultyLevel.cssClass}}">
{% if difficultyLevel.title%}
<h3>{{difficultyLevel.title}} Challenges</h3>
{% endif %}
{% if difficultyLevel.description%}
<p>{{difficultyLevel.description}}</p>
{% endif %}
<div class="projects-grid">
{% for challenge in difficultyLevel.challenges %}
{% set projectContent = challenge %}
{% include "components/project.njk" %}
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
```
{% endraw %}
If you note the `div` with class `projects-grid`, this is where the second nunjucks snippet occurs. It loops through all of the challenges in the json array represented by `difficultyLevel.challenges`.
Basically I pass in the challenge to the snippet below, and use the contents to fill out the a card with a screenshot of the finished build, a short description, and shields for HTML/CSS and if used JavaScript. When I get to later challenges I'll add in 11ty, Vue or whatever. (I'll probably simplify the code too so it grabs the tech svg based on the name, rather than having if checks ... we'll see.)
{% raw %}
```liquid
{# {% set projectPrefix = project %} #}
{% if projectContent %}
<div class="project-card stacked">
<a href="{{projectContent.url}}">
<img loading="lazy" class="card__img" src="{{projectContent.screenshotURL}}"
alt="{{projectContent.screenshotAltText}}"/>
</a>
<div class="card__content">
<h3 class="card__title">
{{projectContent.title}}
</h3>
{% if projectContent.description | length %}
<p class="card__description">
{{projectContent.description}}
</p>
{% endif %}
<ul class="card__techUsed">
{% for tech in projectContent.techUsed %}
{% if tech === "html5" %}
<li>
<img src="/svgs/html5.svg" alt="HTML5"/>
</li>
{% endif %}
{% if tech === "css3" %}
<li>
<img src="/svgs/css3.svg" alt="CSS3"/>
</li>
{% endif %}
{% if tech === "js" %}
<li>
<img src="/svgs/javascript.svg" alt="JavaScript"/>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endif %}
```
{% endraw %}
The finished build is then copied and published.
## Improvements ...
There are improvements I will make at some point, specifically adding optimisation of the images I use. There is little point is downloading a 1440x800px image for the challenge preview when only a small portion of it is shown. I am aware that during the build process you can have 11ty generate more web friendly images at differing sizes.
## Final thoughts
First if you want to know more about whats going on, please do ask I'll do my best to answer. Otherwise I've found 11ty incredibly useful for this purpose, and easy to use. So much so that my intent was/is to move the blog from Jekyll to 11ty ... I just haven't gotten to it yet and because I like the Minimal Mistakes theme I use here.
Having a static site where I can quickly add a challenge to the front page without having to repeat html myself is just a such a relief. Especially as the file gets longer and longer :) At this point in time I've finished 17 challenges for Frontend Mentor with 75 ish more to go. There's 1 for Dev Challenges, and 5 for FreeCodeCamp.
**NOTE** ... There are other project directories that aren't listed on the front page. You can see the code in the projects directory on Github / my gitea instance, and live under [others](https://rmcg.dev/others/). They are things I've done based of Youtube videos or other videos.
[^1]: Because I like redundancy when I can, and I want control of my code.
[^2]: The build action I use ...
```
name: Build Eleventy
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [17.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies & build
run: |
npm ci
npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.7.3
with:
publish_dir: ./www
github_token: ${{ secrets.GITHUB_TOKEN }}
```

25
_drafts/filtering.md Normal file
View File

@ -0,0 +1,25 @@
---
title: Filtering
tags: [webdev, javascript]
category: programming
---
I was watching the video [How To Create A Search Bar In JavaScript](https://www.youtube.com/watch?v=TlP5WIxVirU) by [Web Dev Simplified](https://www.youtube.com/channel/UCFbNIlppjAuEX4znoulh0Cw) to learn how to do a search bar in JavaScript.
What I realised as I was coding along, was that the video was reallymore about filtering data than what I might think of as a search bar. Which is fine, and totally not wrong, but I do find some of Kyle's choices suspect / curious.
For instance he uses data attributes for selecting elements from the html, and runs `toLowerCase()` on strings every single time you type a character. The latter to my mind seems incredibly wasteful.
```javascript
searchInput.addEventListener("input", (event) => {
const value = event.target.value.toLowerCase();
users.forEach((user) => {
const isVisible =
user.name.toLowerCase().includes(value) ||
user.email.toLowerCase().includes(value);
user.element.classList.toggle("hide", !isVisible);
});
});
```

View File

@ -1,3 +1,5 @@
<!-- start custom footer snippets --> <!-- start custom footer snippets -->
<button id='scroll-to-top'></button> <button id='scroll-to-top'></button>
<a rel="me" href="https://social.tarasis.net/@tarasis">For Mastodon verification</a>
<!-- end custom footer snippets --> <!-- end custom footer snippets -->

View File

@ -12,6 +12,6 @@ I can be found many places online, mostly with the username **tarasis**, but als
# Site # Site
Site is built with Jekyll but I use the [Minimal Mistakes](https://mmistakes.github.io/minimal-mistakes/) theme. I've incorporated tweaks by [Jun](jun711.github.io) to add the [rainbow progress bar](https://jun711.github.io/web/add-scroll-progress-bar-to-a-website-to-indicate-read-progress/), and the [scroll to top](https://jun711.github.io/web/adding-scroll-to-top-button-to-a-website/) button. Site is built with Jekyll but I use the [Minimal Mistakes](https://mmistakes.github.io/minimal-mistakes/) theme. I've incorporated tweaks by [Jun](https://jun711.github.io) to add the [rainbow progress bar](https://jun711.github.io/web/add-scroll-progress-bar-to-a-website-to-indicate-read-progress/), and the [scroll to top](https://jun711.github.io/web/adding-scroll-to-top-button-to-a-website/) button.
There are custom tweaks to the CSS used in the Neon theme provided by Minimal Mistakes, both for general site appearance but also to the syntax highlighting. There are custom tweaks to the CSS used in the Neon theme provided by Minimal Mistakes, both for general site appearance but also to the syntax highlighting.

View File

@ -0,0 +1,197 @@
---
title: What happens when I finish a Frontend Mentor Challenge (or how I build and deploy an 11ty site)
tags: [webdev, site, frontendmentor]
category: [programming, webdev]
---
I've been doing challenges from [Frontend Mentor](https://frontendmentor.io) as a means to practice frontend web development. Specifically working with plain HTML, CSS and JavaScript.
Rather than just take the simple route of a Git repo per challenge, I put them all in a single repo that is pushed to two[^1] servers ([Github](https://github.com/tarasis/tarasis.github.io) and a [Gitea](https://git.tarasis.net/tarasis/tarasis.github.io) instance).
The repo is actually a website built with [11ty](https://www.11ty.dev) and [Nunjucks](https://mozilla.github.io/nunjucks/) for templating. The challenges, and other pages I build are in the **projects** directory. They are simply copied over to **www** during the build.
When I do a `git push all`, on my server it runs a script that does the 11ty build. On Github I use a **Github Action**[^2] which builds the site and then a separate action that deploys the page. Vercel watches the main branch on Github for updates and does a similar build action and deployment.
That is then deployed to three different places: [Github Pages](https://tarasis.github.io), [Vercel](https://tarasis.vercel.app), and my own site at [rmcg.dev](https://rmcg.dev).
## Digging a little deeper
The front page of the deployed site acts as a gateway to all of the challenges, for instance this is the newbie section for Frontend Mentor challenges:
![Screenshot of the newbie section of the my portfolio website](/assets/images/portfolio.png)
Each section is generated from a json file I created ([src/_data/webprojects.json](https://github.com/tarasis/tarasis.github.io/blob/main/src/_data/webprojects.json)), where I have the service name, location of an svg file, alt text for it, the difficulty level, then there is an array of challenges in that difficulty level.
```json
"services": [
{
"name": "Frontend Mentor",
"svgSource": "/svgs/frontendmentor.svg",
"svgAltText": "Frontend Mentor Logo",
"description": "Collection of challenges I completed for Frontend Mentor",
"cssClass": "",
"difficulty": [
{
"title": "Junior",
"cssClass": "frontEndMentorChallenges",
"challenges": [
{
"title": "Advice Generator App",
"url": "/FrontendMentor/junior/advice-generator-app/",
"description": "",
"techUsed": [
"html5",
"css3",
"js"
],
"screenshotURL": "/FrontendMentor/junior/advice-generator-app/screenshots/mobile.png",
"screenshotAltText": "Advice Generator App"
}
]
},
```
When the 11ty build occurs, it takes two nunjucks snippets then first takes the service section and fills out the details:
{% raw %}
```liquid
{% for service in webprojects.services %}
<div class="projectsDiv {{service.cssClass}}">
<div class="alignedHeader">
<h2 class="projectsSection__title">{{service.name}}</h2>
{% if service.svgSource%}
<img src="{{service.svgSource}}" alt="{{service.svgAltText}}"/>
{% endif %}
</div>
{% if service.description%}
<p>{{service.description}}</p>
{% endif %}
{% for difficultyLevel in service.difficulty %}
{% if difficultyLevel.challenges | length %}
<div class="{{difficultyLevel.cssClass}}">
{% if difficultyLevel.title%}
<h3>{{difficultyLevel.title}} Challenges</h3>
{% endif %}
{% if difficultyLevel.description%}
<p>{{difficultyLevel.description}}</p>
{% endif %}
<div class="projects-grid">
{% for challenge in difficultyLevel.challenges %}
{% set projectContent = challenge %}
{% include "components/project.njk" %}
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
```
{% endraw %}
If you note the `div` with class `projects-grid`, this is where the second nunjucks snippet occurs. It loops through all of the challenges in the json array represented by `difficultyLevel.challenges`.
Basically I pass in the challenge to the snippet below, and use the contents to fill out the a card with a screenshot of the finished build, a short description, and shields for HTML/CSS and if used JavaScript. When I get to later challenges I'll add in 11ty, Vue or whatever. (I'll probably simplify the code too so it grabs the tech svg based on the name, rather than having if checks ... we'll see.)
{% raw %}
```liquid
{# {% set projectPrefix = project %} #}
{% if projectContent %}
<div class="project-card stacked">
<a href="{{projectContent.url}}">
<img loading="lazy" class="card__img" src="{{projectContent.screenshotURL}}"
alt="{{projectContent.screenshotAltText}}"/>
</a>
<div class="card__content">
<h3 class="card__title">
{{projectContent.title}}
</h3>
{% if projectContent.description | length %}
<p class="card__description">
{{projectContent.description}}
</p>
{% endif %}
<ul class="card__techUsed">
{% for tech in projectContent.techUsed %}
{% if tech === "html5" %}
<li>
<img src="/svgs/html5.svg" alt="HTML5"/>
</li>
{% endif %}
{% if tech === "css3" %}
<li>
<img src="/svgs/css3.svg" alt="CSS3"/>
</li>
{% endif %}
{% if tech === "js" %}
<li>
<img src="/svgs/javascript.svg" alt="JavaScript"/>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endif %}
```
{% endraw %}
The finished build is then copied and published.
## Improvements ...
There are improvements I will make at some point, specifically adding optimisation of the images I use. There is little point is downloading a 1440x800px image for the challenge preview when only a small portion of it is shown. I am aware that during the build process you can have 11ty generate more web friendly images at differing sizes.
## Final thoughts
First if you want to know more about whats going on, please do ask I'll do my best to answer. Otherwise I've found 11ty incredibly useful for this purpose, and easy to use. So much so that my intent was/is to move the blog from Jekyll to 11ty ... I just haven't gotten to it yet and because I like the Minimal Mistakes theme I use here.
Having a static site where I can quickly add a challenge to the front page without having to repeat html myself is just a such a relief. Especially as the file gets longer and longer :) At this point in time I've finished 17 challenges for Frontend Mentor with 75 ish more to go. There's 1 for Dev Challenges, and 5 for FreeCodeCamp.
**NOTE** ... There are other project directories that aren't listed on the front page. You can see the code in the projects directory on Github / my gitea instance, and live under [others](https://rmcg.dev/others/). They are things I've done based of Youtube videos or other videos.
[^1]: Because I like redundancy when I can, and I want control of my code.
[^2]: The build action I use is just up above, because apparently I can't have anything below footnotes go figure 🤪 ...
### Github build action
My Github 11ty build action
```
name: Build Eleventy
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [17.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies & build
run: |
npm ci
npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.7.3
with:
publish_dir: ./www
github_token: ${{ secrets.GITHUB_TOKEN }}
```

BIN
assets/images/portfolio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB