How to create a personal website, but it’s 1999

Michal Koczkodon
19 min readJul 3, 2023

--

Have you ever wondered, what web developer life looked like over 20 years ago, when world wide web was still kinda new thing, you had to listen to strange beeps and boops before you entered it, JavaScript and CSS were more of a curiosity than something commonly used in creating simple websites, and the most popular browser was Internet Explorer? Well, probably you haven’t… but if you have couple of minutes to spare and you want to learn another useless thing — this article will not disappoint you!

Photo of excited tipical ’90s family waiting to learn how to build personal website.

Backstory

At the beginning of 2022 I fell into some nostalgic mood and really wanted to play one game from my childhood (The Prince and The Coward — it’s a polish point and click game and, yeah, I’m from Poland). I had original game CD, but I had no machine that could run it. I thought that it would be more fun to play it on old-school Windows PC, so I decided to go through auction/secondhand item websites/marketplaces yo buy some old, used hardware (and software — Windows 98) to build one for myself… anyway how I built it and what problems I had — let’s leave that for another story.

Windows PC with The Prince And The Coward game on screen, with Lego Head of Lord Vader and Internet For Dummies Book next to it.
My Windows 98 (and XP) Retro-PC (The Prince and The Coward game on the screen)

After completing the game, the nostalgic spirit faded away, leaving me with stuff that occupied space and gathered dust. I quickly came up with the idea to try with some web development on this PC, but there was always something better to do. So this idea spent some time aside until I finally made up my mind to write this article.

1999

I was 10 years old back then and had at least 3 years experience with the Microsoft Windows operating systems. I’m quite sure that my journey with web development started in early 2000s and I used Macromedia Dreamweaver (Adobe acquired Macromedia in 2005) for my first “projects” with no CSS, table-based layouts, and JS snippets copied from the internet to have some trendy effects like falling snow animation.

The internet itself was a mystery to me in 1999. I had probably heard about it and knew what it was all about, but I made my first contact in the 21st century.

Screenshot of Yahoo start page from 1999
yahoo.com in 1999 (source: web.archive.com)

What was happening in the world? European Union introduced new currency — Euro. Bill Clinton was still president of the USA. The World Meteorological Organization announced that the ’90s were the hottest (talking about average temperature) decade ever. SpongeBob SquarePants debuted on Nickelodeon. Eminem released The Slim Shady LP. Movies like The Matrix and Star Wars: Episode 1 — The Phantom Menace (I really loved this one back then, re-watched it recently and I think I still like it) had their world premieres.

Scene from Star Wars: Episode 1 — The Phantom Menace. Jar Jar Binks meet Qui-Gon Jinn and Obi-Wan Kenobi on Naboo.
May 1999 —Jar Jar Binks is about to make some Star Wars fans hate George Lucas for the rest of their life (source: Star Wars: Episode 1 — The Phantom Menace, directed by you know who)

Everyone was talking about the millennium bug, as the year 2000 was supposed to bring a global computer apocalypse due to date formatting issues. So, if you hadn’t done it already, 1999 was your last chance to create a personal website.

The Project

What we’re going to do? A simple personal website with three tabs — home (say hello to our visitors), about (short bio) and contact (some contact info). Layout shouldn’t be very complicated — header (with a title and tab navigation) at the top, footer at the bottom, and tab content area in between.

Simple drowning on sheet of paper showing how the page should look like.
Good design is essential

I would like to use as much CSS as possible, and the switching between tabs should be implemented using JavaScript. There will be no subpages — just one HTML index file, one JS file, one CSS file, and few (two) images. Here is the structure of my project tree:

project-root/
├── assets/
│ ├── scripts.js
│ ├── styles.css
| ├── bg.gif (used as background of the page)
| └── mk.jpg (my face for about section)
|
└── index.html

The page should work and look good on all (two) major browsers available at that time — MS Internet Explorer 5 and Netscape Navigator 4.51.

Tools

I could go classic and choose MS Notepad to write all code for my website. But let’s make it a bit more professional and get some real code editor. My first thought was Notepad++, but since it was released in 2003, it doesn’t exist in 1999. I had in my mind polish code editor called Pajączek (Spidey), available since 1996, but I think it was only in polish and not free. After a few minutes of searching on Google and Wikipedia, I found Arachnophiliareleased in 1996, it’s free, it’s in english, and its name is related to spiders. This will do.

