render an ASCII torus behind thicker glass, drop the light theme

- Replace the flat dither backdrop with a lit, rotating torus drawn the
  way donut.c does it: a z-buffer per cell and a luminance ramp from a
  dot to a solid block, sized against the shorter viewport axis. The
  dither field stays underneath as dust. It leads on the landing page
  and fades back where there is reading to do, and a scrim keeps the
  projects heading legible over it.
- Thicken the glass: one .surface treatment with a heavier blur, more
  transparency and an SVG film grain, shared by the header, hero,
  cards, panels and footer.
- Give the landing page labelled contact cards for LinkedIn, Gitea and
  email in place of three bare icons.
- Drop the status chips from project cards and entry headers.
- Remove the light theme, its toggle and theme.js outright.
- Add restrained motion: underlines growing from the left on links and
  nav items, a lift on cards, buttons and contact cards, and tags
  brightening with their card. All of it is off under
  prefers-reduced-motion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
SowinskiBraedenandClaude Opus 5 committed 2026-10-18 00:10:00 -07:00
1 parent 54c00bf16f
commit 7078cda6c5
10 files changed
+448 -415

No files matched your search

+7 -28
View File
@@ -72,35 +72,15 @@
<link rel="stylesheet" href="./public/css/about.css"> <link rel="stylesheet" href="./public/css/about.css">
</head> </head>
<body class="js-stop-transition" data-page="about"> <body data-page="about">
<canvas id="ascii-bg" aria-hidden="true"></canvas> <canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header"> <header class="header surface">
<nav class="nav"> <nav class="nav">
<div class="logo"> <div class="logo">
<a href="./" title="sowinski.dev (Alt + H)"> <a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;"
aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</span>
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
@@ -134,7 +114,7 @@
<main class="container page-shell"> <main class="container page-shell">
<div class="left-content"> <div class="left-content">
<section id="profile"> <section id="profile" class="surface">
<h2 class="panel__bar">Profile</h2> <h2 class="panel__bar">Profile</h2>
<div class="panel__body"> <div class="panel__body">
<img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="Braeden Sowinski profile photo"> <img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="Braeden Sowinski profile photo">
@@ -149,7 +129,7 @@
</div> </div>
</div> </div>
</section> </section>
<section id="skills"> <section id="skills" class="surface">
<h2 class="panel__bar">Skills</h2> <h2 class="panel__bar">Skills</h2>
<div class="panel__body"> <div class="panel__body">
<ul class="text skills-list"> <ul class="text skills-list">
@@ -170,7 +150,7 @@
</section> </section>
</div> </div>
<section class="about" id="about"> <section class="about surface" id="about">
<h2 class="panel__bar title">About</h2> <h2 class="panel__bar title">About</h2>
<div class="panel__body"> <div class="panel__body">
<p class="text"> <p class="text">
@@ -200,7 +180,7 @@
</div> </div>
</section> </section>
<section class="honors" id="honors"> <section class="honors surface" id="honors">
<h2 class="panel__bar title">Highlights</h2> <h2 class="panel__bar title">Highlights</h2>
<div class="panel__body"> <div class="panel__body">
<dl> <dl>
@@ -230,7 +210,7 @@
</div> </div>
</section> </section>
<footer class="footer flex-container"> <footer class="footer flex-container surface">
<p>Get in touch with me!</p> <p>Get in touch with me!</p>
<div class="social-icons"> <div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" <a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
@@ -259,7 +239,6 @@
</main> </main>
<script src="./public/scripts/mobile.js"></script> <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/ascii-bg.js"></script>
</body> </body>
+3 -24
View File
@@ -51,35 +51,15 @@
<link rel="stylesheet" href="./public/css/projects.css"> <link rel="stylesheet" href="./public/css/projects.css">
</head> </head>
<body class="js-stop-transition" data-page="projects"> <body data-page="projects">
<canvas id="ascii-bg" aria-hidden="true"></canvas> <canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header"> <header class="header surface">
<nav class="nav"> <nav class="nav">
<div class="logo"> <div class="logo">
<a href="./" title="sowinski.dev (Alt + H)"> <a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;"
aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</span>
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
@@ -112,13 +92,12 @@
</header> </header>
<main class="entry-page page-shell"> <main class="entry-page page-shell">
<div id="entry-shell" class="entry-shell"> <div id="entry-shell" class="entry-shell surface">
<p class="loading-state">Loading entry...</p> <p class="loading-state">Loading entry...</p>
</div> </div>
</main> </main>
<script src="./public/scripts/mobile.js"></script> <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/ascii-bg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./public/scripts/content.js"></script> <script src="./public/scripts/content.js"></script>
+21 -39
View File
@@ -109,35 +109,15 @@
<link rel="stylesheet" href="./public/css/style.css"> <link rel="stylesheet" href="./public/css/style.css">
</head> </head>
<body class="js-stop-transition" data-page="home"> <body data-page="home">
<canvas id="ascii-bg" aria-hidden="true"></canvas> <canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header"> <header class="header surface">
<nav class="nav"> <nav class="nav">
<div class="logo"> <div class="logo">
<a href="./" title="sowinski.dev"> <a href="./" title="sowinski.dev">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;"
aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</span>
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
@@ -170,7 +150,7 @@
</header> </header>
<main class="main"> <main class="main">
<section class="term"> <section class="term surface">
<div class="term__bar"> <div class="term__bar">
<span class="term__dot"></span> <span class="term__dot"></span>
<span class="term__dot"></span> <span class="term__dot"></span>
@@ -204,27 +184,30 @@
<a class="button button-secondary" href="./about.html">About</a> <a class="button button-secondary" href="./about.html">About</a>
</div> </div>
<div class="social-icons"> <div class="social-icons hero-social">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" <a class="social-link" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
title="LinkedIn"> title="LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16"> viewBox="0 0 16 16">
<path <path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" /> d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg> </svg>
<span>LinkedIn</span>
</a> </a>
<a class="icon" href="https://git.sowinski.dev/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" title="Gitea" aria-label="Gitea"> <a class="social-link" href="https://git.sowinski.dev/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" title="Gitea" aria-label="Gitea">
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"> <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path <path
d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z" /> d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z" />
</svg> </svg>
<span>Gitea</span>
</a> </a>
<a class="icon" href="mailto:braeden@sowinski.dev" target="_blank" rel="noopener noreferrer me" title="Email"> <a class="social-link" href="mailto:braeden@sowinski.dev" target="_blank" rel="noopener noreferrer me" title="Email">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" /> <polyline points="22,6 12,13 2,6" />
</svg> </svg>
<span>Email</span>
</a> </a>
</div> </div>
</div> </div>
@@ -233,7 +216,6 @@
</main> </main>
<script src="./public/scripts/mobile.js"></script> <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/ascii-bg.js"></script>
<script src="./public/scripts/hero-effects.js"></script> <script src="./public/scripts/hero-effects.js"></script>
</body> </body>
+3 -24
View File
@@ -48,35 +48,15 @@
<link rel="stylesheet" href="./public/css/projects.css"> <link rel="stylesheet" href="./public/css/projects.css">
</head> </head>
<body class="js-stop-transition" data-page="projects"> <body data-page="projects">
<canvas id="ascii-bg" aria-hidden="true"></canvas> <canvas id="ascii-bg" aria-hidden="true"></canvas>
<header class="header"> <header class="header surface">
<nav class="nav"> <nav class="nav">
<div class="logo"> <div class="logo">
<a href="./" title="sowinski.dev (Alt + H)"> <a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="Braeden Sowinski profile photo" style="border-radius:50%;"
aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev aria-label="Braeden Sowinski profile photo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</span>
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
@@ -147,7 +127,7 @@
</div> </div>
</section> </section>
<footer class="footer flex-container site-footer"> <footer class="footer flex-container site-footer surface">
<p>Get in touch with me!</p> <p>Get in touch with me!</p>
<div class="social-icons"> <div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" <a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
@@ -176,7 +156,6 @@
</main> </main>
<script src="./public/scripts/mobile.js"></script> <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/ascii-bg.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./public/scripts/content.js"></script> <script src="./public/scripts/content.js"></script>
+12 -5
View File
@@ -36,13 +36,16 @@
section { section {
padding: 24px; 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); border-radius: var(--radius);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition:
border-color 0.24s var(--ease),
box-shadow 0.24s var(--ease);
}
section:hover {
border-color: var(--border-strong);
} }
.panel__bar { .panel__bar {
@@ -78,6 +81,11 @@ section {
border-radius: var(--radius); border-radius: var(--radius);
border: 1px solid var(--border-strong); border: 1px solid var(--border-strong);
box-shadow: var(--shadow); box-shadow: var(--shadow);
transition: transform 0.4s var(--ease);
}
#profile:hover img.profile {
transform: scale(1.015);
} }
.profile-sub { .profile-sub {
@@ -169,7 +177,6 @@ dd.text {
} }
.honor-inline-link { .honor-inline-link {
display: inline-block;
margin-top: 8px; margin-top: 8px;
font-size: 0.84rem; font-size: 0.84rem;
color: var(--secondary); color: var(--secondary);
+79 -61
View File
@@ -5,10 +5,28 @@
/* The page header sits on the background; only cards get a surface. */ /* The page header sits on the background; only cards get a surface. */
.projects-hero { .projects-hero {
position: relative;
isolation: isolate;
margin-bottom: 40px; margin-bottom: 40px;
max-width: 760px; max-width: 760px;
} }
/* Soft scrim so the heading stays readable over the ASCII field, without
putting the text in a box. */
.projects-hero::before {
content: "";
position: absolute;
inset: -28px -10px -20px -10px;
z-index: -1;
pointer-events: none;
background: radial-gradient(
ellipse at 30% 50%,
rgba(11, 12, 15, 0.92),
rgba(11, 12, 15, 0.6) 45%,
transparent 75%
);
}
.projects-hero h1, .projects-hero h1,
.entry-title { .entry-title {
font-size: clamp(1.5rem, 3vw, 2.1rem); font-size: clamp(1.5rem, 3vw, 2.1rem);
@@ -64,15 +82,6 @@
color: var(--muted); color: var(--muted);
} }
.section-link {
font-size: 13px;
color: var(--muted);
}
.section-link:hover {
color: var(--secondary);
}
.content-grid { .content-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
@@ -90,33 +99,36 @@
/* ---------------------------------------------------------------- cards */ /* ---------------------------------------------------------------- cards */
.content-card { .content-card {
padding: 20px; padding: 22px;
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); border-radius: var(--radius);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%; min-height: 100%;
scroll-margin-top: 90px; scroll-margin-top: 90px;
transition: transition:
border-color 0.2s ease, border-color 0.24s var(--ease),
box-shadow 0.2s ease, box-shadow 0.24s var(--ease),
transform 0.2s ease; transform 0.24s var(--ease);
} }
.content-card:hover { .content-card:hover {
border-color: var(--border-strong); border-color: var(--border-strong);
box-shadow: var(--shadow); transform: translateY(-3px);
box-shadow:
0 26px 60px rgba(0, 0, 0, 0.46),
inset 0 1px 0 rgba(255, 255, 255, 0.09);
} }
.content-card.featured { .content-card.featured {
border-color: rgba(235, 82, 52, 0.28); border-color: rgba(235, 82, 52, 0.28);
} }
body.light .content-card.featured { .content-card.featured:hover {
border-color: rgba(200, 64, 31, 0.3); border-color: rgba(235, 82, 52, 0.55);
box-shadow:
0 26px 60px rgba(0, 0, 0, 0.46),
0 0 32px rgba(235, 82, 52, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.09);
} }
.card-meta { .card-meta {
@@ -136,32 +148,6 @@ body.light .content-card.featured {
font-size: 11px; font-size: 11px;
} }
.card-status {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 7px;
}
.card-status::before {
content: "";
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 { .content-card h3 {
margin: 0 0 10px; margin: 0 0 10px;
font-size: 1.1rem; font-size: 1.1rem;
@@ -191,6 +177,14 @@ body.light .content-card.featured {
color: var(--muted); color: var(--muted);
background: var(--bar); background: var(--bar);
border: 1px solid var(--border); border: 1px solid var(--border);
transition:
color 0.2s var(--ease),
border-color 0.2s var(--ease);
}
.content-card:hover .tag {
color: var(--primary);
border-color: var(--border-strong);
} }
.card-links, .card-links,
@@ -207,21 +201,52 @@ body.light .content-card.featured {
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
} }
/* Text links share one hover: colour plus an underline growing from the left. */
.card-link, .card-link,
.entry-back, .entry-back,
.inline-button { .inline-button,
.section-link,
.honor-inline-link {
position: relative;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: var(--muted); color: var(--muted);
font-size: 13px; font-size: 13px;
} }
.card-link::after,
.entry-back::after,
.inline-button::after,
.section-link::after,
.honor-inline-link::after {
content: "";
position: absolute;
left: 0;
bottom: -2px;
height: 1px;
width: 100%;
background: var(--secondary);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.24s var(--ease);
}
.card-link:hover, .card-link:hover,
.entry-back:hover, .entry-back:hover,
.inline-button:hover { .inline-button:hover,
.section-link:hover,
.honor-inline-link:hover {
color: var(--secondary); color: var(--secondary);
} }
.card-link:hover::after,
.entry-back:hover::after,
.inline-button:hover::after,
.section-link:hover::after,
.honor-inline-link:hover::after {
transform: scaleX(1);
}
.card-link-primary, .card-link-primary,
.inline-button-primary { .inline-button-primary {
color: var(--primary); color: var(--primary);
@@ -239,19 +264,21 @@ body.light .content-card.featured {
align-items: center; align-items: center;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
padding: 7px 18px; padding: 8px 20px;
font-size: 13px; font-size: 13px;
background: var(--bar); background: var(--bar);
color: var(--muted); color: var(--muted);
cursor: pointer; cursor: pointer;
transition: transition:
color 0.18s ease, color 0.2s var(--ease),
border-color 0.18s ease; border-color 0.2s var(--ease),
transform 0.2s var(--ease);
} }
.projects-toggle:hover { .projects-toggle:hover {
color: var(--bright); color: var(--bright);
border-color: var(--border-strong); border-color: var(--border-strong);
transform: translateY(-1px);
} }
.site-footer { .site-footer {
@@ -266,12 +293,7 @@ body.light .content-card.featured {
} }
.entry-shell { .entry-shell {
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); border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 32px 34px 38px; padding: 32px 34px 38px;
} }
@@ -368,17 +390,13 @@ body.light .content-card.featured {
.entry-content pre { .entry-content pre {
padding: 16px 18px; padding: 16px 18px;
overflow-x: auto; overflow-x: auto;
background: rgba(0, 0, 0, 0.22); background: rgba(0, 0, 0, 0.26);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
font-size: 0.84rem; font-size: 0.84rem;
line-height: 1.55; line-height: 1.55;
} }
body.light .entry-content pre {
background: rgba(0, 0, 0, 0.05);
}
.entry-content pre code { .entry-content pre code {
background: transparent; background: transparent;
border: 0; border: 0;
+161 -122
View File
@@ -2,46 +2,30 @@
:root { :root {
--theme: #0b0c0f; --theme: #0b0c0f;
--surface: rgba(21, 23, 28, 0.58); --surface: rgba(20, 22, 27, 0.34);
--surface-raised: rgba(28, 31, 37, 0.62); --surface-raised: rgba(28, 31, 38, 0.46);
--bar: rgba(255, 255, 255, 0.035); --bar: rgba(255, 255, 255, 0.04);
--primary: #cbcfd8; --primary: #ccd0d9;
--bright: #f1f3f7; --bright: #f2f4f8;
--secondary: #eb5234; --secondary: #eb5234;
--accent: #ff005a; --accent: #ff005a;
--ok: #7fb069; --muted: #7d8492;
--muted: #7b8290; --border: rgba(255, 255, 255, 0.1);
--border: rgba(255, 255, 255, 0.09); --border-strong: rgba(255, 255, 255, 0.2);
--border-strong: rgba(255, 255, 255, 0.18);
--glow: rgba(235, 82, 52, 0.16); --glow: rgba(235, 82, 52, 0.16);
--shadow: 0 18px 50px rgba(0, 0, 0, 0.34); --shadow: 0 20px 54px rgba(0, 0, 0, 0.4);
--selection: rgba(235, 82, 52, 0.3); --selection: rgba(235, 82, 52, 0.3);
--ascii-base: #8e97a8; --ascii-base: #99a2b4;
--ascii-accent: #eb5234; --ascii-accent: #eb5234;
--radius: 6px; --ascii-dim: #5f6675;
--radius: 7px;
--ease: cubic-bezier(0.2, 0.7, 0.3, 1);
--mono: --mono:
"JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
Consolas, "Liberation Mono", monospace; Consolas, "Liberation Mono", monospace;
}
body.light { /* Fine film grain that sits on every frosted surface. */
--theme: #e9e9e4; --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)' opacity='0.55'/%3E%3C/svg%3E");
--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: #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;
} }
* { * {
@@ -69,9 +53,6 @@ body {
min-height: 100vh; min-height: 100vh;
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
transition:
background-color 0.3s ease,
color 0.3s ease;
} }
/* Soft light behind the ASCII field, so the page has depth without gloss. */ /* Soft light behind the ASCII field, so the page has depth without gloss. */
@@ -97,15 +78,11 @@ body::before {
inset: 0; inset: 0;
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
opacity: 0.22; opacity: 0.15;
} }
body.light #ascii-bg { body[data-page="home"] #ascii-bg {
opacity: 0.17; opacity: 0.34;
}
body.js-stop-transition * {
transition: none !important;
} }
header, header,
@@ -115,6 +92,33 @@ main,
z-index: 1; z-index: 1;
} }
/* ------------------------------------------------------- frosted surfaces */
.surface {
position: relative;
isolation: isolate;
background: var(--surface);
backdrop-filter: blur(26px) saturate(150%);
-webkit-backdrop-filter: blur(26px) saturate(150%);
border: 1px solid var(--border);
box-shadow:
var(--shadow),
inset 0 1px 0 rgba(255, 255, 255, 0.07);
}
.surface::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
background-image: var(--grain);
background-size: 180px 180px;
opacity: 0.22;
mix-blend-mode: soft-light;
}
a, a,
h1, h1,
h2, h2,
@@ -140,10 +144,11 @@ a {
color: var(--primary); color: var(--primary);
text-decoration: none; text-decoration: none;
transition: transition:
color 0.18s ease, color 0.2s var(--ease),
background-color 0.18s ease, background-color 0.2s var(--ease),
border-color 0.18s ease, border-color 0.2s var(--ease),
box-shadow 0.18s ease; box-shadow 0.2s var(--ease),
transform 0.2s var(--ease);
} }
a:hover { a:hover {
@@ -169,10 +174,9 @@ pre {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1000; z-index: 1000;
border: 0;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
background: var(--surface); box-shadow: none;
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
} }
.nav { .nav {
@@ -183,7 +187,7 @@ pre {
gap: 12px; gap: 12px;
width: min(1120px, calc(100% - 32px)); width: min(1120px, calc(100% - 32px));
margin: 0 auto; margin: 0 auto;
min-height: 52px; min-height: 54px;
} }
.logo { .logo {
@@ -214,72 +218,66 @@ pre {
width: 22px; width: 22px;
height: 22px; height: 22px;
border: 1px solid var(--border); border: 1px solid var(--border);
transition: border-color 0.2s var(--ease);
} }
.logo a:hover { .logo a:hover {
color: var(--secondary); color: var(--secondary);
} }
.logo a:hover img {
border-color: rgba(235, 82, 52, 0.5);
}
#menu { #menu {
display: flex; display: flex;
align-items: center; align-items: center;
list-style: none; list-style: none;
gap: 22px; gap: 24px;
white-space: nowrap; white-space: nowrap;
overflow-x: auto; overflow-x: auto;
} }
#menu a { #menu a {
display: block; display: block;
position: relative;
font-size: 13.5px; font-size: 13.5px;
color: var(--muted); color: var(--muted);
padding: 2px 0; padding: 2px 0;
border-bottom: 1px solid transparent; }
/* Underline grows out from the left on hover. */
#menu a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
height: 1px;
width: 100%;
background: var(--secondary);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.24s var(--ease);
} }
#menu a:hover { #menu a:hover {
color: var(--primary); color: var(--primary);
} }
#menu a:hover::after {
transform: scaleX(1);
}
body[data-page="home"] #home-link a, body[data-page="home"] #home-link a,
body[data-page="about"] #about-link a, body[data-page="about"] #about-link a,
body[data-page="projects"] #projects-link a { body[data-page="projects"] #projects-link a {
color: var(--bright); color: var(--bright);
border-bottom-color: var(--secondary);
} }
.logo-switches { body[data-page="home"] #home-link a::after,
display: inline-flex; body[data-page="about"] #about-link a::after,
align-items: center; body[data-page="projects"] #projects-link a::after {
} transform: scaleX(1);
button#theme-toggle {
background: transparent;
border: 0;
color: var(--muted);
padding: 2px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: color 0.18s ease;
}
button#theme-toggle svg {
height: 14px;
width: 14px;
}
button#theme-toggle:hover {
color: var(--secondary);
}
body.light #moon {
display: none;
}
body:not(.light) #sun {
display: none;
} }
.menu.cta { .menu.cta {
@@ -292,6 +290,9 @@ body:not(.light) #sun {
border-radius: var(--radius); border-radius: var(--radius);
color: var(--muted); color: var(--muted);
background: var(--bar); background: var(--bar);
transition:
color 0.2s var(--ease),
border-color 0.2s var(--ease);
} }
.menu.cta:hover { .menu.cta:hover {
@@ -306,20 +307,16 @@ body:not(.light) #sun {
inset: 0; inset: 0;
z-index: 2147483647; z-index: 2147483647;
isolation: isolate; isolation: isolate;
background: rgba(11, 12, 15, 0.82); background: rgba(11, 12, 15, 0.84);
backdrop-filter: blur(18px) saturate(130%); backdrop-filter: blur(20px) saturate(140%);
-webkit-backdrop-filter: blur(18px) saturate(130%); -webkit-backdrop-filter: blur(20px) saturate(140%);
overflow: hidden; overflow: hidden;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
transition: transition:
opacity 0.2s ease, opacity 0.22s var(--ease),
visibility 0.2s ease; visibility 0.22s var(--ease);
}
body.light .overlay {
background: rgba(233, 233, 228, 0.86);
} }
.overlay--active { .overlay--active {
@@ -367,6 +364,7 @@ body.light .overlay {
border: 0; border: 0;
background: transparent; background: transparent;
padding: 0 8px; padding: 0 8px;
transition: color 0.2s var(--ease);
} }
.overlay .close:hover { .overlay .close:hover {
@@ -403,22 +401,17 @@ body.menu-open .overlay * {
/* ------------------------------------------------------- terminal window */ /* ------------------------------------------------------- terminal window */
.main { .main {
width: min(1120px, calc(100% - 32px)); width: min(880px, calc(100% - 32px));
margin: 0 auto; margin: 0 auto;
padding: 0 0 36px; padding: 0 0 36px;
display: flex; display: flex;
align-items: center; align-items: center;
min-height: calc(100vh - 52px); min-height: calc(100vh - 54px);
} }
.term { .term {
width: 100%; width: 100%;
border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--surface);
backdrop-filter: blur(18px) saturate(140%);
-webkit-backdrop-filter: blur(18px) saturate(140%);
box-shadow: var(--shadow);
overflow: hidden; overflow: hidden;
} }
@@ -452,14 +445,14 @@ body.menu-open .overlay * {
.term__body { .term__body {
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
gap: 34px; gap: 30px;
padding: 34px 32px; padding: 32px 30px;
align-items: center; align-items: center;
} }
.term__avatar { .term__avatar {
width: 160px; width: 148px;
height: 160px; height: 148px;
object-fit: cover; object-fit: cover;
border-radius: var(--radius); border-radius: var(--radius);
border: 1px solid var(--border-strong); border: 1px solid var(--border-strong);
@@ -531,7 +524,7 @@ body.menu-open .overlay * {
border-radius: var(--radius); border-radius: var(--radius);
background: var(--bar); background: var(--bar);
color: var(--primary); color: var(--primary);
padding: 7px 16px; padding: 8px 18px;
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
} }
@@ -539,6 +532,7 @@ body.menu-open .overlay * {
.button:hover { .button:hover {
color: var(--bright); color: var(--bright);
border-color: var(--border-strong); border-color: var(--border-strong);
transform: translateY(-1px);
} }
.button-primary { .button-primary {
@@ -551,7 +545,7 @@ body.menu-open .overlay * {
.button-primary:hover { .button-primary:hover {
border-color: var(--secondary); border-color: var(--secondary);
background: rgba(235, 82, 52, 0.22); background: rgba(235, 82, 52, 0.22);
box-shadow: 0 8px 28px rgba(235, 82, 52, 0.22); box-shadow: 0 10px 30px rgba(235, 82, 52, 0.26);
} }
.caret { .caret {
@@ -574,6 +568,8 @@ body.menu-open .overlay * {
} }
} }
/* ------------------------------------------------------------- contact */
.social-icons { .social-icons {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -581,21 +577,62 @@ body.menu-open .overlay * {
margin-top: 24px; margin-top: 24px;
} }
.social-icons a {
color: var(--muted);
display: inline-flex;
}
.social-icons a svg { .social-icons a svg {
height: 16px; height: 16px;
width: 16px; width: 16px;
display: block; display: block;
} }
.social-icons a {
color: var(--muted);
display: inline-flex;
}
.icon:hover { .icon:hover {
color: var(--secondary); color: var(--secondary);
} }
/* The landing page gets labelled contact cards instead of bare icons. */
.hero-social {
gap: 12px;
flex-wrap: wrap;
margin-top: 28px;
}
.hero-social .social-link {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
font-size: 13.5px;
color: var(--primary);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bar);
}
.hero-social .social-link svg {
height: 18px;
width: 18px;
color: var(--muted);
transition:
color 0.2s var(--ease),
transform 0.2s var(--ease);
}
.hero-social .social-link:hover {
color: var(--bright);
border-color: rgba(235, 82, 52, 0.45);
background: rgba(235, 82, 52, 0.08);
transform: translateY(-2px);
box-shadow: 0 10px 26px rgba(235, 82, 52, 0.16);
}
.hero-social .social-link:hover svg {
color: var(--secondary);
transform: scale(1.08);
}
/* ---------------------------------------------------------------- footer */ /* ---------------------------------------------------------------- footer */
.footer { .footer {
@@ -604,10 +641,6 @@ body.menu-open .overlay * {
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 14px 18px; padding: 14px 18px;
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); border-radius: var(--radius);
font-size: 13px; font-size: 13px;
} }
@@ -621,6 +654,10 @@ body.menu-open .overlay * {
margin-top: 0; margin-top: 0;
} }
.footer .social-icons a:hover {
transform: translateY(-2px);
}
.loading-state { .loading-state {
color: var(--muted); color: var(--muted);
} }
@@ -640,7 +677,7 @@ body.menu-open .overlay * {
.main { .main {
min-height: auto; min-height: auto;
margin-top: 28px; margin-top: 28px;
width: min(1120px, calc(100% - 20px)); width: min(880px, calc(100% - 20px));
} }
.term__body { .term__body {
@@ -653,6 +690,10 @@ body.menu-open .overlay * {
width: min(1120px, calc(100% - 20px)); width: min(1120px, calc(100% - 20px));
} }
.hero-social .social-link {
padding: 9px 14px;
}
.footer { .footer {
display: block; display: block;
} }
@@ -667,12 +708,10 @@ body.menu-open .overlay * {
animation: none; animation: none;
} }
* { *,
*::after,
*::before {
transition: none !important; transition: none !important;
transform: none !important;
} }
} }
/* Typing sequence: outputs keep their space while hidden, so nothing shifts. */
.is-typing-hidden {
visibility: hidden;
}
+161 -64
View File
@@ -8,54 +8,62 @@
const context = canvas.getContext("2d", { alpha: true }); const context = canvas.getContext("2d", { alpha: true });
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
const CHARS = "·.:-=+*#"; const CELL_WIDTH = 13;
const CELL_WIDTH = 14; const CELL_HEIGHT = 17;
const CELL_HEIGHT = 19; const ASPECT = CELL_WIDTH / CELL_HEIGHT;
const FRAME_MS = 1000 / 18; const FRAME_MS = 1000 / 24;
const BASE_DENSITY = 0.09;
const WAVE_DENSITY = 0.24; // Ambient dither behind everything.
const DUST = "·.:-=+*";
const DUST_BASE = 0.05;
const DUST_WAVE = 0.16;
// Lit torus, shaded the way donut.c does it: a luminance ramp from a dot to a
// solid block, with a z-buffer so the near face hides the far one.
const RAMP = ".,-~:;=!*#$@";
const R1 = 1;
const R2 = 2;
const K2 = 5;
const THETA_STEPS = 120;
const PHI_STEPS = 360;
let columns = 0; let columns = 0;
let rows = 0; let rows = 0;
let dither = new Float32Array(0); let dither = new Float32Array(0);
let baseColor = "rgba(255, 255, 255, 1)"; let depth = new Float32Array(0);
let accentColor = "rgba(235, 82, 52, 1)"; let shade = new Int8Array(0);
const thetaCos = new Float32Array(THETA_STEPS);
const thetaSin = new Float32Array(THETA_STEPS);
const phiCos = new Float32Array(PHI_STEPS);
const phiSin = new Float32Array(PHI_STEPS);
for (let i = 0; i < THETA_STEPS; i += 1) {
const theta = (i / THETA_STEPS) * Math.PI * 2;
thetaCos[i] = Math.cos(theta);
thetaSin[i] = Math.sin(theta);
}
for (let i = 0; i < PHI_STEPS; i += 1) {
const phi = (i / PHI_STEPS) * Math.PI * 2;
phiCos[i] = Math.cos(phi);
phiSin[i] = Math.sin(phi);
}
let baseColor = "#8e97a8";
let accentColor = "#eb5234";
let dimColor = "#5d6472";
let time = 0; let time = 0;
let angleA = 0.6;
let angleB = 0.9;
let lastFrame = 0; let lastFrame = 0;
let rafId = 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 readThemeColors = () => {
const styles = getComputedStyle(document.body); const styles = getComputedStyle(document.body);
baseColor = styles.getPropertyValue("--ascii-base").trim() || baseColor; baseColor = styles.getPropertyValue("--ascii-base").trim() || baseColor;
accentColor = styles.getPropertyValue("--ascii-accent").trim() || accentColor; accentColor = styles.getPropertyValue("--ascii-accent").trim() || accentColor;
dimColor = styles.getPropertyValue("--ascii-dim").trim() || dimColor;
}; };
const resize = () => { const resize = () => {
@@ -74,38 +82,131 @@
columns = Math.ceil(width / CELL_WIDTH); columns = Math.ceil(width / CELL_WIDTH);
rows = Math.ceil(height / CELL_HEIGHT); rows = Math.ceil(height / CELL_HEIGHT);
buildDither();
};
const draw = () => { const cells = columns * rows;
const width = canvas.width; dither = new Float32Array(cells);
const height = canvas.height; depth = new Float32Array(cells);
shade = new Int8Array(cells);
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 row = 0; row < rows; row += 1) {
for (let column = 0; column < columns; column += 1) { for (let column = 0; column < columns; column += 1) {
const level = waveAt(column, row, time); const noise = Math.sin(column * 127.1 + row * 311.7) * 43758.5453;
dither[row * columns + column] = noise - Math.floor(noise);
}
}
};
if (dither[row * columns + column] > BASE_DENSITY + level * WAVE_DENSITY) { // Slow interfering swells that move brightness through the dust layer.
const swellAt = (x, y, t) => {
const radius = Math.sqrt(((x - columns / 2) * 0.55) ** 2 + (y - rows / 2) ** 2);
return (
(Math.sin(x * 0.068 + t * 0.55) +
Math.sin(y * 0.1 - t * 0.42) +
Math.sin((x * 0.4 + y * 0.6) * 0.07 + t * 0.33) +
Math.sin(radius * 0.1 - t * 0.6)) /
8 +
0.5
);
};
const projectTorus = () => {
depth.fill(0);
shade.fill(-1);
const sinA = Math.sin(angleA);
const cosA = Math.cos(angleA);
const sinB = Math.sin(angleB);
const cosB = Math.cos(angleB);
const centerX = columns / 2;
const centerY = rows / 2;
// Size the torus against the shorter axis so it always fits on screen.
const span = Math.min(rows, columns * ASPECT) * 0.6;
const K1 = (span * K2) / (R1 + R2);
for (let i = 0; i < THETA_STEPS; i += 1) {
const ct = thetaCos[i];
const st = thetaSin[i];
const circleX = R2 + R1 * ct;
const circleY = R1 * st;
for (let j = 0; j < PHI_STEPS; j += 1) {
const cp = phiCos[j];
const sp = phiSin[j];
const x = circleX * (cosB * cp + sinA * sinB * sp) - circleY * cosA * sinB;
const y = circleX * (sinB * cp - sinA * cosB * sp) + circleY * cosA * cosB;
const z = K2 + cosA * circleX * sp + circleY * sinA;
const ooz = 1 / z;
const screenX = Math.round(centerX + K1 * ooz * x);
const screenY = Math.round(centerY - K1 * ooz * y * ASPECT);
if (screenX < 0 || screenX >= columns || screenY < 0 || screenY >= rows) {
continue; continue;
} }
const glyph = CHARS[Math.min(CHARS.length - 1, Math.floor(level * CHARS.length))]; const luminance =
const color = level > 0.9 ? accentColor : baseColor; cp * ct * sinB -
cosA * ct * sp -
sinA * st +
cosB * (cosA * st - ct * sinA * sp);
if (color !== currentColor) { if (luminance <= 0) {
context.fillStyle = color; continue;
currentColor = color;
} }
context.globalAlpha = 0.06 + level * 0.5; const index = screenY * columns + screenX;
context.fillText(glyph, column * CELL_WIDTH, row * CELL_HEIGHT);
if (ooz > depth[index]) {
depth[index] = ooz;
shade[index] = Math.min(RAMP.length - 1, Math.floor(luminance * 8));
}
}
}
};
const draw = () => {
context.save();
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
context.restore();
projectTorus();
let currentColor = null;
const setColor = (color) => {
if (color !== currentColor) {
context.fillStyle = color;
currentColor = color;
}
};
for (let row = 0; row < rows; row += 1) {
for (let column = 0; column < columns; column += 1) {
const index = row * columns + column;
const lit = shade[index];
const x = column * CELL_WIDTH;
const y = row * CELL_HEIGHT;
if (lit >= 0) {
const level = lit / (RAMP.length - 1);
setColor(level > 0.72 ? accentColor : baseColor);
context.globalAlpha = 0.22 + level * 0.62;
context.fillText(RAMP[lit], x, y);
continue;
}
const swell = swellAt(column, row, time);
if (dither[index] > DUST_BASE + swell * DUST_WAVE) {
continue;
}
setColor(dimColor);
context.globalAlpha = 0.1 + swell * 0.4;
context.fillText(DUST[Math.min(DUST.length - 1, Math.floor(swell * DUST.length))], x, y);
} }
} }
@@ -120,7 +221,9 @@
} }
lastFrame = timestamp; lastFrame = timestamp;
time += 0.021; time += 0.016;
angleA += 0.0115;
angleB += 0.0062;
draw(); draw();
}; };
@@ -155,12 +258,6 @@
resizeTimer = window.setTimeout(reset, 150); 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", () => { document.addEventListener("visibilitychange", () => {
if (document.hidden) { if (document.hidden) {
stop(); stop();
+1 -17
View File
@@ -147,30 +147,15 @@
return `<div class="${className.replace("card-link", "card-links")}">${links.join("")}</div>`; return `<div class="${className.replace("card-link", "card-links")}">${links.join("")}</div>`;
}; };
const statusState = (entry) => {
const status = String(entry.status || "").toLowerCase();
if (status.startsWith("active")) {
return "active";
}
if (entry.type === "updates") {
return "attention";
}
return "idle";
};
const createCard = (entry) => { const createCard = (entry) => {
const featuredClass = entry.featured ? " featured" : ""; const featuredClass = entry.featured ? " featured" : "";
const anchorId = entry.slug ? ` id="${escapeHtml(entry.slug)}"` : ""; const anchorId = entry.slug ? ` id="${escapeHtml(entry.slug)}"` : "";
return ` return `
<article class="content-card${featuredClass}"${anchorId}> <article class="content-card surface${featuredClass}"${anchorId}>
<div class="card-meta"> <div class="card-meta">
<span class="card-kicker">${escapeHtml(entry.label || entry.type)}</span> <span class="card-kicker">${escapeHtml(entry.label || entry.type)}</span>
${entry.date ? `<span class="card-date">${escapeHtml(entry.date)}</span>` : ""} ${entry.date ? `<span class="card-date">${escapeHtml(entry.date)}</span>` : ""}
${entry.status ? `<span class="card-status" data-state="${statusState(entry)}">${escapeHtml(entry.status.toLowerCase())}</span>` : ""}
</div> </div>
<h3>${escapeHtml(entry.title)}</h3> <h3>${escapeHtml(entry.title)}</h3>
<p class="card-summary">${escapeHtml(entry.summary || "")}</p> <p class="card-summary">${escapeHtml(entry.summary || "")}</p>
@@ -336,7 +321,6 @@
<div class="entry-meta"> <div class="entry-meta">
<span class="entry-pill entry-pill-primary">${escapeHtml(entry.label || entry.type)}</span> <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>` : ""} ${entry.date ? `<span class="entry-pill entry-pill-secondary">${escapeHtml(formatDate(entry.date))}</span>` : ""}
${entry.status ? `<span class="entry-pill entry-pill-secondary">${escapeHtml(entry.status)}</span>` : ""}
</div> </div>
<h1 class="entry-title">${escapeHtml(entry.title)}</h1> <h1 class="entry-title">${escapeHtml(entry.title)}</h1>
<p class="entry-summary">${escapeHtml(entry.summary || "")}</p> <p class="entry-summary">${escapeHtml(entry.summary || "")}</p>
-31
View File
@@ -1,31 +0,0 @@
(function() {
const body = document.body;
const themeToggle = document.getElementById("theme-toggle");
const applyStoredTheme = () => {
const storedTheme = localStorage.getItem("pref-theme") || sessionStorage.getItem("theme") || "dark";
if (storedTheme === "light") {
body.classList.add("light");
sessionStorage.setItem("theme", "light");
localStorage.setItem("pref-theme", "light");
return;
}
body.classList.remove("light");
sessionStorage.setItem("theme", "dark");
localStorage.setItem("pref-theme", "dark");
};
applyStoredTheme();
if (themeToggle) {
themeToggle.addEventListener("click", () => {
const isLight = body.classList.toggle("light");
const nextTheme = isLight ? "light" : "dark";
sessionStorage.setItem("theme", nextTheme);
localStorage.setItem("pref-theme", nextTheme);
});
}
body.classList.remove("js-stop-transition");
})();