pull the terminal styling back and add an ASCII backdrop

The first pass applied terminal decoration to everything, which read as
costume rather than design. Strip it back to a few deliberate cues and
let an animated backdrop carry the character instead:

- Drop the prefix glyphs from links, tags, nav items, badges and
  headings, and restore plain section names in place of snake_case.
- Keep one prompt in the hero, the ~/ on the wordmark, ISO dates and
  status dots; everything else is ordinary type.
- Restore depth: acrylic surfaces over soft radial light, 6px radii,
  and an accent glow on the primary action and status dots.
- Draw a dithered ASCII field behind the page. A fixed per-cell dither
  value decides which cells are lit so coverage stays even, while slow
  interfering waves move brightness through it. It idles when the tab
  is hidden and stays still under prefers-reduced-motion.
- Remove the nested panel boxes on the projects page and size the entry
  page to its article column.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
SowinskiBraedenandClaude Opus 5 committed 2026-10-17 23:55:06 -07:00
1 parent 8595290318
commit 54c00bf16f
10 files changed
+693 -698

No files matched your search

+6 -4
View File
@@ -73,6 +73,7 @@
</head>
<body class="js-stop-transition" data-page="about">
<canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header">
<nav class="nav">
<div class="logo">
@@ -134,7 +135,7 @@
<main class="container page-shell">
<div class="left-content">
<section id="profile">
<h2 class="panel__bar">profile</h2>
<h2 class="panel__bar">Profile</h2>
<div class="panel__body">
<img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="Braeden Sowinski profile photo">
<div class="profile-sub">
@@ -149,7 +150,7 @@
</div>
</section>
<section id="skills">
<h2 class="panel__bar">skills</h2>
<h2 class="panel__bar">Skills</h2>
<div class="panel__body">
<ul class="text skills-list">
<li>Java, C, Go, OCaml</li>
@@ -170,7 +171,7 @@
</div>
<section class="about" id="about">
<h2 class="panel__bar title">about_me</h2>
<h2 class="panel__bar title">About</h2>
<div class="panel__body">
<p class="text">
I'm Braeden Sowinski, a Computer Systems Technology student at
@@ -200,7 +201,7 @@
</section>
<section class="honors" id="honors">
<h2 class="panel__bar title">highlights</h2>
<h2 class="panel__bar title">Highlights</h2>
<div class="panel__body">
<dl>
<dt class="text"><b>Red Team Hackathon - 3rd Place</b></dt>
@@ -259,6 +260,7 @@
<script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<script src="./public/scripts/ascii-bg.js"></script>
</body>
</html>
+2
View File
@@ -52,6 +52,7 @@
</head>
<body class="js-stop-transition" data-page="projects">
<canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header">
<nav class="nav">
<div class="logo">
@@ -118,6 +119,7 @@
<script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<script src="./public/scripts/ascii-bg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./public/scripts/content.js"></script>
<script>
+15 -18
View File
@@ -110,6 +110,7 @@
</head>
<body class="js-stop-transition" data-page="home">
<canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header">
<nav class="nav">
<div class="logo">
@@ -171,42 +172,39 @@
<main class="main">
<section class="term">
<div class="term__bar">
<span class="term__title">braeden@sowinski.dev: <b>~</b></span>
<span class="term__dots">&#9472; &#9633; &#10005;</span>
<span class="term__dot"></span>
<span class="term__dot"></span>
<span class="term__dot"></span>
<span class="term__title">braeden@sowinski.dev</span>
</div>
<div class="term__body">
<div class="term__avatar-wrap">
<img src="https://avatars.githubusercontent.com/u/48144618" class="term__avatar" alt="Braeden Sowinski profile photo">
<span class="term__avatar-caption">./braeden.png</span>
</div>
<div class="term__stream">
<p class="cmd">whoami</p>
<h1 class="name line">Braeden Sowinski</h1>
<p class="cmd"><span class="cmd__text">whoami</span><span class="caret"></span></p>
<p class="cmd">cat ./role</p>
<p class="des line">
<h1 class="name">Braeden Sowinski</h1>
<p class="des">
Software Engineer &amp; Systems Admin | CST Student @ BCIT
</p>
<p class="des line hero-description-secondary">
<p class="des hero-description-secondary">
Backend systems, APIs, data-driven software, and low-level problem solving
</p>
<p class="cmd">ls ./highlights</p>
<div class="hero-badges line">
<div class="hero-badges">
<span class="badge">BCIT CST Diploma</span>
<span class="badge">C4A President</span>
<span class="badge">Hackathon Podium Finisher</span>
</div>
<p class="cmd">ls ./pages</p>
<div class="hero-actions line">
<a class="button button-primary" href="./projects.html">projects</a>
<a class="button button-secondary" href="./about.html">about</a>
<div class="hero-actions">
<a class="button button-primary" href="./projects.html">Projects</a>
<a class="button button-secondary" href="./about.html">About</a>
</div>
<p class="cmd">cat ./contact</p>
<div class="social-icons line">
<div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
title="LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
@@ -229,8 +227,6 @@
</svg>
</a>
</div>
<p class="prompt-end">$ <span class="caret"></span></p>
</div>
</div>
</section>
@@ -238,6 +234,7 @@
<script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<script src="./public/scripts/ascii-bg.js"></script>
<script src="./public/scripts/hero-effects.js"></script>
</body>
+6 -5
View File
@@ -49,6 +49,7 @@
</head>
<body class="js-stop-transition" data-page="projects">
<canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header">
<nav class="nav">
<div class="logo">
@@ -109,7 +110,6 @@
<main class="projects-page page-shell">
<section class="projects-hero">
<p class="cmd">ls ~/projects --all</p>
<h1>Braeden Sowinski projects, milestones, and technical writeups.</h1>
<p class="projects-intro">
A collection of backend, infrastructure, data-driven, and full-stack projects,
@@ -119,10 +119,10 @@
<section class="content-section">
<div class="section-heading">
<h2>selected_projects</h2>
<h2>Selected Projects</h2>
<div class="section-heading__right">
<span id="projects-count" class="section-count"></span>
<a class="section-link" href="https://git.sowinski.dev/SowinskiBraeden" target="_blank" rel="noopener noreferrer">gitea</a>
<a class="section-link" href="https://git.sowinski.dev/SowinskiBraeden" target="_blank" rel="noopener noreferrer">Gitea</a>
</div>
</div>
<div id="projects-grid" class="content-grid">
@@ -130,14 +130,14 @@
</div>
<div id="projects-toggle-shell" class="projects-toggle-shell" hidden>
<button id="projects-toggle" class="projects-toggle" type="button" aria-expanded="false">
show more projects
Show more projects
</button>
</div>
</section>
<section class="content-section updates-section">
<div class="section-heading">
<h2>updates_and_achievements</h2>
<h2>Updates &amp; Achievements</h2>
<div class="section-heading__right">
<span id="updates-count" class="section-count"></span>
</div>
@@ -177,6 +177,7 @@
<script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<script src="./public/scripts/ascii-bg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./public/scripts/content.js"></script>
<script>
+148 -172
View File
@@ -1,243 +1,219 @@
.container {
display: grid;
grid-template-columns: minmax(260px, 300px) repeat(3, 1fr);
grid-template-areas:
"pro about about about"
"pro other other other"
"footer footer footer footer";
grid-template-rows: auto 1fr auto;
gap: 18px;
padding-top: 22px;
min-height: calc(100vh - 120px);
align-items: stretch;
display: grid;
grid-template-columns: minmax(260px, 310px) repeat(3, 1fr);
grid-template-areas:
"pro about about about"
"pro other other other"
"footer footer footer footer";
grid-template-rows: auto 1fr auto;
gap: 20px;
padding-top: 40px;
min-height: calc(100vh - 140px);
align-items: stretch;
}
.left-content {
grid-area: pro;
display: flex;
flex-direction: column;
gap: 18px;
grid-area: pro;
display: flex;
flex-direction: column;
gap: 20px;
}
.about {
grid-area: about;
grid-area: about;
}
.honors {
grid-area: other;
display: flex;
flex-direction: column;
height: 100%;
grid-area: other;
display: flex;
flex-direction: column;
height: 100%;
}
.footer {
grid-area: footer;
grid-area: footer;
}
/* ----------------------------------------------------------- TUI panels */
section {
padding: 0;
background: var(--surface);
border: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 24px;
background: var(--surface);
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
border: 1px solid var(--border);
border-radius: var(--radius);
display: flex;
flex-direction: column;
}
.panel__bar {
margin: 0;
padding: 6px 12px;
background: var(--bar);
border-bottom: 1px solid var(--border);
font-size: 0.92rem;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--bright);
}
.panel__bar::before {
content: "▸ ";
color: var(--secondary);
margin: 0 0 16px;
font-size: 0.95rem;
font-weight: 700;
color: var(--bright);
}
.panel__body {
padding: 16px;
flex: 1;
}
#skills {
flex: 1;
}
#profile .panel__body {
display: flex;
flex-direction: column;
gap: 14px;
}
#profile img.profile {
width: 100%;
max-width: 180px;
aspect-ratio: 1;
object-fit: cover;
border: 1px solid var(--border-strong);
border-radius: 0;
filter: grayscale(0.3) contrast(1.03);
}
.profile-sub {
padding: 0;
border: 0;
background: transparent;
}
.profile-name {
margin: 0 0 4px;
font-size: 1.15rem;
line-height: 1.25;
}
.profile-role,
.profile-focus {
margin: 0;
font-size: 0.82rem;
color: var(--primary);
padding-left: 14px;
text-indent: -14px;
}
.profile-focus {
margin-top: 6px;
color: var(--muted);
}
.profile-role::before,
.profile-focus::before {
content: "#\00a0";
color: var(--border-strong);
}
/* ------------------------------------------------------------- contents */
.skills-list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0.85rem;
}
.skills-list li {
margin-bottom: 4px;
color: var(--primary);
padding-left: 14px;
text-indent: -14px;
}
.skills-list li::before {
content: "-\00a0";
color: var(--secondary);
}
.text {
font-weight: 400;
margin: 0 0 14px;
font-size: 0.9rem;
flex: 1;
}
.about .panel__body,
.honors .panel__body {
max-width: 92ch;
max-width: 86ch;
}
#skills {
flex: 1;
}
#profile .panel__body {
display: flex;
flex-direction: column;
gap: 18px;
}
#profile img.profile {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
border-radius: var(--radius);
border: 1px solid var(--border-strong);
box-shadow: var(--shadow);
}
.profile-sub {
padding: 0;
border: 0;
background: transparent;
}
.profile-name {
margin: 0 0 8px;
font-size: 1.2rem;
line-height: 1.25;
}
.profile-role,
.profile-focus {
margin: 0;
font-size: 0.84rem;
color: var(--primary);
}
.profile-focus {
margin-top: 8px;
color: var(--muted);
}
/* ---------------------------------------------------------------- content */
.skills-list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0.86rem;
display: flex;
flex-direction: column;
gap: 6px;
}
.skills-list li {
color: var(--primary);
padding-left: 16px;
text-indent: -16px;
}
.skills-list li::before {
content: "—\00a0";
color: var(--secondary);
}
.text {
font-weight: 400;
margin: 0 0 16px;
font-size: 0.9rem;
}
.about .text:last-child {
margin-bottom: 0;
margin-bottom: 0;
}
.about .text,
.honors .text {
color: var(--primary);
color: var(--primary);
}
.honors dl {
margin: 0;
margin: 0;
}
dt {
margin-top: 16px;
margin-top: 22px;
}
dt:first-of-type {
margin-top: 0;
margin-top: 0;
}
dt.text {
margin-bottom: 4px;
color: var(--bright);
}
dt.text b::before {
content: "▸ ";
color: var(--secondary);
font-weight: 400;
margin-bottom: 6px;
color: var(--bright);
}
dd {
margin-left: 14px;
color: var(--muted);
border-left: 1px solid var(--border);
padding-left: 12px;
margin-left: 0;
color: var(--muted);
}
dd.text {
color: var(--muted);
color: var(--muted);
}
.honor-inline-link {
display: inline-block;
margin-top: 6px;
font-size: 0.82rem;
color: var(--secondary);
}
.honor-inline-link::before {
content: "> ";
display: inline-block;
margin-top: 8px;
font-size: 0.84rem;
color: var(--secondary);
}
.honor-subitem {
margin-top: 10px;
padding-left: 12px;
border-left: 1px solid var(--border);
margin-top: 12px;
padding-left: 14px;
border-left: 1px solid var(--border);
}
.honor-subitem__label {
display: inline-block;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--secondary);
display: inline-block;
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--secondary);
}
.honor-subitem__text {
margin: 2px 0 0;
color: var(--muted);
font-size: 0.85rem;
margin: 4px 0 0;
color: var(--muted);
font-size: 0.86rem;
}
abbr {
text-decoration: underline dotted var(--border-strong);
cursor: help;
text-decoration: underline dotted var(--border-strong);
cursor: help;
}
@media only screen and (max-width: 920px) {
.container {
display: flex;
flex-direction: column;
min-height: auto;
gap: 14px;
}
.container {
display: flex;
flex-direction: column;
min-height: auto;
gap: 16px;
padding-top: 28px;
}
#profile img.profile {
max-width: 140px;
}
section {
padding: 20px;
}
#profile img.profile {
max-width: 160px;
}
}
+136 -235
View File
@@ -1,108 +1,82 @@
.projects-page,
.entry-page {
padding-top: 22px;
}
/* ----------------------------------------------------------- TUI panels */
.projects-hero,
.entry-shell,
.content-section {
background: var(--surface);
border: 1px solid var(--border);
padding-top: 40px;
}
/* The page header sits on the background; only cards get a surface. */
.projects-hero {
margin-bottom: 20px;
padding: 20px;
margin-bottom: 40px;
max-width: 760px;
}
.projects-hero h1,
.entry-title {
font-size: clamp(1.3rem, 2.6vw, 1.75rem);
line-height: 1.25;
margin: 8px 0 10px;
max-width: 860px;
font-size: clamp(1.5rem, 3vw, 2.1rem);
line-height: 1.2;
margin: 0 0 14px;
}
.projects-intro,
.entry-summary {
margin: 0;
max-width: 760px;
max-width: 720px;
color: var(--muted);
font-size: 0.92rem;
}
.eyebrow {
margin: 0;
font-size: 11px;
letter-spacing: 0.16em;
margin: 0 0 10px;
font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--secondary);
font-weight: 700;
}
.eyebrow::before {
content: "// ";
color: var(--border-strong);
font-weight: 500;
}
.content-section {
margin-bottom: 20px;
padding: 0;
margin-bottom: 44px;
}
/* Panel title bar, like a window chrome strip in a TUI. */
.section-heading {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin: 0;
padding: 6px 12px;
background: var(--bar);
align-items: baseline;
gap: 14px;
margin: 0 0 18px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
.section-heading h2 {
margin: 0;
font-size: 0.95rem;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.section-heading h2::before {
content: "▸ ";
color: var(--secondary);
.section-heading__right {
display: flex;
align-items: baseline;
gap: 16px;
}
.section-heading .eyebrow {
display: none;
.section-count {
font-size: 12px;
color: var(--muted);
}
.section-link {
font-size: 12px;
font-size: 13px;
color: var(--muted);
border: 1px solid var(--border);
padding: 1px 8px;
background: var(--surface);
}
.section-link::before {
content: "> ";
color: var(--secondary);
}
.section-link:hover {
color: var(--bright);
border-color: var(--secondary);
color: var(--secondary);
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 14px;
padding: 16px;
gap: 16px;
}
.updates-grid {
@@ -113,92 +87,91 @@
display: none;
}
/* ------------------------------------------------------------ TUI cards */
/* ---------------------------------------------------------------- cards */
.content-card {
padding: 0;
background: var(--surface-alt);
padding: 20px;
background: var(--surface);
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
border: 1px solid var(--border);
border-radius: var(--radius);
display: flex;
flex-direction: column;
min-height: 100%;
scroll-margin-top: 80px;
scroll-margin-top: 90px;
transition:
border-color 0.12s linear,
background-color 0.12s linear;
border-color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.content-card:hover {
border-color: var(--border-strong);
box-shadow: var(--shadow);
}
.content-card.featured {
border-color: rgba(235, 82, 52, 0.42);
border-color: rgba(235, 82, 52, 0.28);
}
body.light .content-card.featured {
border-color: rgba(200, 64, 31, 0.42);
border-color: rgba(200, 64, 31, 0.3);
}
.card-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin: 0;
padding: 4px 12px;
background: var(--bar);
border-bottom: 1px solid var(--border);
font-size: 11px;
gap: 12px;
margin: 0 0 14px;
font-size: 11.5px;
color: var(--muted);
}
.card-kicker {
color: var(--secondary);
font-weight: 700;
letter-spacing: 0.08em;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.card-date {
color: var(--muted);
font-size: 11px;
}
.card-status {
margin-left: auto;
color: var(--muted);
display: inline-flex;
align-items: center;
gap: 6px;
gap: 7px;
}
.card-status::before {
content: "";
width: 6px;
height: 6px;
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--muted);
display: inline-block;
}
.card-status[data-state="active"]::before {
background: var(--ok);
box-shadow: 0 0 8px rgba(127, 176, 105, 0.6);
}
.card-status[data-state="attention"]::before {
background: var(--secondary);
box-shadow: 0 0 8px rgba(235, 82, 52, 0.5);
}
.content-card h3 {
margin: 0;
padding: 12px 12px 0;
font-size: 1.05rem;
margin: 0 0 10px;
font-size: 1.1rem;
line-height: 1.3;
}
.card-summary {
color: var(--muted);
margin: 0;
padding: 8px 12px 12px;
font-size: 0.85rem;
margin: 0 0 16px;
font-size: 0.86rem;
flex: 1;
}
@@ -209,40 +182,29 @@ body.light .content-card.featured {
gap: 6px;
}
.tag-list {
padding: 0 12px 12px;
}
.tag,
.entry-tag {
display: inline-flex;
text-transform: lowercase;
padding: 0 6px;
padding: 2px 8px;
border-radius: var(--radius);
font-size: 11px;
color: var(--muted);
background: transparent;
background: var(--bar);
border: 1px solid var(--border);
}
.tag::before,
.entry-tag::before {
content: "#";
color: var(--border-strong);
margin-right: 2px;
}
.card-links,
.entry-links {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 18px;
align-items: center;
}
.card-links {
margin-top: auto;
padding: 10px 12px;
margin-top: 18px;
padding-top: 16px;
border-top: 1px solid var(--border);
background: var(--surface);
}
.card-link,
@@ -250,48 +212,25 @@ body.light .content-card.featured {
.inline-button {
display: inline-flex;
align-items: center;
border: 1px solid var(--border);
background: transparent;
color: var(--primary);
padding: 2px 9px;
font-size: 12px;
}
.card-link::before,
.entry-back::before,
.inline-button::before {
content: ">\00a0";
color: var(--secondary);
color: var(--muted);
font-size: 13px;
}
.card-link:hover,
.entry-back:hover,
.inline-button:hover {
color: var(--bright);
border-color: var(--secondary);
color: var(--secondary);
}
.card-link-primary,
.inline-button-primary {
border-color: var(--border-strong);
color: var(--bright);
}
.card-link-primary:hover,
.inline-button-primary:hover {
background: var(--secondary);
color: #0c0d10;
}
.card-link-primary:hover::before,
.inline-button-primary:hover::before {
color: #0c0d10;
color: var(--primary);
}
.projects-toggle-shell {
display: flex;
justify-content: center;
padding: 0 16px 16px;
margin-top: 24px;
}
.projects-toggle {
@@ -299,110 +238,87 @@ body.light .content-card.featured {
display: inline-flex;
align-items: center;
border: 1px solid var(--border);
padding: 4px 14px;
font-size: 12px;
background: var(--surface-alt);
border-radius: var(--radius);
padding: 7px 18px;
font-size: 13px;
background: var(--bar);
color: var(--muted);
cursor: pointer;
transition:
color 0.12s linear,
border-color 0.12s linear;
}
.projects-toggle::before {
content: "+\00a0";
color: var(--secondary);
}
.projects-toggle[aria-expanded="true"]::before {
content: "-\00a0";
color 0.18s ease,
border-color 0.18s ease;
}
.projects-toggle:hover {
color: var(--bright);
border-color: var(--secondary);
}
.updates-grid .content-card {
background: var(--surface-alt);
border-color: var(--border-strong);
}
.site-footer {
margin-bottom: 22px;
margin-bottom: 28px;
}
/* ------------------------------------------------------------ entry page */
/* ----------------------------------------------------------- entry page */
/* The article column sets the page width, so the card hugs its text. */
.entry-page.page-shell {
width: min(880px, calc(100% - 32px));
}
.entry-shell {
padding: 20px 22px 26px;
background: var(--surface);
backdrop-filter: blur(18px) saturate(135%);
-webkit-backdrop-filter: blur(18px) saturate(135%);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 32px 34px 38px;
}
.entry-header {
margin-bottom: 20px;
margin-bottom: 8px;
}
.entry-back {
margin-bottom: 18px;
margin-bottom: 26px;
}
.entry-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 14px;
font-size: 11px;
}
.entry-pill {
display: inline-flex;
align-items: center;
padding: 1px 8px;
border: 1px solid var(--border);
gap: 14px;
margin-bottom: 14px;
font-size: 11.5px;
color: var(--muted);
}
.entry-pill-primary {
color: var(--secondary);
border-color: rgba(235, 82, 52, 0.4);
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 700;
}
.entry-pill-secondary {
color: var(--muted);
letter-spacing: 0.1em;
font-size: 11px;
}
.entry-links {
margin-top: 16px;
margin-top: 20px;
}
.entry-content {
margin-top: 24px;
padding-top: 20px;
margin-top: 30px;
padding-top: 26px;
border-top: 1px solid var(--border);
font-size: 0.92rem;
max-width: 88ch;
}
.entry-content h1,
.entry-content h2,
.entry-content h3,
.entry-content h4 {
margin-top: 26px;
margin-bottom: 8px;
margin-top: 32px;
margin-bottom: 10px;
line-height: 1.3;
font-size: 1rem;
}
.entry-content h2::before,
.entry-content h3::before {
content: "## ";
color: var(--secondary);
font-weight: 400;
}
.entry-content h3::before {
content: "### ";
font-size: 1.02rem;
}
.entry-content p,
@@ -415,24 +331,17 @@ body.light .content-card.featured {
padding-left: 20px;
}
.entry-content ul {
list-style: none;
padding-left: 4px;
.entry-content li {
margin-bottom: 4px;
}
.entry-content ul li {
padding-left: 14px;
text-indent: -14px;
}
.entry-content ul li::before {
content: "-\00a0";
.entry-content li::marker {
color: var(--secondary);
}
.entry-content a {
color: var(--secondary);
border-bottom: 1px solid rgba(235, 82, 52, 0.35);
border-bottom: 1px solid rgba(235, 82, 52, 0.3);
}
.entry-content a:hover {
@@ -440,28 +349,34 @@ body.light .content-card.featured {
}
.entry-content blockquote {
margin: 16px 0;
padding: 10px 14px;
margin: 18px 0;
padding: 12px 16px;
border-left: 2px solid var(--secondary);
background: var(--surface-alt);
background: var(--bar);
border-radius: 0 var(--radius) var(--radius) 0;
color: var(--muted);
}
.entry-content code {
background: var(--bar);
border: 1px solid var(--border);
padding: 0 4px;
border-radius: 3px;
padding: 1px 5px;
font-size: 0.86rem;
}
.entry-content pre {
padding: 14px;
padding: 16px 18px;
overflow-x: auto;
background: var(--bar);
background: rgba(0, 0, 0, 0.22);
border: 1px solid var(--border);
border-left: 2px solid var(--secondary);
border-radius: var(--radius);
font-size: 0.84rem;
line-height: 1.5;
line-height: 1.55;
}
body.light .entry-content pre {
background: rgba(0, 0, 0, 0.05);
}
.entry-content pre code {
@@ -472,18 +387,25 @@ body.light .content-card.featured {
.entry-empty {
text-align: center;
padding: 36px 18px;
padding: 40px 18px;
}
@media only screen and (max-width: 860px) {
.projects-hero,
.entry-shell {
padding: 18px;
.projects-page,
.entry-page {
padding-top: 28px;
}
.content-grid {
padding: 12px;
gap: 12px;
.entry-page.page-shell {
width: min(880px, calc(100% - 20px));
}
.entry-shell {
padding: 22px 20px 26px;
}
.content-section {
margin-bottom: 34px;
}
.section-heading {
@@ -492,24 +414,3 @@ body.light .content-card.featured {
gap: 6px;
}
}
.section-heading__right {
display: flex;
align-items: center;
gap: 10px;
}
.section-count {
font-size: 11px;
color: var(--muted);
}
.section-count::before {
content: "[ ";
color: var(--border-strong);
}
.section-count::after {
content: " ]";
color: var(--border-strong);
}
+179 -248
View File
@@ -1,40 +1,47 @@
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap");
:root {
--theme: #0c0d10;
--surface: #111318;
--surface-alt: #15171d;
--bar: #16181e;
--primary: #c9cdd6;
--bright: #eef1f6;
--theme: #0b0c0f;
--surface: rgba(21, 23, 28, 0.58);
--surface-raised: rgba(28, 31, 37, 0.62);
--bar: rgba(255, 255, 255, 0.035);
--primary: #cbcfd8;
--bright: #f1f3f7;
--secondary: #eb5234;
--accent: #ff005a;
--ok: #7fb069;
--muted: #767d8a;
--border: #262a33;
--border-strong: #3a404c;
--grid: rgba(255, 255, 255, 0.022);
--selection: rgba(235, 82, 52, 0.28);
--muted: #7b8290;
--border: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.18);
--glow: rgba(235, 82, 52, 0.16);
--shadow: 0 18px 50px rgba(0, 0, 0, 0.34);
--selection: rgba(235, 82, 52, 0.3);
--ascii-base: #8e97a8;
--ascii-accent: #eb5234;
--radius: 6px;
--mono:
"JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
Consolas, "Liberation Mono", monospace;
}
body.light {
--theme: #e7e7e2;
--surface: #f2f2ee;
--surface-alt: #ebebe5;
--bar: #ddddd6;
--primary: #24262b;
--bright: #0e0f12;
--theme: #e9e9e4;
--surface: rgba(255, 255, 255, 0.56);
--surface-raised: rgba(255, 255, 255, 0.72);
--bar: rgba(0, 0, 0, 0.035);
--primary: #2a2d33;
--bright: #101216;
--secondary: #c8401f;
--accent: #c20046;
--ok: #4b7a3a;
--muted: #6a6e76;
--border: #cbcbc2;
--border-strong: #a9a9a0;
--grid: rgba(0, 0, 0, 0.03);
--muted: #6b6f78;
--border: rgba(0, 0, 0, 0.1);
--border-strong: rgba(0, 0, 0, 0.2);
--glow: rgba(200, 64, 31, 0.14);
--shadow: 0 18px 44px rgba(0, 0, 0, 0.1);
--selection: rgba(200, 64, 31, 0.22);
--ascii-base: #6f7682;
--ascii-accent: #c8401f;
}
* {
@@ -63,21 +70,38 @@ body {
position: relative;
overflow-x: hidden;
transition:
background-color 0.25s linear,
color 0.25s linear;
background-color 0.3s ease,
color 0.3s ease;
}
/* Faint graph-paper grid, fixed so it reads as a backdrop, not content. */
/* Soft light behind the ASCII field, so the page has depth without gloss. */
body::before {
content: "";
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background-image:
linear-gradient(var(--grid) 1px, transparent 1px),
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
background-size: 28px 28px;
background:
radial-gradient(circle at 18% 12%, var(--glow), transparent 42%),
radial-gradient(circle at 82% 8%, rgba(255, 0, 90, 0.07), transparent 38%),
radial-gradient(
circle at 50% 95%,
rgba(255, 255, 255, 0.035),
transparent 46%
);
filter: blur(28px);
}
#ascii-bg {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
opacity: 0.22;
}
body.light #ascii-bg {
opacity: 0.17;
}
body.js-stop-transition * {
@@ -108,7 +132,7 @@ h2,
h3,
h4 {
font-weight: 700;
letter-spacing: -0.01em;
letter-spacing: -0.015em;
color: var(--bright);
}
@@ -116,9 +140,10 @@ a {
color: var(--primary);
text-decoration: none;
transition:
color 0.12s linear,
background-color 0.12s linear,
border-color 0.12s linear;
color 0.18s ease,
background-color 0.18s ease,
border-color 0.18s ease,
box-shadow 0.18s ease;
}
a:hover {
@@ -141,10 +166,13 @@ pre {
/* ---------------------------------------------------------------- header */
.header {
position: relative;
position: sticky;
top: 0;
z-index: 1000;
border-bottom: 1px solid var(--border);
background: var(--bar);
background: var(--surface);
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
}
.nav {
@@ -155,7 +183,7 @@ pre {
gap: 12px;
width: min(1120px, calc(100% - 32px));
margin: 0 auto;
min-height: 46px;
min-height: 52px;
}
.logo {
@@ -167,11 +195,10 @@ pre {
.logo a {
display: inline-flex;
align-items: center;
gap: 8px;
gap: 9px;
font-size: 14px;
font-weight: 700;
color: var(--bright);
letter-spacing: 0.01em;
}
.logo a::before {
@@ -183,11 +210,10 @@ pre {
.logo a img {
display: block;
pointer-events: none;
border-radius: 2px !important;
border-radius: 3px !important;
width: 22px;
height: 22px;
border: 1px solid var(--border-strong);
filter: grayscale(0.25);
border: 1px solid var(--border);
}
.logo a:hover {
@@ -198,56 +224,28 @@ pre {
display: flex;
align-items: center;
list-style: none;
gap: 4px;
gap: 22px;
white-space: nowrap;
overflow-x: auto;
}
#menu a {
display: block;
font-size: 13px;
text-transform: lowercase;
padding: 3px 9px;
border: 1px solid transparent;
font-size: 13.5px;
color: var(--muted);
}
#menu a::before {
content: "[";
color: var(--border-strong);
margin-right: 2px;
}
#menu a::after {
content: "]";
color: var(--border-strong);
margin-left: 2px;
padding: 2px 0;
border-bottom: 1px solid transparent;
}
#menu a:hover {
color: var(--secondary);
}
#menu a:hover::before,
#menu a:hover::after {
color: var(--secondary);
color: var(--primary);
}
body[data-page="home"] #home-link a,
body[data-page="about"] #about-link a,
body[data-page="projects"] #projects-link a {
color: var(--bright);
background: var(--surface-alt);
border-color: var(--border);
}
body[data-page="home"] #home-link a::before,
body[data-page="about"] #about-link a::before,
body[data-page="projects"] #projects-link a::before,
body[data-page="home"] #home-link a::after,
body[data-page="about"] #about-link a::after,
body[data-page="projects"] #projects-link a::after {
color: var(--secondary);
border-bottom-color: var(--secondary);
}
.logo-switches {
@@ -257,27 +255,23 @@ body[data-page="projects"] #projects-link a::after {
button#theme-toggle {
background: transparent;
border: 1px solid var(--border);
border: 0;
color: var(--muted);
padding: 2px 5px;
height: 24px;
padding: 2px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition:
color 0.12s linear,
border-color 0.12s linear;
transition: color 0.18s ease;
}
button#theme-toggle svg {
height: 13px;
width: 13px;
height: 14px;
width: 14px;
}
button#theme-toggle:hover {
color: var(--secondary);
border-color: var(--secondary);
}
body.light #moon {
@@ -291,22 +285,18 @@ body:not(.light) #sun {
.menu.cta {
display: none;
margin: 0;
text-transform: lowercase;
padding: 2px 8px;
padding: 4px 10px;
font-size: 13px;
cursor: pointer;
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--muted);
}
.menu.cta::before {
content: "≡ ";
color: var(--secondary);
background: var(--bar);
}
.menu.cta:hover {
color: var(--secondary);
border-color: var(--secondary);
border-color: var(--border-strong);
}
/* --------------------------------------------------------------- overlay */
@@ -316,14 +306,20 @@ body:not(.light) #sun {
inset: 0;
z-index: 2147483647;
isolation: isolate;
background: var(--theme);
background: rgba(11, 12, 15, 0.82);
backdrop-filter: blur(18px) saturate(130%);
-webkit-backdrop-filter: blur(18px) saturate(130%);
overflow: hidden;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 0.15s linear,
visibility 0.15s linear;
opacity 0.2s ease,
visibility 0.2s ease;
}
body.light .overlay {
background: rgba(233, 233, 228, 0.86);
}
.overlay--active {
@@ -341,31 +337,21 @@ body:not(.light) #sun {
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
gap: 4px;
}
.overlay a {
color: var(--primary);
text-transform: lowercase;
padding: 10px 18px;
font-size: 1.3rem;
padding: 12px 20px;
font-size: 1.4rem;
display: block;
text-align: left;
width: min(320px, 80%);
border: 1px solid var(--border);
background: var(--surface);
}
.overlay a::before {
content: "> ";
color: var(--secondary);
text-align: center;
width: min(300px, 80%);
}
.overlay a:hover,
.overlay a:focus {
color: var(--bright);
border-color: var(--secondary);
background: var(--surface-alt);
color: var(--secondary);
}
.overlay .close {
@@ -373,19 +359,18 @@ body:not(.light) #sun {
z-index: 3;
top: 14px;
right: 18px;
font-size: 26px;
font-size: 28px;
line-height: 1;
font-family: var(--mono);
color: var(--muted);
cursor: pointer;
border: 1px solid var(--border);
border: 0;
background: transparent;
padding: 0 10px;
padding: 0 8px;
}
.overlay .close:hover {
color: var(--secondary);
border-color: var(--secondary);
}
body.menu-open {
@@ -412,105 +397,83 @@ body.menu-open .overlay * {
.page-shell {
width: min(1120px, calc(100% - 32px));
margin: 0 auto;
padding-bottom: 28px;
padding-bottom: 36px;
}
/* ------------------------------------------------------- terminal window */
.main {
width: min(1120px, calc(100% - 32px));
margin: 34px auto 0;
padding-bottom: 28px;
margin: 0 auto;
padding: 0 0 36px;
display: flex;
align-items: center;
min-height: calc(100vh - 52px);
}
.term {
width: 100%;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
}
body[data-page="home"] .main {
display: flex;
min-height: calc(100vh - 110px);
}
body[data-page="home"] .term {
flex: 1;
display: flex;
flex-direction: column;
}
body[data-page="home"] .term__body {
flex: 1;
align-content: start;
backdrop-filter: blur(18px) saturate(140%);
-webkit-backdrop-filter: blur(18px) saturate(140%);
box-shadow: var(--shadow);
overflow: hidden;
}
.term__bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 5px 10px;
gap: 7px;
padding: 9px 14px;
background: var(--bar);
border-bottom: 1px solid var(--border);
font-size: 12px;
color: var(--muted);
}
.term__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--border-strong);
flex: none;
}
.term__dot:first-child {
background: rgba(235, 82, 52, 0.75);
}
.term__title {
color: var(--muted);
}
.term__title b {
color: var(--primary);
font-weight: 500;
}
.term__dots {
letter-spacing: 2px;
color: var(--border-strong);
margin-left: 8px;
}
.term__body {
display: grid;
grid-template-columns: auto 1fr;
gap: 26px;
padding: 22px 22px 18px;
gap: 34px;
padding: 34px 32px;
align-items: center;
}
.term__avatar {
width: 148px;
height: 148px;
width: 160px;
height: 160px;
object-fit: cover;
border-radius: var(--radius);
border: 1px solid var(--border-strong);
border-radius: 0;
filter: grayscale(0.3) contrast(1.03);
}
.term__avatar-wrap {
display: flex;
flex-direction: column;
gap: 8px;
align-items: flex-start;
}
.term__avatar-caption {
font-size: 11px;
color: var(--muted);
box-shadow:
var(--shadow),
0 0 0 6px rgba(235, 82, 52, 0.05);
}
.term__stream {
min-width: 0;
}
.line {
margin: 0;
}
.line + .cmd {
margin-top: 16px;
}
.cmd {
margin: 0 0 18px;
color: var(--muted);
font-size: 13px;
}
@@ -518,98 +481,82 @@ body[data-page="home"] .term__body {
.cmd::before {
content: "$ ";
color: var(--secondary);
font-weight: 700;
}
.name {
font-size: clamp(1.5rem, 3.2vw, 2.1rem);
font-size: clamp(1.7rem, 3.6vw, 2.4rem);
font-weight: 700;
margin: 2px 0 0;
line-height: 1.2;
margin: 0;
line-height: 1.15;
color: var(--bright);
}
.des {
margin: 2px 0 0;
font-size: 0.94rem;
margin: 10px 0 0;
font-size: 0.95rem;
}
.hero-description-secondary {
color: var(--muted);
margin-top: 2px;
}
.hero-badges {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 6px;
margin-top: 20px;
}
.badge {
border: 1px solid var(--border);
background: var(--surface-alt);
color: var(--primary);
padding: 2px 8px;
background: var(--bar);
color: var(--muted);
border-radius: var(--radius);
padding: 3px 10px;
font-size: 12px;
}
.badge::before {
content: "+ ";
color: var(--ok);
}
.hero-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 6px;
margin-top: 22px;
}
.button {
display: inline-flex;
align-items: center;
border: 1px solid var(--border-strong);
background: var(--surface-alt);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bar);
color: var(--primary);
padding: 5px 12px;
padding: 7px 16px;
font-size: 13px;
font-weight: 500;
}
.button::before {
content: ">\00a0";
color: var(--secondary);
}
.button:hover {
color: var(--bright);
border-color: var(--secondary);
background: var(--surface);
border-color: var(--border-strong);
}
.button-primary {
border-color: var(--secondary);
border-color: rgba(235, 82, 52, 0.5);
color: var(--bright);
background: rgba(235, 82, 52, 0.14);
box-shadow: 0 6px 22px rgba(235, 82, 52, 0.14);
}
.button-primary:hover {
background: var(--secondary);
color: #0c0d10;
}
.button-primary:hover::before {
color: #0c0d10;
}
.prompt-end {
margin: 14px 0 0;
color: var(--secondary);
font-weight: 700;
border-color: var(--secondary);
background: rgba(235, 82, 52, 0.22);
box-shadow: 0 8px 28px rgba(235, 82, 52, 0.22);
}
.caret {
display: inline-block;
width: 0.6em;
width: 0.55em;
height: 1em;
vertical-align: -0.15em;
background: var(--secondary);
@@ -630,12 +577,13 @@ body[data-page="home"] .term__body {
.social-icons {
display: flex;
align-items: center;
gap: 14px;
gap: 16px;
margin-top: 24px;
}
.social-icons a svg {
height: 15px;
width: 15px;
height: 16px;
width: 16px;
display: block;
}
@@ -648,49 +596,36 @@ body[data-page="home"] .term__body {
color: var(--secondary);
}
/* --------------------------------------------------------- status footer */
/* ---------------------------------------------------------------- footer */
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding: 0;
background: var(--bar);
padding: 14px 18px;
background: var(--surface);
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
border: 1px solid var(--border);
font-size: 12px;
border-radius: var(--radius);
font-size: 13px;
}
.footer p {
margin: 0;
padding: 6px 12px;
color: var(--muted);
}
.footer p::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
background: var(--ok);
margin-right: 8px;
vertical-align: 1px;
}
.footer .social-icons {
padding: 6px 12px;
border-left: 1px solid var(--border);
margin-top: 0;
}
.loading-state {
color: var(--muted);
}
.loading-state::before {
content: "… ";
}
/* ---------------------------------------------------------- responsive */
/* ------------------------------------------------------------ responsive */
@media only screen and (max-width: 860px) {
#menu,
@@ -702,19 +637,16 @@ body[data-page="home"] .term__body {
display: block;
}
.nav {
min-height: 42px;
.main {
min-height: auto;
margin-top: 28px;
width: min(1120px, calc(100% - 20px));
}
.term__body {
grid-template-columns: 1fr;
gap: 0;
padding: 18px 16px 14px;
}
.main {
margin-top: 22px;
width: min(1120px, calc(100% - 20px));
padding: 26px 20px;
}
.page-shell {
@@ -726,8 +658,7 @@ body[data-page="home"] .term__body {
}
.footer .social-icons {
border-left: 0;
border-top: 1px solid var(--border);
margin-top: 12px;
}
}
+178
View File
@@ -0,0 +1,178 @@
(function() {
const canvas = document.getElementById("ascii-bg");
if (!canvas || !canvas.getContext) {
return;
}
const context = canvas.getContext("2d", { alpha: true });
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
const CHARS = "·.:-=+*#";
const CELL_WIDTH = 14;
const CELL_HEIGHT = 19;
const FRAME_MS = 1000 / 18;
const BASE_DENSITY = 0.09;
const WAVE_DENSITY = 0.24;
let columns = 0;
let rows = 0;
let dither = new Float32Array(0);
let baseColor = "rgba(255, 255, 255, 1)";
let accentColor = "rgba(235, 82, 52, 1)";
let time = 0;
let lastFrame = 0;
let rafId = 0;
// Interfering waves, centred on the viewport, give the field slow bright and
// dark swells. Which cells are lit comes from a fixed per-cell dither value,
// so the texture stays even everywhere instead of pooling into blobs, and
// cells brighten as a swell passes rather than flickering at random.
const waveAt = (x, y, t) => {
const radius = Math.sqrt(
((x - columns / 2) * 0.55) ** 2 + (y - rows / 2) ** 2
);
const a = Math.sin(x * 0.068 + t * 0.55);
const b = Math.sin(y * 0.1 - t * 0.42);
const c = Math.sin((x * 0.4 + y * 0.6) * 0.07 + t * 0.33);
const d = Math.sin(radius * 0.1 - t * 0.6);
return (a + b + c + d) / 8 + 0.5;
};
const buildDither = () => {
dither = new Float32Array(columns * rows);
for (let row = 0; row < rows; row += 1) {
for (let column = 0; column < columns; column += 1) {
const noise = Math.sin(column * 127.1 + row * 311.7) * 43758.5453;
dither[row * columns + column] = noise - Math.floor(noise);
}
}
};
const readThemeColors = () => {
const styles = getComputedStyle(document.body);
baseColor = styles.getPropertyValue("--ascii-base").trim() || baseColor;
accentColor = styles.getPropertyValue("--ascii-accent").trim() || accentColor;
};
const resize = () => {
const ratio = Math.min(window.devicePixelRatio || 1, 2);
const width = window.innerWidth;
const height = window.innerHeight;
canvas.width = Math.floor(width * ratio);
canvas.height = Math.floor(height * ratio);
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
context.setTransform(ratio, 0, 0, ratio, 0, 0);
context.font = `12px "JetBrains Mono", ui-monospace, monospace`;
context.textBaseline = "top";
columns = Math.ceil(width / CELL_WIDTH);
rows = Math.ceil(height / CELL_HEIGHT);
buildDither();
};
const draw = () => {
const width = canvas.width;
const height = canvas.height;
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, width, height);
context.restore();
let currentColor = null;
for (let row = 0; row < rows; row += 1) {
for (let column = 0; column < columns; column += 1) {
const level = waveAt(column, row, time);
if (dither[row * columns + column] > BASE_DENSITY + level * WAVE_DENSITY) {
continue;
}
const glyph = CHARS[Math.min(CHARS.length - 1, Math.floor(level * CHARS.length))];
const color = level > 0.9 ? accentColor : baseColor;
if (color !== currentColor) {
context.fillStyle = color;
currentColor = color;
}
context.globalAlpha = 0.06 + level * 0.5;
context.fillText(glyph, column * CELL_WIDTH, row * CELL_HEIGHT);
}
}
context.globalAlpha = 1;
};
const loop = (timestamp) => {
rafId = window.requestAnimationFrame(loop);
if (timestamp - lastFrame < FRAME_MS) {
return;
}
lastFrame = timestamp;
time += 0.021;
draw();
};
const stop = () => {
if (rafId) {
window.cancelAnimationFrame(rafId);
rafId = 0;
}
};
const start = () => {
if (rafId || prefersReducedMotion.matches) {
return;
}
lastFrame = 0;
rafId = window.requestAnimationFrame(loop);
};
const reset = () => {
readThemeColors();
resize();
draw();
};
reset();
start();
let resizeTimer = 0;
window.addEventListener("resize", () => {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(reset, 150);
});
// The theme toggle swaps a class on <body>; re-read the palette when it does.
new MutationObserver(() => {
readThemeColors();
draw();
}).observe(document.body, { attributes: true, attributeFilter: ["class"] });
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
stop();
return;
}
start();
});
prefersReducedMotion.addEventListener("change", () => {
stop();
start();
draw();
});
})();
+9 -9
View File
@@ -134,12 +134,12 @@
const renderLinks = (entry, className) => {
const links = [];
const sourceLabel = entry.github && entry.github.includes("git.sowinski.dev") ? "gitea" : "github";
const sourceLabel = entry.github && entry.github.includes("git.sowinski.dev") ? "Gitea" : "GitHub";
links.push(`<a class="${className} ${className}-primary" href="./entry.html?type=${encodeURIComponent(entry.type)}&slug=${encodeURIComponent(entry.slug)}">details</a>`);
links.push(`<a class="${className} ${className}-primary" href="./entry.html?type=${encodeURIComponent(entry.type)}&slug=${encodeURIComponent(entry.slug)}">Details</a>`);
if (entry.demo) {
links.push(`<a class="${className}" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">live</a>`);
links.push(`<a class="${className}" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>`);
} else if (entry.github) {
links.push(`<a class="${className}" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">${sourceLabel}</a>`);
}
@@ -224,7 +224,7 @@
card.classList.toggle("is-hidden", !expanded && index >= defaultVisibleCount);
});
toggleButton.textContent = expanded ? "show fewer projects" : "show more projects";
toggleButton.textContent = expanded ? "Show fewer projects" : "Show more projects";
toggleButton.setAttribute("aria-expanded", String(expanded));
};
@@ -306,7 +306,7 @@
<div class="entry-empty">
<p class="eyebrow">Not found</p>
<h1 class="entry-title">This entry could not be loaded.</h1>
<a class="entry-back" href="./projects.html">cd ../projects</a>
<a class="entry-back" href="./projects.html">&larr; Back to projects</a>
</div>
`;
return;
@@ -332,7 +332,7 @@
shell.innerHTML = `
<div class="entry-header">
<a class="entry-back" href="./projects.html">cd ../projects</a>
<a class="entry-back" href="./projects.html">&larr; Back to projects</a>
<div class="entry-meta">
<span class="entry-pill entry-pill-primary">${escapeHtml(entry.label || entry.type)}</span>
${entry.date ? `<span class="entry-pill entry-pill-secondary">${escapeHtml(formatDate(entry.date))}</span>` : ""}
@@ -342,8 +342,8 @@
<p class="entry-summary">${escapeHtml(entry.summary || "")}</p>
${renderTags(entry.tags || [], "entry-tag-list")}
<div class="entry-links">
${entry.demo ? `<a class="inline-button inline-button-primary" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">live</a>` : ""}
${entry.github ? `<a class="inline-button" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">${entry.github.includes("git.sowinski.dev") ? "gitea" : "github"}</a>` : ""}
${entry.demo ? `<a class="inline-button inline-button-primary" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>` : ""}
${entry.github ? `<a class="inline-button" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">${entry.github.includes("git.sowinski.dev") ? "Gitea" : "GitHub"}</a>` : ""}
</div>
</div>
<article class="entry-content">
@@ -355,7 +355,7 @@
<div class="entry-empty">
<p class="eyebrow">Not found</p>
<h1 class="entry-title">${escapeHtml(error.message)}</h1>
<a class="entry-back" href="./projects.html">cd ../projects</a>
<a class="entry-back" href="./projects.html">&larr; Back to projects</a>
</div>
`;
}
+14 -7
View File
@@ -17,11 +17,18 @@
// Build the playback script before hiding anything, so a thrown error here
// leaves the page in its fully rendered state.
const steps = Array.from(stream.children).map((element) => ({
element,
isCommand: element.classList.contains("cmd"),
text: element.textContent
}));
// Commands type into their own span so the blinking caret beside them
// survives the text rewrites.
const steps = Array.from(stream.children).map((element) => {
const target = element.querySelector(".cmd__text");
return {
element,
target: target || element,
isCommand: element.classList.contains("cmd"),
text: (target || element).textContent
};
});
if (!steps.some((step) => step.isCommand)) {
return;
@@ -29,7 +36,7 @@
steps.forEach((step) => {
if (step.isCommand) {
step.element.textContent = "";
step.target.textContent = "";
return;
}
@@ -48,7 +55,7 @@
const play = async () => {
for (const step of steps) {
if (step.isCommand) {
await typeCommand(step.element, step.text);
await typeCommand(step.target, step.text);
await wait(AFTER_COMMAND_DELAY);
continue;
}