Screenshot of Pajączek 2000 made on Windows 98.
Pajączek 2000 v4.8.1 by Cream Software (source: grafoteka.pl)

My Windows 98 came with preinstalled Internet Explorer (v5.0), but I had to get somewhere other apps — Netscape Navigator and Arachnophilia. Naturally, both had to be versions that were available 23 years ago. Looking for old software is not easy but usually oldversion.com is a good place to start. Unfortunately when I was trying to get there it was down for couple of days, and I was pretty sure it’s gone for good. However, as I type these words, it seems to be back online.

Nevertheless, I had to search elsewhere. It took me some time, cause phrase like “netscape 4.5 download” typed in google won’t give you satisfying results on the first page (it won’t get you any satisfying results at all), so I had to dig around. Eventually, the page archive.org gave me what I needed — apcmag.cd disk image from May 1999 which included both apps: Netscape Navigator v4.51 and Arachnophilia v3.9.

List of applications on apcmag.cd
List of programs on apcmag.cd, May 1999 (source: archive.org)

My initial ambitious plan involved using a local server and running my website on localhost (I even found an old article about installing Apache and PHP on Windows 98). However, for a project of this scale, it felt a bit overwhelming, so in the end I dropped that idea. Perhaps one day I will go with more advanced retro web-thing, that requires backend logic, but for now, let’s focus only on the frontend.

Arachnophilia

Let’s shortly talk about code editor of my choose. it may have been considered quite good back then, but now it feels a bit cheap. Still it’s more than regular MS Notepad can offer — it has some basic HTML syntax highlighting and preview feature (click on a button and Arachnophilia saves the current HTML code in a temporary file and opens it in IE for preview; it claims to reload the page after every save, but unfortunately, that feature didn’t work for me).

Screen shot of Arachnophilia App with Tip of The Day saying that running with scissors can be dangerous.
Arachnophilia gives some Tips about life

It doesn’t support JS and CSS files, but you can create them as txt files with .js or .css extension. Then just write code there.

When you choose to create a new HTML file, a prompt window will appear, allowing you to set the page title, text and link colors. Based on your input, Arachnophilia will generate some initial HTML code for you, with the title set in the head section and the text and link styles added as attributes to the body tag (I’m not going to keep it that way).

The initial HTML code has capitalized tag names, which I quite like as it gives off an old-school vibe, so I’ll try to follow it. Of course, there is no auto-indentation.

Screen shot o Arachnophilia app with initial HTML

Logic comes first

Before implementing the entire page, I want to explore what I can do with JavaScript.. My goal is to have JS-controlled tabs — when a navigation link is clicked, the associated tab should be displayed while others are hidden.

First I focused on Internet Explorer. There are no dev tools and no JS console — if something throws an error IE shows warning icon in the bottom bar, duble-click on it gives us more detailed info — error message and where it occurred.

Screenshot of Internet Explorer 5 showing alert about JavaScript error.
Internet Explorer JS error (notice the warning icon in bottom-left corner)

Not what I’m used to, but still quite helpful. When I had some problem I usually was able to find solution when googling it (results pointing to old threads on forgotten forums) or I used caniuse.com to figure out what methods are available (it lists only IE v6 and higher, but from my experience, when something was “green” for 6–8, then it worked on IE v5 as well).

Screenshot of caniuse.com page with information about support for getElementsByName JavaScript method in browsers.

