rework the landing layout, revert the torus and heavy glass
The torus backdrop and film-grain glass were too much, so this goes back to the quieter dithered field and the lighter frosted surfaces, keeping the light-theme and status-chip removals from the previous commit. The hero now lays out as a three-part grid: the photo pinned to the top left, the contact buttons directly under it, and the text column beside them, so the left column carries weight instead of leaving ragged space. The contact buttons lose their labels and become 48px icon squares that line up exactly with the width of the photo. On narrow screens the photo drops out and the contact row moves below the text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7078cda6c5
commit
91b7960dc4
4 files changed
+105
-234
No files matched your search
+4
-8
@@ -158,9 +158,7 @@
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<div class="term__stream">
|
||||
<p class="cmd"><span class="cmd__text">whoami</span><span class="caret"></span></p>
|
||||
@@ -184,34 +182,32 @@
|
||||
<a class="button button-secondary" href="./about.html">About</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="social-icons hero-social">
|
||||
<a class="social-link" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
|
||||
title="LinkedIn">
|
||||
title="LinkedIn" aria-label="LinkedIn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
|
||||
viewBox="0 0 16 16">
|
||||
<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" />
|
||||
</svg>
|
||||
<span>LinkedIn</span>
|
||||
</a>
|
||||
<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">
|
||||
<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" />
|
||||
</svg>
|
||||
<span>Gitea</span>
|
||||
</a>
|
||||
<a class="social-link" 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" aria-label="Email">
|
||||
<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">
|
||||
<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" />
|
||||
</svg>
|
||||
<span>Email</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -5,28 +5,10 @@
|
||||
|
||||
/* The page header sits on the background; only cards get a surface. */
|
||||
.projects-hero {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
margin-bottom: 40px;
|
||||
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,
|
||||
.entry-title {
|
||||
font-size: clamp(1.5rem, 3vw, 2.1rem);
|
||||
|
||||
+45
-49
@@ -2,8 +2,8 @@
|
||||
|
||||
:root {
|
||||
--theme: #0b0c0f;
|
||||
--surface: rgba(20, 22, 27, 0.34);
|
||||
--surface-raised: rgba(28, 31, 38, 0.46);
|
||||
--surface: rgba(21, 23, 28, 0.58);
|
||||
--surface-raised: rgba(28, 31, 37, 0.62);
|
||||
--bar: rgba(255, 255, 255, 0.04);
|
||||
--primary: #ccd0d9;
|
||||
--bright: #f2f4f8;
|
||||
@@ -13,7 +13,7 @@
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
--border-strong: rgba(255, 255, 255, 0.2);
|
||||
--glow: rgba(235, 82, 52, 0.16);
|
||||
--shadow: 0 20px 54px rgba(0, 0, 0, 0.4);
|
||||
--shadow: 0 18px 50px rgba(0, 0, 0, 0.34);
|
||||
--selection: rgba(235, 82, 52, 0.3);
|
||||
--ascii-base: #99a2b4;
|
||||
--ascii-accent: #eb5234;
|
||||
@@ -23,9 +23,6 @@
|
||||
--mono:
|
||||
"JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
|
||||
Consolas, "Liberation Mono", monospace;
|
||||
|
||||
/* Fine film grain that sits on every frosted surface. */
|
||||
--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");
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -78,11 +75,7 @@ body::before {
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
body[data-page="home"] #ascii-bg {
|
||||
opacity: 0.34;
|
||||
opacity: 0.22;
|
||||
}
|
||||
|
||||
header,
|
||||
@@ -96,27 +89,11 @@ main,
|
||||
|
||||
.surface {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
background: var(--surface);
|
||||
backdrop-filter: blur(26px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(26px) saturate(150%);
|
||||
backdrop-filter: blur(18px) saturate(135%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(135%);
|
||||
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;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
a,
|
||||
@@ -442,17 +419,25 @@ body.menu-open .overlay * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Photo and contact row stack in a left column, both pinned to the top,
|
||||
with the text column beside them. */
|
||||
.term__body {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 30px;
|
||||
padding: 32px 30px;
|
||||
align-items: center;
|
||||
grid-template-columns: 164px 1fr;
|
||||
grid-template-areas:
|
||||
"photo stream"
|
||||
"contact stream";
|
||||
grid-template-rows: auto auto;
|
||||
align-content: start;
|
||||
column-gap: 30px;
|
||||
row-gap: 16px;
|
||||
padding: 32px 30px 34px;
|
||||
}
|
||||
|
||||
.term__avatar {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
grid-area: photo;
|
||||
width: 164px;
|
||||
height: 164px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border-strong);
|
||||
@@ -462,11 +447,12 @@ body.menu-open .overlay * {
|
||||
}
|
||||
|
||||
.term__stream {
|
||||
grid-area: stream;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cmd {
|
||||
margin: 0 0 18px;
|
||||
margin: 0 0 16px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -592,19 +578,21 @@ body.menu-open .overlay * {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* The landing page gets labelled contact cards instead of bare icons. */
|
||||
/* The landing page gets large icon buttons for contact. */
|
||||
.hero-social {
|
||||
gap: 12px;
|
||||
grid-area: contact;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 28px;
|
||||
margin-top: 0;
|
||||
width: 164px;
|
||||
}
|
||||
|
||||
.hero-social .social-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13.5px;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: var(--primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
@@ -612,8 +600,8 @@ body.menu-open .overlay * {
|
||||
}
|
||||
|
||||
.hero-social .social-link svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
color: var(--muted);
|
||||
transition:
|
||||
color 0.2s var(--ease),
|
||||
@@ -666,7 +654,7 @@ body.menu-open .overlay * {
|
||||
|
||||
@media only screen and (max-width: 860px) {
|
||||
#menu,
|
||||
.term__avatar-wrap {
|
||||
.term__avatar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -682,8 +670,15 @@ body.menu-open .overlay * {
|
||||
|
||||
.term__body {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
padding: 26px 20px;
|
||||
grid-template-areas:
|
||||
"stream"
|
||||
"contact";
|
||||
row-gap: 24px;
|
||||
padding: 26px 20px 28px;
|
||||
}
|
||||
|
||||
.hero-social {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
@@ -691,7 +686,8 @@ body.menu-open .overlay * {
|
||||
}
|
||||
|
||||
.hero-social .social-link {
|
||||
padding: 9px 14px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
+56
-159
@@ -8,62 +8,54 @@
|
||||
const context = canvas.getContext("2d", { alpha: true });
|
||||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
|
||||
const CELL_WIDTH = 13;
|
||||
const CELL_HEIGHT = 17;
|
||||
const ASPECT = CELL_WIDTH / CELL_HEIGHT;
|
||||
const FRAME_MS = 1000 / 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;
|
||||
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 depth = new Float32Array(0);
|
||||
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 baseColor = "rgba(255, 255, 255, 1)";
|
||||
let accentColor = "rgba(235, 82, 52, 1)";
|
||||
let time = 0;
|
||||
let angleA = 0.6;
|
||||
let angleB = 0.9;
|
||||
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;
|
||||
dimColor = styles.getPropertyValue("--ascii-dim").trim() || dimColor;
|
||||
};
|
||||
|
||||
const resize = () => {
|
||||
@@ -82,131 +74,38 @@
|
||||
|
||||
columns = Math.ceil(width / CELL_WIDTH);
|
||||
rows = Math.ceil(height / CELL_HEIGHT);
|
||||
|
||||
const cells = columns * rows;
|
||||
dither = new Float32Array(cells);
|
||||
depth = new Float32Array(cells);
|
||||
shade = new Int8Array(cells);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const luminance =
|
||||
cp * ct * sinB -
|
||||
cosA * ct * sp -
|
||||
sinA * st +
|
||||
cosB * (cosA * st - ct * sinA * sp);
|
||||
|
||||
if (luminance <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = screenY * columns + screenX;
|
||||
|
||||
if (ooz > depth[index]) {
|
||||
depth[index] = ooz;
|
||||
shade[index] = Math.min(RAMP.length - 1, Math.floor(luminance * 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
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, canvas.width, canvas.height);
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.restore();
|
||||
|
||||
projectTorus();
|
||||
|
||||
let currentColor = null;
|
||||
const setColor = (color) => {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
context.globalAlpha = 0.06 + level * 0.5;
|
||||
context.fillText(glyph, column * CELL_WIDTH, row * CELL_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +120,7 @@
|
||||
}
|
||||
|
||||
lastFrame = timestamp;
|
||||
time += 0.016;
|
||||
angleA += 0.0115;
|
||||
angleB += 0.0062;
|
||||
time += 0.021;
|
||||
draw();
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user