Starting from the start, as most things do, my idea is to have a portfolio style website.
How do we do this though, without really knowing what it takes to make a website? I've seen a lot of frameworks that all claim to make website building easy, and that's probably true if you put in a thousand hours to learn their ecosystem. But who has that sort of time?
too much for one man to handle
Without even really scratching the surface, that sure is a lot of stuff to evaluate and memorize.
The obvious question then is, of course, which one to choose? They all have their benefits and problems, and trying each one individually would take to much time.
What if we just choose the most popular one? yeah that could work, but how do we know what the most popular one is? This needs more investigation.
I could use php, as I've already got an xampp stack installed, but the general security of php without a framework seems abysmal. I could use a framework, but learning that on top of all the other stuff would take too much time. Php does make it pretty easy to get something up and running though, I have used it a little bit for one off scripts in the past.
Anyway, after a whole bunch of reading I've come to the conclusion that the best way to make a site is with plain html, css and javascript.
it's node.js!
I'll probably use node.js not only because it's already installed, but because it seems to be the biggest and has been around the longest. It also has npm, which seems like a nice tool for getting and maintaining packages.
I want the website to be easy to add stuff to, and to have a simple design that's easy to navigate. The page also has to load fast and be responsive.
I'll organise the site into three main sections: a gallery of all the good looking things I make, a section detailing the nitty-gritty of how I made the things look good, and this blog. I'll also put in a contact page, because every site needs a contact page.
This leads directly to the first problem: how wide should a webpage be? Well, a resposive website doesn't have any fixed width, and it's possible that people will be looking at it on potentially any device with a screen, but there's a limit to how long a line of text can be before it becomes unreadable.
For images though, the bigger the better, so I'll let their maximum width be the whole screen, so I'll think 960px as the maximum width will be pretty nice.
I'll of course need a common header and footer that contain links to all the other pages, but they'll need to be collapsible into a easy menu for mobile screens.
yep, looks like a website to me
This is my first concept for the site. Not my best work, but it gets the point across. Pretty sure this will do the trick, now to make the html and css look like that. Somehow.
start from the top down, the logo is the beginning i guess
want images to span the whole page width it's so obvious that margin: auto; makes the page content centered. so obvious. other css layout info seems okay, css reference is pretty good. w3 schools also has good info. positioning is tedious though
google webfonts seems okay? it might have load time implications though. not to worry, the whole font for the page that i like is 18.3kb and will be used for all the text, this seems okay. making the logo for a start, which font size to use? px? em? vw? guess i'll use pixels. this seems to scale with view width nicely too.
alright so after fudging with the font for a while,
after fucking about i saw that user agents stylesheet was being used for some reason, and after googling it seems that it does that for no particular reason? oh normalize.css seems to be a useful thing, found it after googling for the user agent thingy.
after spending too much time trying to do the logo with css, I think it's easier and faster to just make one in photoshop and scale it.
so that's what I did:
it's okay, but could be a lot better
as you can see, it doesn't look good. -resons heree- the one i got up top left is bestteer.
ok so after a while trying to make a site with just html and css from scratch on my own, I have found that it will take an unreasonable amount of time to make what I wanna make. I need another solution.
so graceful, so elegant~
so i found that w3 has some templates that use their own w3.css thingy, and in their plethora of templates I found one that looks like a good start for my desired layout.
okey doke so an immediate problem for trying to integrate the way I'm writing this blog is that w3.css uses multiple css classes to style it's elements, which is handy in one way because I don't have to worry about writing the css myself, but is difficult because I need multiple classes attached to one in order to style things how I want.
oh i probably need to mention that my initial idea for the blog portion of the site is to write it in markdown, because that's easiest and just transform it into html for upload. luckily i found a npm package Marked that doesn't have any dependencies and outputs a nice html file. it can conveniently also be used as an api, so i can probably automate all the content publishing in the future.
also for the record i am aware of the thousands of "make a blog in 5 minutes" tutorials, but eh, i wanna do it myself.
a solution to this need for inheritance has presented itself in the form of Less, a css preprocessor that has inheritance as a feature. The only problem is that this is another thing that I need to learn to some degree to integrate it into my pipeline. so much for simplicity I guess.
another thing i wanted entirely because i thought it looked cool, is the floating points behind the login screen on netlify. by using the handy-dandy chrome dev tools and clicking on the background, i found that they're using node garden, which is nicely under the MIT license. so I took a copy of the script file and customised it for a while to make it look like it does now.
The node garden
I also removed the mouse interaction from it, which was harder to figure out than I thought it would be, although it was solved simply by adding style="pointer-events:none;" to the canvas element. and it works! but now it's in the wrong position when the browser window is wider than the page, another straightforward fix though, just change it to position:absolute; and done. now, it doesn't display when the screen is too small, it's supposed to be hidden until the menu button is clicked, but it doesn't display at all. hmm, what to do.
so a good while later, it seems that there's a problem with the hidden menu when the screen is not full width. I don't really know how to fix this, and all my attempts so far have failed. it looks fine without it, even though they still technically load, so i'm just going to leave it for now and come back when I'm more experienced/less frustrated with it.
A problem with the garden
the problem, specifically, is that it appears that my browser (chrome) is adding height:0 and width:0 attributes to the canvas and the div surrounding it, but only when the top nav element is hidden by default. my attempted fix for this was to set the height and width and also the style's height and width when the open menu button is pressed. this doens't work and i don't know why, it seems there's a race condition between when the height and width are set and when the function is called.
so back to the matter at hand, what do i want as the main content attractor of the page? well everyone loves a picture, they tell a thousand words so i've heard.
so if i have a 4x4 grid, it looks fine, but if the window shrinks then it collapses into 2 colums, and eventually into 1 column on a very thin screen. this is fine, but with 16 images it's too long to scroll through reasonably. the solution i think is to paginate them into 2x8 images then 4x4 images, just got to work out the css to do that. how hard could it possibly be.
they look nice on small screens now!
I've become distracted by a bunch of stuff, and will return to pagination after some other stuff. perhaps worth noting, while i don't have a bunch of images to put up, i'm using place kittens for placeholders.
it occured to me that in regards to the nice floating node garden i have behind the nav menu, there's no reason for it not to just cover the whole page. so im going to move it up to the top of the body and let it be an overlay with a z-index below everything else, and stop it from being scrolled offscreen. this is so much simpler than trying to mess with the other css.
so here some time has passed and I've decided I don't like the look of the garden. it's just a bit too messy for my tastes, which is the point of the thing, a random garden of nodes that float around, meaning to imply a connected system or something like that. It's just not what I want to convey on the page. the page also feels a bit cold and lifeless because there's no color anywhere. also somewhere i made the images greyscale until hovered which doesn't help.
there's just no color at all
so i decided to remove it and replace it with a subtle but colorful gradient on the navbar, which has opened another can of worms. ugh, css will be the death of me. an animated gradient seems pretty easy right? well, haha my friend, you've fallen directly into a perfectly laid trap! the way to add a gradient to an element's background is using "background-image: linear-gradient()" but looking at w3 schools' description, and by trial and error, we find that this property simply cannot be animated. why? because reasons that are surely very sensible. so what is a young lad to do? according to the article I found on medium we could use javascript (no), an svg (what), or just some other css. but what it suggests is to transition the opacity of an overlaid element. that works, but it limits us to two colours.
the solution I worked out? it's easy enough, given we're allowed to have some transparency in our linear gradient. we have the transparent gradient on top of an element with an animated background-color, it's so simple I could kiss myself. but aha, a problem that hit me out of left field is that css properties marked as !important cannot be animated. why? surely more good reasons. so I've just got to go through and remove the bits that mark the color as important, easy enough. also got to remember to make the overlay's height and width the same as the rotating colours' so it all fits right. the fun cannot be halted.
so onto the simple job of making the images tabbed when the sceeen is thin. A little bit of css and javascript should do the trick, the buttons are already in place thanks to the template and hidden by some css media queries i wrote earlier. the javascript was easy enough to write, but accounting for each case and screen width to hide/show the images was a bit annoying. there's probably a more effective way than brute force, but at least it's fast and the code is readable. one thing i discovered is that vanilla Javascript doesn't have a built in math.clamp function, which seems odd. there's still a bit more to do with the images, but I'll get to them when I have proper non-placeholder images to put there.
the text gets cut off when the image is opened in the modal
aha! i have solved the full size modal image problem! all i had to do was make the image part of the element max-height: 90vh! like all css it's just so intuitive! wow! it's not 100vh because it looks silly having the image touch the borders of the screen.
so the next thing to do besides writing some paragraphs about how great I am (I'll do that at some point between now and never), is to make the contact form functional. All it's gotta do is send an email to me when the the form is filled out and clicked. in my inital searching of the problem, it seems that everything recommends php for handling the form, which i could do but surely there's a javascript solution for sending an email?
after a bit of searching i've found nodemailer, a npm package with no dependencies. so far I haven't used any node/npm stuff for this project, and this seems like a fine place to start. installing and using it seems easy enough, all I've gotta do is work out how to get the form data into the script.
at some point i also made the background a slight off white.
so the default form submit button wants to navigate to another page when it gets clicked, as defined by the action part of the form tag. this isn't great, so i removed it. then by setting the target to "_self" i get it to not navigate, but instead it reloads the page. this isn't great either, so i found out that I can just make a hidden iframe and set the form's target to the frame, so it doesn't reload the page when the button is clicked, and the javascript function is called through the onclick attribute of the form, which I can then use to both hide the form and do stuff with the entered data.
now what i do with the data is pretty strightforward, just get it and email it away. but therein lies a problem. to send an email i need a webserver apparently. so looking into it, it seems that my email host can act as an smtp server which i think is what i need, and because im hosting the site on netlify I think I can use their 'functions' thingy, which lets me run a javascript function on their servers? and it seems to support node.js, which is convenient as heck and just what I want. probably, I'll come back with some answers.
so it took a fair while to make the email work properly, and i ran into a couple of issues along the way that weren't easy to debug, and were essentially educated guessing on my part.
first i had to work out how to get data from an html form into javascript, turns out html5 defines a FormData type which asks for an element, then just bundles the form bits into key value pairs. easy enough, but then i had to work out how to send the data out as an email. for this i wanted to email it to myself, but from what I read this can only be done server side.
elementary googling tells me that php is the way to do this, just set an action as the action of a form and bob's yer uncle. but as i wrote earlier I can't use php because of my netlify hosting provider, I've got to use javascript instead. my next stop was to look at using an ajax request with xhttp or something, that seemed no bueno. eventually I found the simplest solution, the javascript function fetch() which only wants a url and the data you want to send, easy peasy.
the email worked!
the next trick was receiving the data on netlify's end, which they describe as being a javascript file that exports a handler method. easy enough, and setting it to just log the incoming data i can see that it works. all i've got to do from here is import the nodemailer module i described earlier and use it. my first attempt resulted in this warning:
5:05:48 PM: (node:1) UnhandledPromiseRejectionWarning: Error: 140264722483072:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:827:
5:05:48 PM: (node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
5:05:48 PM: (node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
which to me doesn't make a lick of sense. my best guess is that I was returning the wrong thing from the function, but who knows. so it seems the problem was that netlify is expecting the callback function to be called to let it know that the function has been completed, so calling that as the nodemailer's SendMail function's promise callback thingy seems to have fixed it. it also complained about being an async/await function, so to google I headed once again to try and find a solution. I stumbled upon a nodemailer example which does exactly what I want, it must be my lucky day, so copying it almost word for word, it worked immediately.
I used netlify to hold the secret username and password for the email client to send the emails, but I wrote them apparently wrong which gave me this error:
5:43:46 PM: (node:1) UnhandledPromiseRejectionWarning: Error: Invalid login: 535 Authentication Failed
which was handily fixed by removing the quotes from the value part of the netlify environment variable. so now I've got it to the point where it successfully emails me and I get a notification on my phone, which I'm quite pleased with. there's just one problem, the email's body is unreadably by a human. the string appears to be base64 encoded, but I'm not sure whether it's the formdata or the fetch call encoding it, no problem, I can simply decode it before sending the email. a quick search tells me that atob() is a function for decoding base64 strings, but it gives me this error:
TypeError: t.body.atob is not a function
hmm, that's a strange one, guess I'll have to find another way to decode the string. further searching finds me the Buffer.from() method which takes a string and an encoding and returns a decoded buffer, and luckily the nodemailer can take a buffer as the email's text, so the problem is solved, the contact form is now fully functional.
next, while trying to think of stuff to properly write on the main page, I'll make the blog page, which I think will be algorithmically generated by a bit o' javascript. specifically, it'll have a list of the pages generated from their .md files and listed with maybe a small description, probably an image too. specifically I like the way casey muratori's blog looks. so using the page I've been working on as a template, I'll make the blog page.
setting up the template for the page was easy enough, all I had to do was strip all the content from the main page and make a little template for the blog post link, which I decided will be an image with some text on it so it's easy to click, and some tags so they can be found and sorted easily.
the blog template
so while this looks good, I also want to have the time I posted the post to be under the image inline with the tags, my initial thought was to put the post date and the tags in their own spans inside the p element, and set their text alignments separately. this didn't work, but I learned through a quick search that I only need a span on the tags part, and can have the main p element text-align:left; and set the tag span to float:right; and it just works. sometimes css is ok.
to make the tags clickable, it was really rather simple. All i had to do was write the tag inside an a element and set the href to javascript:void(0); to make it do nothing when clicked, but set the onclick to a function that simply writes the link's text in to the filter box and then calls the filter function. easy as pie.
another small problem I ran into while setting up the blog page, the go-to-top button that only appears when you scroll down wasn't appearing. the reason I discovered is that the script that sets the scrolling to a function wasn't getting called, because the line before it asks to get a button by it's id, but the button doesn't exist on the page, so the script errored and stopped execution. my solution was to add a button with the same id, but make it do nothing and hide it.
now to actually write the generator for the blogs, an easy enough proposition i suppose, all it's gotta do is call marked to transform the .md into some html and insert that into a template, then add an entry into the blog page on the site.
alrighty, so writing the code was quite straightforward, the challenge was finding a way to index all files in a particular subfolder and only grabbing th e.md and .html extensions, but I found a packaged called glob that does just this, but I couldn't figure out how to use multiple arguments with it, so I just manually separated them afterwards. the next trick was to only get the md files that don't have a corresponding html file, because that means they haven't been genrated yet, which i accomplished with a simple symmetric filter which looks like this:
let diff = htmlFiles
.filter(x => !mdFiles.includes(x))
.concat(mdFiles.filter(x => !htmlFiles.includes(x)));
it's probably wrong in a bunch of ways, but it seems to work for my purposes. the next thing to do was also straightforward, read in the md file, extract a bit of metadata from the top of it to generate an entry on the main blog index, read in and replace the body of the template file, and finally write out the data to a new html file. easy as cake. a note about the metadata, i decided to use the format: Title || Post Date || Tags, because if I need to update a post in the future the order of the posts on the main blog page won't change because the date is entered manually.
incidentally, i believe that I've run into a small version of what is known as 'callback hell', because the current count of callbacks in the blog generator is: 8. which seems like a problem, and there's almost certainly a better way, but I've been following what was written in the official docs so it's probably fine? it does what I want it to do, but it feels just a bit wrong, you know?
it's probably not a problem, probably, because it is a series of callbacks from the multiple readfiles and writefiles and foreaches that need to be done to loop through all the things, theoretically this will handle it even if I make multiple blog posts before running the generator and uplaoding them.
fix this shit up
problem with javascript function replace, gotta remember that replace doesnt do its thing inline, you gotta assign it. it theoretically handles multiple files, but doesnt at the moment, that'd introduce sorting problems, and it also empties the blog.html file when multiple md files need to be built, probably a race condition or something, would need to refactor the code a bunch, and its not really a problem now, just something to think about in the future. a lot of little things, like making sure the image links are writtent correctly, handling the multiple tags and having an off-by-1 error with the lenght, changing all the bits on the html page like title. also added in the ability to update the blog from the command line, just by adding a param, it was easy though, minimal changes needed to the code because of my thoughts that i'd do it eventually anyway, and it just became more convenient to do it now
so while trying to add a bit of style to the blog posts, i've discovered that css has no previous sibling selector. The reason I discovered this is because I want to remove the margin between the image and it's caption, but the image is wrapped in it's own p tag as a result of the markdown generator. my solution to this was to translate the caption using css TransformY(-10px); to move the text upwards a bit, it seems to work fine but might break on mobile.
to make the blog images have their own modals, all i did was copy across the modal block that's initially hidden from the front page to the blog template, then modify the text returned from the md to html conversion to include the onclick="openModal()" stuff to make it actually functional, however I found that javascript's replace function doesn't automatically replace all instances of the search string when using string.replace, it only handles the first instance, so again I had to search the web to find out that regex is needed to make it function properly. ugh.
the next immediate problem is that it wasn't getting the text for the modal's caption from the image's caption text, so I had to search and find how to navigate through the html DOM to find the image's parent's next sibling, because that's where the caption text is stored because of how the md to html conversion works. so to finally get to the image's caption it's: element.parentNode.nextElementSibling.innerText. luckily iterating through the process was easier thanks to the modifications I made to the blog making process I made earlier.
finally, after a while, I've fixed up most of the little problems that i've found, and made the front page images come from the proper folder instead of a temporary placeholder service, I've also made the text for the front page modals be handled in an easier way, their just html strings selected by a switch in the javascript, because it's the easiest way to keep them all in a reasonably sensible place. the last few things to do are to check up on website best practises to see if there's anything I haven't thought of, and to maybe put in some google webmaster tracking stuff so i can see if anybody's actually visiting my site.