Here are couple of things, that I figured out during PoC JavaScript development for Internet Explorer 5:

  • getElementsByClassNameis not supported, but getElementsByNameworks fine (so I used it to select all navigation links)…
  • … but not with DIVs (so I couldn’t use it to select tab contents),
  • addEventListener doesn’t exist, but IE has own method for that — attachEvent— it takes at least two arguments — eventName (but with on at the beginning, as example: onclick instead of click) and callback (which doesn’t receive the event object as an argument),
  • anchorElement.getAttribute('href') returns the entire URL path instead of just the value added in the href attribute (so if the attribute has assigned the value #foobar, you will get locally something like C:\\…\#foobar)

That’s my HTML to test my tabs concept:

<A href="#tab0" tab="tab0" name="link">Link 1</a>
<A href="#tab1" tab="tab1" name="link">Link 2</a>
<A href="#tab2" tab="tab2" name="link">Link 3</a>
<DIV id="tab0">Content 1</div>
<DIV id="tab1" style="display: none;">Content 2</div>
<DIV id="tab2" style="display: none;">Content 3</div>

I kept href attributes to make it more semantic, but needed custom attribute tab to make it easier to get tab ID with JS.

And here’s my JavaScript code to control switching between tabs:

var tabLinks = document.getElementsByName('link');
var currentOpenTabElement = document.getElementById('tab0');

for (var i = 0; i < tabLinks.length; i++) {
tabLinks[i].attachEvent('onclick', createOnClickHandler(i))
}

function createOnClickHandler(tabLinkIndex) {
var tabLink = tabLinks[tabLinkIndex];
var tabId = tabLink.getAttribute('tab');

return function() {
openTab(tabId);
}
}

function openTab(tabId) {
var tab = document.getElementById(tabId);
currentOpenTabElement.style.display = 'none';
tab.style.display = ''
currentOpenTabElement = tab;
}

And I need to admit — I’m surprised. Code looks quite good and it does what I wanted (switches between tabs). It’s 1999, it’s Internet Explorer and it works. I’m really surprised.

Netscape Navigator — first impact

If my code works on Internet Explorer, what could possibly go wrong on Netscape browser? — I asked myself. In the ’90s it was even the most popular browser, and while it eventually lost to IE, I was sure I have nothing to worry about. So I opened my page in Netscape Navigator and…

…tabs control doesn’t work.

My first bet was that attachEvent is just IE thing, so it won’t work in Netscape. But how can I know? Where Netscape prints out JS error messages? Is there any JS console? No. Is there some info in the bottom bar like in IE? No. Is there any indication, that something went wrong when executing JS code? Well… it doesn’t work so it’s kind of feedback, but no, there isn’t.

So how to debug JavaScript in Netscape Navigator 4.51? Well, you need to make some error handling by your own — add an window.onerror handler that will call alert method with error message (I didn’t came up with this by my own, found it here).

Screenshot of Netscape Navigator showing alert window with information about JavaScript error.
Netscape Navigator error handling (custom)

Ok, so getElementsByName didn’t exist in Netscape world at the time. What about getElementsByClassName? No. Maybe getElementById at least? No. Is Netscape Navigator all about no’s? No, but for sure it gives you a lot of them.

You won’t find any info about support on caniuse.com, you’re limited to what’s left on the internet. And, fortunately, there is one resource that can be useful here — Client-Side JavaScript reference (version 1.3) by Netscape Communications Corporation, published in 1999. That gave me some idea of what I could use, and, oh boy, the Netscape version of JavaScript feels like a completely different story. There are collections like document.ids or document.classes but we can’t do much with them — usually, we can only set some basic styles, but only once when the browser loads the page.

My idea was to use the onClick attribute (as it’s a more cross-browser solution) to attach click handlers responsible for setting display style property for tab elements. But I couldn’t make it work. Once again, I had to turn to Uncle Google and hope for some luck. Found few old articles, but most of them pointed me nowhere. Finally I got this one that saved my day. Netscape introduced own layer HTML tag, that was intended to position and animate (through scripting) elements in a page.

The only problem with the <layer> tag is that it behaves like an element with CSS absolute positioning (relative to the parent layer or window). Luckily, there is also the <ilayer> tag, which is just an inline layer. That's more useful, but there is still one minor issue - the visibility attribute for layers works like the CSS visibility property, which means you can hide the element but it still takes up space. As a result, the first tab will be shown where I want it to be when visible, but the other ones will appear below, which would look bad. To fix this, I need to use a negative top property value to move them into the correct position. Phew...

So, how does the cross-browser version look? First, I had to add some inline script in the document’s <head> to determine if the browser is Netscape:

<SCRIPT>
var probablyNetscape = !!document.layers && !!document.classes && !!document.ids;
</SCRIPT>

Why didn’t I use the navigator property to check which browser is being used to visit the page? navigator.appName returns Netscape, fine, but same as Chrome in 2023 and navigator.userAgent gives me back Mozilla v4.51 — that’s a bit more specific, but not entirely reliable. Checking if document has properties like layers, which are totally Netscape things, provides me with more confidence.

Anyway — I’ll use the info that it’s probablyNetscape in my scripts.js file and directly in the HTML. Speaking of HTML, here it is:

<A href="#tab0" onClick="tabLinkClickHandler('tab0')">Link 1</A>
<A href="#tab1" onClick="tabLinkClickHandler('tab1')">Link 2</A>
<A href="#tab2" onClick="tabLinkClickHandler('tab2')">Link 3</A>
<DIV>
<ILAYER name="tab0" style="display: block;">
<DIV id="tab0">Content 1</DIV>
</ILAYER>
<ILAYER name="tab1" visibility="hide" style="display: block;">
<DIV id="tab1">Content 2</DIV>
</ILAYER>
<SCRIPT>if (!probablyNetscape) {document.getElementById('tab1').style.display = 'none';}</SCRIPT>
<ILAYER name="tab2" visibility="hide" style="display: block;">
<DIV id="tab2">Content 3</DIV>
</ILAYER>
<SCRIPT>if (!probablyNetscape) {document.getElementById('tab2').style.display = 'none';}</SCRIPT>
</DIV>

As you can see, I used some inline script to add the display: none style to tabs that should be initially hidden if the browser is not Netscape. I did this because the possibility to modify the display property with JS in Netscape Navigator doesn't work as you would expect (so I couldn't remove it), and I wanted to hide those tabs as soon as possible (before scripts.js loads).

