To Err is Human
From a tattered office sign on a door to a CSS parody design—
and then, into the TNT ecosystem: navbar, hero, SPARK bar, and beyond.
This log has two stories. The first is older: a CSS design challenge to recreate a beloved dot-matrix printer sign using nothing but HTML, CSS, and a generous helping of creative stubbornness. The second is recent: a one-session upgrade that pulled that design into the TNT navigation ecosystem.
Watch for Prompt Critique boxes (amber), Design Decision boxes (teal), and For Students boxes (red). The most reusable lesson in this log is the base stylesheet + page-specific stylesheet pattern — the same structure used by every production design system.
The real sign (stored as images/ErrHumanSign.jpg) is a physical artifact:
green-bar dot-matrix paper printed with a now-classic office joke, mounted on a wood door that
has seen better decades. Three design elements were extracted from it:
- The paper — alternating teal and cream horizontal bands, each with perforated “punch holes” on the left and right edges.
- The text — three lines of Instrument Serif (closest approximation to
the original dot-matrix character shapes), color
#574C3B(warm brown, like aged ink). - The misalignment — individual letters shifted with
vertical-alignand rotated withtransform: rotate(), simulating a printer that’s slowly losing its mind.
position: relative + top / left for Pixel-NudgingThe three phrase paragraphs are placed on top of the paper design using
position: relative with negative top values:
p#phrase1 { position: relative; top: -358px; left: 130px; }
p#phrase2 { position: relative; top: -375px; left: 40px; }
p#phrase3 { position: relative; top: -417px; left: 140px; }
position: relative moves an element from where it would normally sit
in the document flow, without removing it from that flow (unlike absolute or
fixed). The element still occupies its original space; only its visual rendering shifts.
Negative top values move the element upward — here, up into the nine-band
paper design above it.
This is not always the most maintainable layout technique (pixel values break when the paper height changes) but it is the right tool when you need precise visual overlay of text onto a fixed-height decorative element. The sign’s paper is always 400px tall; the offsets are deliberate and stable.
display: inline-block as a Transform PrerequisiteThe misaligned letters in phrase 3 use transform: rotate() on
individual <span> elements:
<span style="vertical-align:-9px; letter-spacing:2px; transform:rotate(-10deg);">e</span>
The eureka: transform has no effect on inline elements.
The CSS specification states that transforms apply to block-level and
inline-block elements only. A <span> is inline by default, so
the rotation is ignored — the letter sits perfectly upright, looking nothing like a
paper-jam.
The fix is one rule in signStyles.css:
p#phrase3 span {
display: inline-block; /* enables transform */
}
inline-block gives the span a block formatting context (so transforms apply)
while keeping it in the text flow (so the letter stays in its word). It is the minimum change that
unlocks transform without breaking the line layout. This pattern applies to any CSS
animation or transform on a text character: the element must be inline-block or
block.
vw Units for Fluid Font ScalingThe phrase font sizes use viewport-width units:
<p id="phrase1" class="phrase googleFont1" style="font-size:3vw;">
1vw = 1% of the browser’s viewport width. At 1000px wide,
3vw = 30px. At 600px wide, it drops to 18px automatically — no media queries
required. This is called fluid typography: the font scale continuously tracks the
available space rather than jumping at discrete breakpoints.
The limitation: vw doesn’t clamp at a minimum. At very
narrow widths the text can become unreadably small. The more robust modern pattern is
font-size: clamp(1rem, 3vw, 2rem) which sets a minimum, a fluid mid-range, and
a maximum. The sign design uses the simpler 3vw as a first-pass approach
— the collapsible “About the design” section acknowledges this honestly.
background-image on the Door and the HolesTwo elements share the same texture: the outer “door” frame
(#signDiv) and the punch-hole dots (.dot):
div#signDiv { background-image: url("../images/doorGrain.png"); }
.dot { background-image: url("../images/doorGrain.png"); }
The dot-matrix paper sits on top of the door texture. The punch holes are
circular divs with border-radius: 50% — and because they use the
same door grain as their background, the door texture shows through the holes
in the paper. The paper appears to be physically punched out: you can see the door behind it.
This is a simple but satisfying illusion. The alternative — giving the holes a static door-colored background — would break the moment you changed the door texture. Using the same image on both keeps them automatically synchronized. Texture continuity through a hole is always handled this way in CSS: repeat the same image reference, let the browser handle the positioning.
visually-hidden + JavaScriptThe design div and intro div both start as visually-hidden.
A loading paragraph is shown instead. When the page finishes loading, myInit()
hides the loading message and reveals the content:
/* HTML */
<p id="loadingPara">... l.o.a.d.i.n.g...</p>
<div id="designDiv" class="visually-hidden">...</div>
/* JavaScript */
document.getElementById("loadingPara").classList.add("visually-hidden");
document.getElementById("designDiv").classList.remove("visually-hidden");
Bootstrap’s visually-hidden class hides an element from
visual display while keeping it readable by screen readers — which is the correct
accessibility behavior for content that is merely delayed, not absent.
This pattern is a lightweight page-load guard: the dot-matrix paper uses
multiple background-image references that can cause a brief layout shift as
they load. Holding the design visually-hidden until onload
fires prevents a flash of unstyled content.
The conQuest app is an application primarily showing the use of CSS in design. It was created with Copilot using our SPARK technique, along with a chatlog chronicling its development. I have another app, errHumanIndex.html that I’d like to upgrade to fit into our TNT ecosystem. I like all of the content of the page: I’d just like it to look like it fits in by modifying the navbar, hero area and footer to look more like the poster app. You can use the existing background image for the hero area. The historical ‘banter’ prior to the sign’s design is an important feature to me, along with the imagery. Let’s begin here with the remodel and also lets create errHumanChatlog.html to showcase the development and SPARK process. You have the conQuestChatlog.html as a model for the chat page I’d like.
The most important phrase in this prompt is “I like all of the content of the page.” This is a scope constraint: do not touch the sign design, do not touch the collapsible retrospective, do not touch the sign imagery. Only the presentation layer — navbar, hero, footer — is in scope.
“You can use the existing background image for the hero area”
is good prompting practice: it tells the AI to check what’s already in the workspace before
generating anything new. vintage-computer-collection-stockcake.jpg was already in
the images/ folder — a vintage computer collection, which is exactly the
right thematic match for a page about dot-matrix printers. No new image needed.
“The poster app” serves as the style reference:
convQuestPoster.html was the model for the navbar, SPARK bar, hero structure,
and footer pattern. Providing a concrete reference page is faster and more accurate than
describing each element from scratch.
tnt-base-styles.css Before signStyles.cssThe TNT ecosystem uses a two-layer stylesheet architecture:
<!-- Layer 1: ecosystem-wide rules (navbar, footer, hero structure) --> <link rel="stylesheet" href="../styles/tnt-base-styles.css"> <!-- Layer 2: page-specific rules (sign design, body gradient, hero background) --> <link rel="stylesheet" href="styles/signStyles.css">
The cascade resolves conflicts between the two layers:
when both files set the same property on the same selector, the later file wins.
signStyles.css loads after tnt-base-styles.css, so page-specific
rules always override the base. This means the body’s green radial-gradient
(in signStyles.css) overwrites the base’s background: #fff,
preserving the page’s character.
What the base stylesheet provides for free: the navbar styles, the footer
styles, the hero structural rules (display: flex; align-items: center;),
the CSS custom property tokens (--tnt-red, --tnt-amber,
--tnt-dark), and the shared button and typography utilities. Page-specific
CSS only needs to add what’s different.
vintage-computer-collection-stockcake.jpgBefore generating a new image, the workspace was checked.
vintage-computer-collection-stockcake.jpg was already present in the
images/ folder. It is a photograph of a vintage computer collection —
keyboards, monitors, and peripherals from the era when dot-matrix printers were common
office furniture.
This image is a better choice than a generated image for three reasons:
- It is already in the project — no generation, no download, no new asset to maintain.
- It is thematically precise: the page is about dot-matrix printing. A vintage computer collection is the correct visual context.
- It is dark enough for text overlay when combined with a gradient mask, without being artificially darkened.
The hero CSS uses the standard TNT image slot pattern:
gradient overlay first, image second, solid fallback on background-color.
If the image is absent, the dark green gradient renders alone — still readable,
still thematically appropriate.
max-width: 1000px from mainThe original signStyles.css constrained the entire
main element:
/* Original — limits main to 1000px and centres it */
main { max-width: 1000px; margin: auto; background-color: #F2F3E5; }
A hero section inside a 1000px-wide main would be limited
to 1000px — it would not reach the screen edges on wide monitors. The TNT hero pattern
requires full viewport width.
The fix was to remove the width constraint from main while
verifying that the individual content elements have their own constraints:
#introDivuses Bootstrap’scontainer-fluidwith internal padding.#signDivhaswidth: 80%; max-width: 800px; margin: auto;which still centres it.
Removing max-width from main also means the
body’s green radial-gradient no longer shows as visible side columns. The
#F2F3E5 light background fills the full width below the hero. This is a
cleaner look and consistent with how other TNT pages handle their content areas.
The SPARK bar needs an accent color for its links. Two candidates:
- #CCF4AD (light green) — the page’s original accent from
signStyles.css. Fits the page’s character. var(--tnt-amber)— the TNT ecosystem amber. Consistent with every other SPARK bar on the site.
The TNT amber was chosen. The SPARK bar is a navigation element that belongs to the TNT ecosystem layer, not the page’s content layer. Using a page-specific green would make the SPARK bar feel like part of the sign design rather than part of the site infrastructure.
Consistency in navigation is more valuable than local character. Students scanning multiple TNT pages should be able to recognise the SPARK bar immediately from its amber links — regardless of which app they are visiting.
main: Why It MovedThe original errHumanIndex.html placed its
<footer> inside <main>. The TNT footer is always
outside and after <main>:
/* Before */
<main>
...content...
<footer>...</footer> <!-- inside main — semantically incorrect -->
</main>
/* After */
<main>...content...</main>
<footer>...</footer> <!-- outside main — correct semantics -->
The <main> element is defined by the HTML spec as
“the dominant content of the document.” A site-wide footer is not dominant
page content — it is persistent page chrome. Placing it outside main
is semantically correct and matches the expectation of screen readers and accessibility tools.
The original .stickyFooter { min-height: calc(60vh); } class
on #primaryContent was removed from the HTML at the same time (the TNT footer
at the bottom of the document flow handles vertical spacing naturally).
| File | Status | What changed |
|---|---|---|
errHumanIndex.html |
Updated |
New <head>: Font Awesome, tnt-base-styles.css, SEO meta tags, updated script vars (stage 4, date 07/23/2026).Old jumbotron replaced with TNT navbar + SPARK bar + <section id="hero">.Footer moved outside <main>, replaced with standard TNT footer (icons + validate link + copyright).myInit() updated: uses textContent instead of innerHTML, adds stageSpan + footerPara + validate-link logic.
|
styles/signStyles.css |
Updated |
Removed max-width and margin: auto from main.Added #hero section styles (background image + gradient overlay, min-height, padding).Added .spark-bar styles.Added .hero-content, .hero-title, .hero-subtitle sizing overrides.
|
errHumanChatlog.html |
New | This page. Documents the original CSS techniques (Stages 1–3) and the Stage 4 TNT ecosystem upgrade. |
innerHTML → textContentThe original myInit() used innerHTML to write
the copyright string:
/* Original — avoidable innerHTML */ copyrightSpan.innerHTML = "© 2015-" + currentYear + " TechNoviceTools (TNT)";
This was refactored to textContent in the upgrade:
/* Upgrade — textContent for plain strings */
document.getElementById("copyrightSpan").textContent =
"\u00A9 2015\u2013" + currentYear + " TechNoviceTools (TNT)";
textContent treats everything as literal text —
no HTML parsing occurs. The Unicode escape \u00A9 is the copyright
symbol © and \u2013 is an en-dash –. Because these are
JavaScript string literals (not HTML), they render correctly without the
© entity.
The OWASP rule: use textContent for any value you
control but don’t need to parse as HTML. Use innerHTML only
when inserting trusted HTML structure. Applying this habit to every DOM write —
even harmless ones — prevents accidental XSS when code is later refactored
to accept external input.
display: inline-blockunlockstransform. Inline elements ignore CSS transforms entirely. Addingdisplay: inline-blockto a<span>is the minimum change that enables rotation, scaling, and translation while keeping the element in the text flow. This pattern applies to every CSS animation targeting an inline element.- The cascade is a feature, not a hazard.
Loading
tnt-base-styles.cssbeforesignStyles.csslets the page-specific file selectively override the base. The same technique powers every CSS framework (Bootstrap sets defaults; your code overrides them). Understanding which file wins, and why, is the foundation of maintainable CSS architecture. - Check the workspace before generating assets.
vintage-computer-collection-stockcake.jpgwas already present and semantically perfect. The best asset is the one you already have. Always look before you generate. - Semantic HTML matters for accessibility and maintenance.
A footer inside
<main>works visually but is semantically wrong. Moving it outside<main>takes five seconds and correctly signals its role to screen readers, search engines, and future developers. - Use
textContentfor plain strings. EveryinnerHTMLwrite is a potential XSS vector if the data source ever changes.textContentfor non-HTML strings is a habit worth building early — it has no performance cost and meaningful security benefit.
The entry in our image gallery: ‘Err is Human’ should have a
link to this app. Can you put one in there? (Let’s include this in our
chatlog). Also to punctuate the power of CSS, why don’t we place a
button under the rendered image of the sign that links to a page,
plainSignNoStyles.html that has the same page as
errHumanIndex.html but just not the styles that render the sign? That way,
students can see the before and after of what CSS did. (Add this to chatlog
too)
This prompt contains two independent requests. The first (gallery link) is
a one-line change in tnt_image_gallery.html. The second (CSS before/after page)
is a new file plus a button in the existing page. Combining them in one prompt is
efficient when both tasks are small and clear — the AI can verify neither conflicts
with the other before acting.
“Just not the styles that render the sign” is
a precise scope statement. It means: keep tnt-base-styles.css (navbar, footer),
remove signStyles.css (everything else). The gallery, image, and intro text
are all in scope. Only one stylesheet link changes.
“Students can see the before and after of what CSS did” frames the page as a teaching tool, not just a debugging page. That framing influenced the design: the CSS-off page needed a clear, prominent explanation banner so students arriving there understand immediately what they’re looking at.
The Image Gallery’s “Err Is Human” entry already had
the original sign photograph and a humanEntry comment about AI and technology.
The link was added to the end of that comment:
We even
<a href='ErrHumanSPARK2026-07-23/errHumanIndex.html'
target='_blank' rel='noopener noreferrer'>
recreated this sign in CSS
</a>
— dot-matrix paper, punch holes, misaligned letters and all!
The path uses the full subfolder prefix because tnt_image_gallery.html
lives at the site root while the app lives one level down. Relative paths in gallery
data-desc attributes are resolved from the gallery page’s own location.
The phrase “recreated this sign in CSS” is chosen deliberately: it names both the artifact (this sign) and the technology (CSS), so a student reading the gallery entry gets a preview of what the linked page is about before clicking.
plainSignNoStyles.html Keeps and DropsThe stripped page keeps tnt-base-styles.css and drops
signStyles.css. This is the smallest possible change that demonstrates
the most important point. The result:
| Element | With signStyles.css | Without signStyles.css |
|---|---|---|
| TNT navbar | Styled (tnt-base) | Styled (tnt-base) — unchanged |
| SPARK bar | Dark bar, amber links | Plain unstyled <div> |
| Hero section | Vintage-computer background, dark overlay | No background, no padding — text only |
| Body background | Green radial-gradient | White (tnt-base default) |
| Sign: door frame | Oak-grain texture, rounded, shadow | Plain block element |
| Sign: paper bands | Teal & cream stripes | Invisible — divs with no height or color |
| Phrase text | Overlaid, correct position | Below the band divs (document flow order) |
| TNT footer | Styled (tnt-base) | Styled (tnt-base) — unchanged |
The table is the lesson. Navbar and footer survive intact because their
styles live in a separate stylesheet that wasn’t removed. Everything between them
— the page’s character, its atmosphere, the sign itself — comes entirely
from signStyles.css.
plainSignNoStyles.html opens with a Bootstrap
alert-warning banner that explains what students are looking at:
<div class="alert alert-warning d-flex align-items-start gap-3">
<i class="fas fa-eye-slash fs-2"></i>
<div>
<strong>signStyles.css is OFF.</strong>
This page is identical to the styled version —
the only difference is that one stylesheet link has been removed...
</div>
</div>
Without the banner, a student loading the plain page would see a broken or incomplete-looking layout and might not understand why. The banner establishes the pedagogical frame before anything else is visible: this is intentional, and here is what you are meant to observe.
The fas fa-eye-slash icon reinforces the “CSS is off”
message visually. The corresponding “View With Sign Styles” button on the plain
page uses fas fa-eye — the same icon family, opposite state. Students
toggle between the two pages and the icon state mirrors what they’re doing.
The compare button lives inside #designDiv, directly below
the sign — so it appears only after the page finishes loading and
myInit() makes the div visible. A student who sees the sign gets the
button immediately underneath it:
errHumanIndex.html→ grey outline button: View Without Sign StylesplainSignNoStyles.html→ green solid button: View With Sign Styles
The colour coding is deliberate: grey signals “something is being turned off,” green signals “turn it back on.” The two buttons form a toggle pair even though they are on different pages.
Placing the button below the sign (not at the top of the page) means the student has already seen the sign before navigating away. The comparison is visceral: they click, the page loads, and the same HTML produces a completely different visual. The impact of that moment is the whole point of the exercise.
| File | Status | What changed |
|---|---|---|
tnt_image_gallery.html |
Updated | Link to ErrHumanSPARK2026-07-23/errHumanIndex.html added to the “Err Is Human” gallery entry’s humanEntry description. |
errHumanIndex.html |
Updated | Grey outline “View Without Sign Styles” button added below #signDiv, inside #designDiv. |
plainSignNoStyles.html |
New | Same HTML as errHumanIndex.html; signStyles.css removed; CSS-off alert banner added; green “View With Sign Styles” button replaces the grey button. |
errHumanChatlog.html |
Updated | Stage 5 section added (this content). |
The source image (noun-error-7255825-DEB887.png) is a
Noun Project warning/error icon in a warm wheat-gold color — thematically
perfect for a page whose entire premise is that computers amplify human error.
It was already in the images/ folder as a decorative element from
the original jumbotron design. The question was: can this same image become the
browser tab icon?
A favicon (short for “favorites icon”) is the small image in the browser tab, the bookmarks bar, and mobile home screen shortcuts. Every tab you have open right now is showing one. Getting one into your page is a two-step process:
- Generate the file. Take any image to favicomatic.com — a free tool that accepts a JPEG, PNG, GIF, or BMP and produces a properly scaled favicon package. It outputs multiple sizes (16×16 through 152×152) so the icon looks crisp in every browser, on every device, and in the bookmarks bar. One upload, download a zip, done.
- Link it in the
<head>. Copy the favicon file into the project’simages/folder, then add one line:<link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32">
The browser picks it up on the next reload — no server config, no build step, no other files needed.
The favicon-32x32.png now sitting in
ErrHumanSPARK2026-07-23/images/ is the error icon scaled and
packaged by favicomatic. A student who opens the app in a tab sees the warning
triangle next to the page title — a small detail that makes the app feel
complete and intentional rather than sporting the browser’s default blank page icon.
The custom favicon lives in ErrHumanSPARK2026-07-23/images/
and is applied to errHumanIndex.html. This chatlog links instead to the
standard TNT dynamite favicon at ../images/favicon-32x32.png —
the icon shared by every page on the TNT site:
<!-- errHumanIndex.html: custom favicon from the local images/ folder --> <link rel="icon" type="image/png" href="images/favicon-32x32.png" sizes="32x32"> <!-- errHumanChatlog.html: standard TNT favicon from the parent images/ folder --> <link rel="icon" type="image/png" href="../images/favicon-32x32.png" sizes="32x32">
The reasoning: the chatlog is a TNT ecosystem document, not part of the app experience. When a student opens the chatlog in a tab, it should look like any other TNT page — the same dynamite icon they see on Home, News, Explore, and everywhere else. The error icon belongs on the app because it is thematically tied to that page’s content and identity. The chatlog does not need to match the app; it needs to match the site.
This is a design principle worth naming: favicons are
branding signals, and different pages within the same site can legitimately
signal different contexts. The app is a project with its own personality; the chatlog
is documentation that belongs to the site’s editorial voice. Their tab icons
reflect that distinction. If you want students to notice it live, open both
errHumanIndex.html and errHumanChatlog.html in adjacent
tabs — two different icons, two different contexts, one folder.
| File | Status | What changed |
|---|---|---|
errHumanChatlog.html |
Updated | Favicon link updated from images/favicon-32x32.png
to ../images/favicon-32x32.png (standard TNT icon).Stage 6 section added: noun-error source image displayed, favicomatic workflow documented, app-vs-site favicon decision explained. |