The <ILAYER> properties, such as visibility and top, will only be understood by Netscape. Other browsers will ignore them (well, IE 5 ignores them, so I hope that future browsers will do the same).

The links for tab navigation have onClick attributes with click handlers assigned, which are defined in my scripts.js file:

var activeTab = 'tab0';

if (probablyNetscape) {
window.onerror = function(message, file, line) {
alert('JavaScript error!\nFile: ' + file + '\nLine: ' + line + '\nMessage: ' + message);
}

// Set position for layers in Netscape Navigator
document.layers.tab1.top = -38;
document.layers.tab2.top = -76;
}

function tabLinkClickHandler(tab) {
if (tab === activeTab) return;

if (probablyNetscape) {
document.layers[activeTab].visibility = 'hide'
document.layers[tab].visibility = 'show'
} else {
document.getElementById(activeTab).style.display = 'none';
document.getElementById(tab).style.display = 'block';
}

activeTab = tab;
}

And I know — if someone clicks a link before script file loads, it will cause an error, so at this point I could just put all my script code into index.html, but… I just want it to be like that.

Now, I have a working cross-browser proof of concept. That experience was — thanks to Netscape Navigator — a bit frustrating. But now it’s time for relaxing part — layout and css styling!

Good look with that

I learned my lesson — it doesn’t make sense to develop something specifically for IE5 and then try to adjust it for Netscape Navigator, as I would end up with completely different code. From the beginning I need to look for solutions that will work on both browsers.

I had some idea of HTML structure and CSS styles I would like to use, so to make it cross browser, I started doing things step by step. Now let me tell you a story about craziness of styling HTML layouts in 1999.

Centring elements with margin left and right set to auto doesn’t work. There are two possible solutions — <center> tag or <div> with attribute align=”center”. That element should wrap page content container (with width set to 480px). In both cases IE centers also content of descendants as well, so we can set attribute align to left on page container, that fix things IE. However Netscape moves the entire container back to left, so to make it right (I mean center, but with content on left), we need third container with align attribute set to left. Here’s some code, to make it easier to understand:

<DIV align="center">
<!-- this one has width set to 480px in styles.css -->
<DIV class="page-container">
<DIV align="left">...</DIV>
</DIV>
</DIV>

CSS flexbox (not to mention grid system) wasn’t around all the time. If you’re in web development more than ~10 years then most probably you remember that to make a grid layout developers used float property (even Bootstrap did). I believe it wasn’t intended to be used for that (originally), but developers gave it quite important place in history of styling website layouts.

Anyway, I used floats to align my tab controls (links), I wanted to use here list tags — ul and li — but looks like Netscape doesn’t like combination of float and list elements… Moreover, using float style directly on anchor tag will remove all default anchor styles (I’m talking about Netscape, on IE everything is fine). I’ve end up with DIVs-based list.

Screen shot from Windows 98 with Internet Explorer and Netscape Navigator showing difference in rendering list elements with CSS float property.
List elements with float: left; — Netscape doesn’t like this.

Background color on a DIV set with CSS is some serious s*** for Netscape Navigator v4.51. Without CSS border property set to none (or whatever, it just needs border style to fix itself), only text inside DIV will have desired background color (like some text highlight style).

Screenshot of Windows 98 with Internet Explorer and Netscape Navigator opened — shows problem with Netscape Navigator background rendering.
Background set in CSS — IE vs Netscape Navigator
Screenshot of Windows 98 with Internet Explorer and Netscape Navigator opened — shows fixed problem with background rendering on Netscape Navigator.
Netscape needs border property to properly apply background to the element.

Setting background-image gave me another headache. IE requires image (bg.gif) path to be relative to styles.css file (so url('bg.gif') is ok), but Netscape expects it to be relative to index.html file (url('assets/bg.gif')). The easiest solution here is to set background directly in body style attribute (with path relative to index.html, of course).

When I was working on the navigation, I wanted to change styles of link when associated tab is opened. Due to Netscape’s limitations I had to play with (i)layer tag and I got to the point where my HTML caused Netscape critical error.

...
<DIV class="tab-control">
<ILAYER name="start-link" class="tab-control-layer">
<DIV class="tab-control-inner">
<A href="#start" id="start-link">Start</A>
</DIV>
</ILAYER>
</DIV>
...

Well, maybe it also had something to do with CSS, but I haven’t checked that.

Screenshot of Netscape Navigator taken on Windows 98. Shows critical error caused by HTML syntax.
Netscape can’t take it anymore

Next problem — margins. Let’s make it more clear: margins on Netscape Navigator v4.51. You can set margin: 0 everywhere, but Netscape doesn’t give a… you know what. It just knows better. Margins must be. Of course there is a solution — use negative values. However that would affect other browsers (IE) too, and other browsers understand that ZERO is ZERO. NO MARGINS THEN. WHAT’S WRONG WITH YOU, NETSCAPE?

Screenshot of Windows 98 with Internet Explorer and Netscape Navigator opened — shows difference in appling margins.
Margins are very important for Netscape Navigator

At this point I knew I would never make Netscape version of my site as good as I want. And I need to use special styles for it. How to differentiate it? My first thought was to add additional class to body with JavaScript in case of non-Netscape browser (because I can’t set class names with Netscape version of JS), but it turned out that Netscape Navigator 4 is so broken, that it’s quite easy to set styles that will be ignored by it. Using html as descendant selector will do the job, cause Netscape doesn’t recognise it as valid one.

Working with CSS for my page realised me how many things are broken in Netscape Navigator 4, as example: you can use every selector only once (others will be ignored) and combined selectors are not welcome (no go for something like .class.with-other-class).

I wasn’t able to remove spaces between tab links that you can see on image above.

Of course Internet Explorer 5 is not perfect either, as it has some unexpected behaviours, but Netscape Navigator 4 is just pure craziness.

Speaking about IE — I wanted to implement simple animation of catchphrase (Welcome to 1999!) below page headline that slides from left to right continuously. I used setInterval for that purpose, and it turned out that passing anonymous function as callback will crash IE after few seconds.

// IE5 KILLER
setInterval(function() {...}, 100);
Screenshot of Internet Explorer taken on Windows 98. Shows critical error caused by JavaScript.
setInterval can cause Internet Explorer 5 crash

Defining function first and then passing it to setInterval makes things right.

// THAT'S FINE!
function animateCatchphrase() {...}

setInterval(animateCatchphrase, 100);

My bet is that IE has some problem with memory management in case of anonymous function. Netscape Navigator has no problem with that. Good job Netscape, 10 points to Gryffindor.

Finally

If I would want to describe all problems I had when working on my 1999 page project, I would have to write a book about it. I think I gave you already most interesting (frustrating) part of it. But believe me — there is more, like paragraphs styling — in this case I quickly gave up and used DIVs instead.

Anyway, after few hours spent on it my page was ready and working on both Internet Explorer 5 and Netscape Navigator 4.51.

Total size of all files required to run the page is ~42KB (html, css, js and two images — all uncompressed). I couldn’t find any reliable information about average speed of the Internet connection in 1999, so let’s calculate downloading time for telephone-based modem that in late ’90s was able to reach 56 kbit/s speed — with that kind of device (operating on full speed) it would take 6 seconds to get all data. In 1999 it was probably acceptable.

I’m not going to paste the whole code here (long and boring), you’ll find link to Github repository somewhere below. Now, let’s take a look on some screenshots of my page I took on Windows 98.

Internet Explorer 5

Screenshot of 1999 webpage with start tab open on Internet Explorer 5
Start tab on Internet Explorer
Screenshot of 1999 webpage with about tab open on Internet Explorer 5
About tab on Internet Explorer
Screenshot of 1999 webpage with contact tab open on Internet Explorer 5
Contact tab on Internet Explorer

I must say that I’m really proud of how it ended up on Internet Explorer. It looks and works as I wanted.

Netscape Navigator 4.51

Screenshot of 1999 webpage with start tab open on Netscape Navigator v4.51
Start tab on Netscape Navigator
Screenshot of 1999 webpage with about tab open on Netscape Navigator v4.51
About tab on Netscape Navigator
Screenshot of 1999 webpage with contact tab open on Netscape Navigator v4.51
Contact tab on Netscape Navigator

As you can see Netscape Navigator version has some flaws… the content section and tabs appear broken and despite spending a lot of time trying to make them look as close as possible to what I achieved on IE, I ultimately failed… Perhaps I would been able to do so if I would done everything on LAYERs, but that sounds like over-engineering.

Back to the future

Let’s back to 2023 and open my page in some modern browser, like Google Chrome.

Screenshot of 1999 webpage with start tab open on Chrome browser in 2023
1999 page opened in 2023 in Google Chrome

It looks… small, but apart from that everything looks and works good. There is one minor problem with size of the content box (it’s too wide, you can easily tell that if you look on the right-bottom corner of grey box). The page wrapper has a width of 480px, and the content box inside the page wrapper also has a width of 480px but also has 10px padding on each side. Back in 1999, this worked fine (although I believe it was a bug in both IE and Netscape). However, now the content box has a total width of 500px (sum of the width and padding on both sides) — which is a more expected behaviour. We can fix this with the following three lines of CSS code:

* {
box-sizing: border-box;
}

Retro browsers won’t understand it, so nothing should be broken there. No further changes are required (well, I need to add short info about privacy policy, because of the GDPR law we have in EU).

If you would like to check this page on your own, it’s published at 1999.mihau.co, and you can find the code in the Github repository.

Last words

This small, simple website took me much more time than I had initially assumed, and it’s not because I’m bad at task estimation (well, sometimes I am, but it wasn’t my fault this time). The possibilities of Internet Explorer 5, compared to modern browsers, are, of course, much, much smaller, but it felt like a world I knew. Netscape Navigator 4.51 was a completely different universe. It had very limited DOM manipulation and event handling capabilities, and CSS felt completely broken (you can find all the bugs described here).

Screenshot of part of the page richinstyle.com showing summary of Netscape Navigator 4 bugs.
Summary of Netscape 4 CSS bugs (source richinstyle.com)

But, stop. I’ve already complained about it a lot. I’m polish, so complaining is in my DNA, but enough is enough. Let’s try to understand some things here.

First of all, it was 1999. I believe that even in the early 2000s, table layouts and attributes for styling were still more popular than CSS. And the content itself was more important than the look. JavaScript? Relatively new and lacked standardization. Netscape came up with that, Microsoft implemented own version, with different APIs and more possibilities. And anyway Flash became more popular back then.

Secondly — 4.51 wasn’t a major release, Internet Explorer 5 was. I strongly believe that Netscape improved and fixed a lot of things in Navigator v5 (released in 2000). Well, I haven’t checked that, but maybe I’ll try one day.

Anyway, despite all the frustration caused by the broken Netscape browser, it was still a nice travel in time. Now I can go back to the future and live long and happy with all these JS frameworks and browsers that are updated more often than I take out the trash.

Thanks for your time! I hope you enjoyed the article. Don’t hesitate to give a clap or leave a comment!

You can follow me on medium, twitter or mastodon.

More from me:
The Game of Life in one tweet — a short story about condensing code to fit within 280 characters.
Do not waste your money on developer certifications — why you should think twice before you spend your money to get another dev certificate.

--

--

Michal Koczkodon

Web dev. Lives and codes somewhere in Poland. JavaScript enjoyer and craft beer lover.