Pages Hard
Game Developer Portfolio
Retro pixel art game developer portfolio with CRT scanlines, canvas starfield + animated pixel character, HUD-style stat bars, game cartridge cards, and inventory skill items.
Open in Lab
MCP
canvas-2d gsap lenis view-transitions-api css-animation
Targets: JS HTML
Code
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
35-gamedev-portfolio โ styles.css
Retro pixel art aesthetic, CRT scanlines, HUD elements
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
/* โโ Google Font: Press Start 2P โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@import url("https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap");
/* โโ Custom Properties โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
:root {
--bg: #0f0f1a;
--text: #e8e8f0;
--accent: #ffdd57;
--accent-2: #ff6b9d;
--accent-3: #4ecdc4;
--accent-4: #ff4757;
--muted: #4a4a6a;
--border: #1e1e3a;
--panel: #141428;
--panel-2: #0c0c1e;
--scanline: rgba(0, 0, 0, 0.15);
--pixel-font: "Press Start 2P", monospace;
--hero-font: Impact, "Arial Black", "Haettenschweiler", sans-serif;
}
/* โโ Reset & Base โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
font-size: 16px;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--pixel-font);
font-size: 8px;
line-height: 1.8;
overflow-x: hidden;
}
/* โโ CRT Scanlines โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
body::after {
content: "";
position: fixed;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 2px,
rgba(0, 0, 0, 0.15) 2px,
rgba(0, 0, 0, 0.15) 4px
);
pointer-events: none;
z-index: 9999;
animation: crtFlicker 8s steps(1) infinite;
}
/* Subtle screen vignette */
body::before {
content: "";
position: fixed;
inset: 0;
background: radial-gradient(ellipse at center, transparent 60%, rgba(0, 0, 0, 0.6) 100%);
pointer-events: none;
z-index: 9998;
}
@keyframes crtFlicker {
0%,
97%,
100% {
opacity: 1;
}
98% {
opacity: 0.97;
}
99% {
opacity: 0.99;
}
}
/* โโ Blink โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.blink {
animation: blink 1s steps(1) infinite;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* โโ Loading Overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.loading-overlay {
position: fixed;
inset: 0;
background: var(--bg);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.4s ease;
}
.loading-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.loading-inner {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.loading-logo {
font-family: var(--pixel-font);
font-size: 18px;
color: var(--accent);
text-shadow: 3px 3px 0 rgba(255, 221, 87, 0.3);
animation: pulse 1s ease-in-out infinite alternate;
}
.loading-text {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--text);
letter-spacing: 0.2em;
}
.loading-dots::after {
content: "";
animation: dots 1.5s steps(4) infinite;
}
@keyframes dots {
0% {
content: "";
}
25% {
content: ".";
}
50% {
content: "..";
}
75% {
content: "...";
}
}
.loading-bar-wrap {
width: 240px;
height: 16px;
border: 3px solid var(--accent);
background: var(--panel-2);
image-rendering: pixelated;
}
.loading-bar {
height: 100%;
width: 0%;
background: var(--accent);
transition: width 0.05s steps(1);
box-shadow: 0 0 8px var(--accent);
}
.loading-pct {
font-family: var(--pixel-font);
font-size: 8px;
color: var(--accent);
}
/* โโ Pixel Nav โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.pixel-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: rgba(15, 15, 26, 0.92);
border-bottom: 2px solid var(--border);
backdrop-filter: blur(4px);
transform: translateY(-100%);
transition: transform 0.3s steps(6);
}
.pixel-nav.visible {
transform: translateY(0);
}
.nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 24px;
max-width: 1100px;
margin: 0 auto;
}
.nav-logo {
font-family: var(--pixel-font);
font-size: 12px;
color: var(--accent);
text-shadow: 2px 2px 0 rgba(255, 221, 87, 0.4);
letter-spacing: 0.1em;
}
.nav-links {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.nav-link {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--muted);
text-decoration: none;
letter-spacing: 0.1em;
transition: color 0.1s, text-shadow 0.1s;
padding: 4px 6px;
}
.nav-link:hover,
.nav-link.active {
color: var(--accent);
text-shadow: 0 0 8px var(--accent);
}
/* โโ Hero โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.hero {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.hero-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
/* HUD corners */
.hud-corner {
position: absolute;
font-family: var(--pixel-font);
font-size: 7px;
z-index: 10;
}
.hud-tl {
top: 80px;
left: 24px;
}
.hud-tr {
top: 80px;
right: 24px;
text-align: right;
}
.hud-hp,
.hud-mp {
margin-bottom: 10px;
}
.hud-label {
color: var(--accent-4);
display: inline-block;
width: 24px;
margin-right: 6px;
}
.hud-bar {
display: inline-block;
width: 100px;
height: 8px;
background: var(--border);
border: 1px solid var(--muted);
vertical-align: middle;
margin-right: 6px;
}
.hud-bar-fill {
height: 100%;
animation: hudPulse 2s ease-in-out infinite alternate;
}
.hud-bar-hp {
width: 100%;
background: var(--accent-4);
}
.hud-bar-mp {
width: 88%;
background: var(--accent-3);
}
@keyframes hudPulse {
from {
filter: brightness(1);
}
to {
filter: brightness(1.3);
}
}
.hud-val {
color: var(--text);
}
.hud-xp-label {
color: var(--accent);
margin-bottom: 4px;
}
.hud-xp-bar-wrap {
width: 120px;
height: 8px;
background: var(--border);
border: 1px solid var(--muted);
margin-bottom: 4px;
}
.hud-xp-bar {
width: 73%;
height: 100%;
background: var(--accent);
box-shadow: 0 0 6px var(--accent);
}
.hud-level {
color: var(--accent);
}
.hud-level-num {
color: var(--accent-2);
}
/* Hero content */
.hero-content {
position: relative;
z-index: 10;
text-align: center;
padding: 24px;
}
.hero-eyebrow {
font-family: var(--pixel-font);
font-size: 9px;
color: var(--accent-3);
letter-spacing: 0.3em;
margin-bottom: 24px;
text-shadow: 0 0 12px var(--accent-3);
}
.hero-name {
font-family: var(--hero-font);
font-size: clamp(3.5rem, 10vw, 8rem);
font-weight: 900;
font-style: italic;
letter-spacing: 0.05em;
color: var(--accent);
/* Chunky pixel shadow simulation */
text-shadow: 3px 3px 0 #b89c00, 6px 6px 0 rgba(184, 156, 0, 0.5), 0 0 40px rgba(255, 221, 87, 0.3);
line-height: 0.95;
margin-bottom: 20px;
text-transform: uppercase;
}
.hero-subtitle {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--text);
letter-spacing: 0.25em;
margin-bottom: 28px;
opacity: 0.85;
}
.hero-tags {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 60px;
}
.hero-tag {
font-family: var(--pixel-font);
font-size: 7px;
padding: 6px 10px;
border: 2px solid var(--accent-2);
color: var(--accent-2);
letter-spacing: 0.1em;
background: rgba(255, 107, 157, 0.08);
}
.hero-scroll-cue {
font-family: var(--pixel-font);
font-size: 8px;
color: var(--accent);
letter-spacing: 0.1em;
}
/* Score display */
.hud-score {
position: absolute;
bottom: 24px;
right: 24px;
font-family: var(--pixel-font);
font-size: 8px;
text-align: right;
z-index: 10;
}
.score-label {
color: var(--muted);
margin-bottom: 4px;
}
.score-val {
font-size: 14px;
color: var(--accent);
text-shadow: 0 0 10px var(--accent);
letter-spacing: 0.1em;
}
/* โโ Sections (shared) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.section {
padding: 100px 24px;
position: relative;
}
.section-inner {
max-width: 960px;
margin: 0 auto;
}
.section-header-pixel {
display: flex;
align-items: baseline;
gap: 16px;
margin-bottom: 56px;
padding-bottom: 20px;
border-bottom: 3px solid var(--border);
flex-wrap: wrap;
}
.section-num {
font-family: var(--pixel-font);
font-size: 9px;
color: var(--accent-2);
}
.section-title {
font-family: var(--pixel-font);
font-size: clamp(14px, 3vw, 20px);
color: var(--text);
text-shadow: 2px 2px 0 rgba(255, 255, 255, 0.1);
}
.section-deco {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--muted);
margin-left: auto;
}
/* โโ Stats Section โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.stats-section {
background: var(--panel-2);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 48px;
}
.stat-card {
background: var(--panel);
border: 2px solid var(--border);
padding: 20px;
transition: border-color 0.2s, transform 0.1s steps(2);
}
.stat-card:hover {
border-color: var(--accent);
transform: translate(-2px, -2px);
}
.stat-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.stat-abbr {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--accent);
min-width: 36px;
text-shadow: 0 0 8px rgba(255, 221, 87, 0.5);
}
.stat-name {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--text);
flex: 1;
letter-spacing: 0.05em;
}
.stat-num {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--accent-3);
min-width: 28px;
text-align: right;
}
.stat-bar-wrap {
width: 100%;
height: 12px;
background: var(--border);
border: 2px solid var(--muted);
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
.stat-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--accent-3), var(--accent));
transition: none;
position: relative;
}
/* Pixel block pattern on bar fill */
.stat-bar::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(
90deg,
transparent 0px,
transparent 10px,
rgba(0, 0, 0, 0.2) 10px,
rgba(0, 0, 0, 0.2) 12px
);
}
.stat-desc {
font-family: var(--pixel-font);
font-size: 6px;
color: var(--muted);
line-height: 1.9;
}
/* XP progress bar */
.xp-progress {
background: var(--panel);
border: 2px solid var(--border);
padding: 20px;
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.xp-label {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--accent);
min-width: 180px;
}
.xp-bar-outer {
flex: 1;
min-width: 200px;
height: 16px;
background: var(--border);
border: 2px solid var(--muted);
overflow: hidden;
}
.xp-bar-inner {
height: 100%;
width: 0%;
background: var(--accent);
box-shadow: 0 0 8px var(--accent);
position: relative;
}
.xp-bar-inner::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(
90deg,
transparent 0px,
transparent 14px,
rgba(0, 0, 0, 0.25) 14px,
rgba(0, 0, 0, 0.25) 16px
);
}
.xp-pct {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--accent);
min-width: 48px;
text-align: right;
}
/* โโ Games Section โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.games-section {
background: var(--bg);
}
.games-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 32px;
}
.game-card {
display: flex;
flex-direction: column;
border: 3px solid var(--border);
background: var(--panel);
cursor: pointer;
transition: transform 0.1s steps(2), border-color 0.15s;
outline: none;
}
.game-card:hover,
.game-card:focus {
transform: translate(-4px, -4px);
border-color: var(--cart-color, var(--accent));
}
.game-card:hover .cartridge-label,
.game-card:focus .cartridge-label {
filter: brightness(1.1);
}
/* Cartridge label */
.cartridge-label {
position: relative;
padding: 0;
overflow: hidden;
height: 160px;
background: var(--cart-color, var(--accent));
transition: filter 0.15s;
}
.cart-stripe {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 16px;
background: var(--cart-color2, #b89c00);
border-bottom: 3px solid rgba(0, 0, 0, 0.3);
}
.cart-body {
position: absolute;
inset: 16px 0 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 12px;
}
.cart-genre {
font-family: var(--pixel-font);
font-size: 7px;
color: rgba(0, 0, 0, 0.7);
letter-spacing: 0.1em;
margin-bottom: 10px;
}
.cart-title {
font-family: var(--pixel-font);
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
text-shadow: 2px 2px 0 rgba(255, 255, 255, 0.2);
line-height: 1.4;
margin-bottom: 6px;
}
.cart-sub {
font-family: var(--pixel-font);
font-size: 6px;
color: rgba(0, 0, 0, 0.6);
letter-spacing: 0.05em;
}
.cart-platforms {
font-size: 16px;
margin-top: 10px;
}
.cart-bottom {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 24px;
background: var(--cart-color2, #b89c00);
border-top: 3px solid rgba(0, 0, 0, 0.3);
/* Cart connector notches */
clip-path: polygon(0 0, 30% 0, 35% 100%, 65% 100%, 70% 0, 100% 0, 100% 100%, 0 100%);
}
.game-info {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.game-meta-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.game-year,
.game-engine {
font-family: var(--pixel-font);
font-size: 6px;
color: var(--muted);
}
.game-engine::before {
content: "| ";
color: var(--border);
}
.game-rating {
font-size: 10px;
margin-left: auto;
color: var(--accent);
}
.game-desc {
font-family: var(--pixel-font);
font-size: 6px;
color: var(--text);
line-height: 2;
opacity: 0.8;
}
.game-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-top: auto;
}
.gtag {
font-family: var(--pixel-font);
font-size: 5px;
padding: 4px 8px;
border: 1px solid var(--border);
color: var(--muted);
background: var(--panel-2);
letter-spacing: 0.05em;
}
/* โโ Inventory Section โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.tech-section {
background: var(--panel-2);
}
.inventory-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}
.inv-item {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 16px;
background: var(--panel);
border: 2px solid var(--border);
cursor: pointer;
position: relative;
outline: none;
transition: transform 0.1s steps(2), border-color 0.15s;
}
.inv-item:hover,
.inv-item:focus {
transform: translate(-2px, -2px);
}
.inv-item[data-rarity="legendary"] {
border-color: rgba(255, 221, 87, 0.4);
}
.inv-item[data-rarity="rare"] {
border-color: rgba(78, 205, 196, 0.4);
}
.inv-item[data-rarity="common"] {
border-color: var(--border);
}
/* Inventory icons using CSS geometry */
.inv-icon {
width: 40px;
height: 40px;
flex-shrink: 0;
position: relative;
image-rendering: pixelated;
}
/* C++ โ crossed pillars */
.inv-icon--cpp {
background: var(--accent-4);
clip-path: polygon(20% 0%, 80% 0%, 100% 50%, 80% 100%, 20% 100%, 0% 50%);
}
.inv-icon--cpp::after {
content: "++";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 8px;
color: white;
font-weight: bold;
}
/* Unity โ circle */
.inv-icon--unity {
background: var(--text);
border-radius: 50%;
}
.inv-icon--unity::after {
content: "U";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 10px;
color: var(--bg);
font-weight: bold;
}
/* Unreal โ diamond */
.inv-icon--unreal {
background: var(--accent-2);
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
.inv-icon--unreal::after {
content: "UE";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 6px;
color: white;
}
/* GLSL โ triangle */
.inv-icon--glsl {
background: var(--accent-3);
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
/* Godot โ robot head shape */
.inv-icon--godot {
background: var(--accent);
clip-path: polygon(10% 30%, 50% 0%, 90% 30%, 90% 70%, 50% 100%, 10% 70%);
}
.inv-icon--godot::after {
content: "G";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 10px;
color: var(--bg);
}
/* Vulkan โ flame-like */
.inv-icon--vulkan {
background: var(--accent-4);
clip-path: polygon(
50% 0%,
80% 25%,
100% 10%,
80% 60%,
100% 60%,
50% 100%,
0% 60%,
20% 60%,
0% 10%,
20% 25%
);
}
/* Python โ square */
.inv-icon--python {
background: var(--muted);
border: 3px solid var(--border);
}
.inv-icon--python::after {
content: "PY";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 7px;
color: var(--accent-3);
}
/* Blender โ rounded star */
.inv-icon--blender {
background: var(--accent);
clip-path: polygon(
50% 0%,
61% 35%,
98% 35%,
68% 57%,
79% 91%,
50% 70%,
21% 91%,
32% 57%,
2% 35%,
39% 35%
);
}
.inv-icon--blender::after {
content: "";
position: absolute;
inset: 35%;
background: var(--bg);
border-radius: 50%;
}
/* Git โ fork */
.inv-icon--git {
background: var(--accent-4);
border-radius: 4px;
}
.inv-icon--git::after {
content: "GIT";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--pixel-font);
font-size: 6px;
color: white;
}
.inv-details {
flex: 1;
min-width: 0;
}
.inv-name {
font-family: var(--pixel-font);
font-size: 8px;
color: var(--text);
margin-bottom: 6px;
}
.inv-rarity {
font-family: var(--pixel-font);
font-size: 6px;
letter-spacing: 0.05em;
}
.rarity-legendary {
color: var(--accent);
}
.rarity-rare {
color: var(--accent-3);
}
.rarity-common {
color: var(--muted);
}
/* Sparkle icon */
.inv-sparkle {
font-size: 16px;
color: var(--accent);
opacity: 0;
transition: opacity 0.15s;
}
.inv-item:hover .inv-sparkle,
.inv-item:focus .inv-sparkle {
opacity: 1;
animation: sparkle 0.6s steps(2) infinite;
}
@keyframes sparkle {
0%,
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
50% {
transform: scale(1.3) rotate(20deg);
opacity: 0.7;
}
}
/* Legendary glow on hover */
.inv-item[data-rarity="legendary"]:hover,
.inv-item[data-rarity="legendary"]:focus {
box-shadow: 0 0 12px rgba(255, 221, 87, 0.25), inset 0 0 12px rgba(255, 221, 87, 0.05);
}
.inv-item[data-rarity="rare"]:hover,
.inv-item[data-rarity="rare"]:focus {
box-shadow: 0 0 12px rgba(78, 205, 196, 0.25), inset 0 0 12px rgba(78, 205, 196, 0.05);
}
/* โโ Contact Section โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.contact-section {
background: var(--bg);
}
.contact-screen {
max-width: 600px;
margin: 0 auto;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.contact-title {
font-family: var(--pixel-font);
font-size: clamp(18px, 4vw, 28px);
color: var(--accent-4);
text-shadow: 3px 3px 0 rgba(255, 71, 87, 0.5);
letter-spacing: 0.15em;
}
.contact-sub {
font-family: var(--pixel-font);
font-size: clamp(12px, 2.5vw, 18px);
color: var(--accent-3);
letter-spacing: 0.2em;
text-shadow: 2px 2px 0 rgba(78, 205, 196, 0.4);
margin-top: -20px;
}
.contact-prompt {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.contact-question {
font-family: var(--pixel-font);
font-size: 10px;
color: var(--text);
letter-spacing: 0.2em;
}
.contact-buttons {
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
/* Pixel buttons */
.pixel-btn {
font-family: var(--pixel-font);
font-size: 8px;
padding: 14px 20px;
border: 3px solid;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
letter-spacing: 0.1em;
background: transparent;
transition: transform 0.1s steps(2), box-shadow 0.1s;
position: relative;
}
.pixel-btn::after {
content: "";
position: absolute;
right: -6px;
bottom: -6px;
width: 100%;
height: 100%;
border: 3px solid;
border-color: inherit;
opacity: 0.3;
z-index: -1;
transition: right 0.1s steps(2), bottom 0.1s steps(2);
}
.pixel-btn:hover {
transform: translate(-2px, -2px);
}
.pixel-btn:hover::after {
right: -4px;
bottom: -4px;
}
.pixel-btn:active {
transform: translate(0, 0);
}
.pixel-btn:active::after {
right: -6px;
bottom: -6px;
}
.pixel-btn--yes {
color: var(--accent-3);
border-color: var(--accent-3);
}
.pixel-btn--yes:hover {
background: rgba(78, 205, 196, 0.1);
box-shadow: 0 0 16px rgba(78, 205, 196, 0.25);
}
.pixel-btn--no {
color: var(--accent-4);
border-color: var(--accent-4);
}
.pixel-btn--no:hover {
background: rgba(255, 71, 87, 0.1);
box-shadow: 0 0 16px rgba(255, 71, 87, 0.25);
}
.btn-cursor {
animation: blink 0.8s steps(1) infinite;
}
/* Contact links */
.contact-links {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
max-width: 360px;
}
.contact-link {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border: 2px solid var(--border);
color: var(--text);
text-decoration: none;
transition: border-color 0.15s, transform 0.1s steps(2);
}
.contact-link:hover {
border-color: var(--accent);
transform: translate(-2px, -2px);
}
.contact-link-icon {
color: var(--accent);
font-size: 10px;
}
.contact-link-label {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--muted);
min-width: 80px;
}
.contact-link-val {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--accent-3);
}
/* Email CTA */
.contact-email {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.contact-email-label {
font-family: var(--pixel-font);
font-size: 7px;
color: var(--muted);
letter-spacing: 0.1em;
}
.contact-email-addr {
font-family: var(--pixel-font);
font-size: 9px;
color: var(--accent);
text-decoration: none;
text-shadow: 0 0 10px rgba(255, 221, 87, 0.5);
letter-spacing: 0.05em;
}
/* Credits */
.credits {
border-top: 2px solid var(--border);
padding-top: 24px;
width: 100%;
text-align: center;
display: flex;
flex-direction: column;
gap: 8px;
}
.credits-line {
font-family: var(--pixel-font);
font-size: 6px;
color: var(--muted);
letter-spacing: 0.1em;
}
.credits-line.muted {
color: var(--border);
}
/* โโ View Transition overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.vt-overlay {
position: fixed;
inset: 0;
background: var(--bg);
z-index: 5000;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s steps(3);
}
.vt-overlay.active {
opacity: 1;
}
.vt-text {
font-family: var(--pixel-font);
font-size: 14px;
color: var(--accent);
letter-spacing: 0.2em;
text-shadow: 0 0 16px rgba(255, 221, 87, 0.6);
}
/* โโ View Transition API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@keyframes vt-slide-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes vt-slide-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
::view-transition-old(root) {
animation: vt-slide-out 0.2s steps(4) both;
}
::view-transition-new(root) {
animation: vt-slide-in 0.2s steps(4) 0.2s both;
}
/* โโ Pulse animation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@keyframes pulse {
from {
opacity: 0.7;
transform: scale(0.98);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* โโ Reduced motion overrides โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.blink {
animation: none;
}
body::after {
animation: none;
}
.pixel-btn {
transition: none;
}
.game-card,
.inv-item,
.stat-card {
transition: none;
}
}
/* โโ Responsive โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@media (max-width: 600px) {
.hud-corner {
display: none;
}
.hud-score {
display: none;
}
.nav-links {
gap: 10px;
}
.nav-link {
font-size: 6px;
}
.section {
padding: 64px 16px;
}
.section-title {
font-size: 11px;
}
.hero-eyebrow {
font-size: 7px;
}
.contact-title {
font-size: 14px;
}
.contact-sub {
font-size: 11px;
}
.games-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: 1fr;
}
.inventory-grid {
grid-template-columns: 1fr;
}
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
/**
* 35-gamedev-portfolio โ main.js
* Retro pixel art game developer portfolio
* Canvas starfield + pixel character, GSAP, Lenis, View Transitions
*/
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import Lenis from "lenis";
gsap.registerPlugin(ScrollTrigger);
// โโโ Init demo shell โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
initDemoShell({
title: "Game Developer Portfolio",
category: "pages",
tech: ["canvas-2d", "gsap", "lenis", "view-transitions-api", "css-animation"],
});
// โโโ Reduced motion โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let reducedMotion = prefersReducedMotion();
window.addEventListener("motion-preference", (e) => {
reducedMotion = e.detail?.reduced ?? prefersReducedMotion();
});
// โโโ Loading Screen โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const overlay = document.getElementById("loadingOverlay");
const bar = document.getElementById("loadingBar");
const pct = document.getElementById("loadingPct");
function runLoadingScreen() {
return new Promise((resolve) => {
let progress = 0;
const steps = [12, 8, 15, 5, 20, 10, 18, 12];
let stepIndex = 0;
function tick() {
if (stepIndex >= steps.length) {
bar.style.width = "100%";
pct.textContent = "100%";
setTimeout(() => {
overlay.classList.add("hidden");
setTimeout(resolve, 400);
}, 300);
return;
}
progress = Math.min(100, progress + steps[stepIndex]);
bar.style.width = progress + "%";
pct.textContent = progress + "%";
stepIndex++;
setTimeout(tick, 80 + Math.random() * 120);
}
tick();
});
}
// โโโ Lenis smooth scroll โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const lenis = new Lenis({ lerp: 0.08, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
// โโโ Canvas setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const canvas = document.getElementById("heroCanvas");
const ctx = canvas.getContext("2d");
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
// โโโ Starfield โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const stars = Array.from({ length: 150 }, () => ({
x: Math.random(), // normalized 0-1
y: Math.random(),
size: Math.random() * 2 + 1, // 1-3px
twinkle: Math.random() * Math.PI * 2,
speed: 0.02 + Math.random() * 0.04,
color: Math.random() < 0.1 ? (Math.random() < 0.5 ? "#ff6b9d" : "#4ecdc4") : "#ffffff",
}));
function drawStars(time) {
const w = canvas.width;
const h = canvas.height;
stars.forEach((s) => {
const alpha = 0.4 + Math.sin(s.twinkle + time * s.speed * 30) * 0.4;
ctx.fillStyle =
s.color === "#ffffff"
? `rgba(255, 255, 255, ${alpha})`
: s.color +
Math.round(alpha * 255)
.toString(16)
.padStart(2, "0");
// pixel-crisp: use fillRect for square pixels, no antialiasing
ctx.fillRect(Math.round(s.x * w), Math.round(s.y * h), s.size, s.size);
});
}
// โโโ Pixel Character โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// 8x12 pixel grid; each cell = PIXEL_SCALE px square
const PIXEL_SCALE = 4;
const COLORS = {
0: null, // transparent
1: "#f4c88a", // skin
2: "#3d2314", // hair / dark
3: "#4ecdc4", // body (teal armor)
4: "#ffdd57", // accent (yellow visor / details)
5: "#ff6b9d", // accent-2 (pink trim)
6: "#1a1a3a", // shadow / dark belt
7: "#ffffff", // white
8: "#ff4757", // red detail
};
// 8-frame walk cycle โ 8 columns wide, 12 rows tall
// frame index selects leg pattern (bottom 4 rows vary)
const FRAME_TOPS = [
// rows 0-7: shared upper body (helmet, visor, torso, arms)
[0, 0, 2, 2, 2, 2, 0, 0], // 0 โ helmet top
[0, 2, 4, 4, 4, 4, 2, 0], // 1 โ visor
[2, 2, 4, 4, 4, 4, 2, 2], // 2 โ visor lower / helmet side
[0, 2, 1, 1, 1, 1, 2, 0], // 3 โ face
[0, 0, 1, 1, 1, 1, 0, 0], // 4 โ chin
[5, 3, 3, 3, 3, 3, 3, 5], // 5 โ shoulders
[0, 3, 3, 6, 6, 3, 3, 0], // 6 โ torso / belt
[0, 3, 3, 6, 6, 3, 3, 0], // 7 โ lower torso
];
// Leg frames โ 4 rows, cycled across 8 animation frames
const LEG_FRAMES = [
// frame 0: stand
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 0, 0, 0, 0, 3, 0],
[0, 4, 0, 0, 0, 0, 4, 0],
],
// frame 1: step right fwd
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 0, 3, 3, 0, 3, 0, 0],
[0, 0, 4, 4, 0, 4, 0, 0],
],
// frame 2: step right
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 0, 0, 3, 0, 3, 0, 0],
[0, 0, 0, 4, 0, 4, 0, 0],
],
// frame 3: step right back
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 0, 3, 0, 0, 3, 3, 0],
[0, 0, 4, 0, 0, 4, 4, 0],
],
// frame 4: stand (mirror)
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 0, 0, 0, 0, 3, 0],
[0, 4, 0, 0, 0, 0, 4, 0],
],
// frame 5: step left fwd
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 0, 0, 3, 3, 0, 0],
[0, 4, 0, 0, 4, 4, 0, 0],
],
// frame 6: step left
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 0, 0, 3, 0, 0, 0],
[0, 4, 0, 0, 4, 0, 0, 0],
],
// frame 7: step left back
[
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 3, 3, 0],
[0, 3, 3, 0, 0, 0, 3, 0],
[0, 4, 4, 0, 0, 0, 4, 0],
],
];
function drawPixelChar(cx, cy, frameIndex) {
const ps = PIXEL_SCALE;
const fi = frameIndex % LEG_FRAMES.length;
// upper body (shared)
FRAME_TOPS.forEach((row, ry) => {
row.forEach((colorKey, rx) => {
if (!colorKey) return;
ctx.fillStyle = COLORS[colorKey];
ctx.fillRect(Math.round(cx + rx * ps - 4 * ps), Math.round(cy + ry * ps - 6 * ps), ps, ps);
});
});
// legs (animated)
LEG_FRAMES[fi].forEach((row, ry) => {
row.forEach((colorKey, rx) => {
if (!colorKey) return;
ctx.fillStyle = COLORS[colorKey];
ctx.fillRect(
Math.round(cx + rx * ps - 4 * ps),
Math.round(cy + (ry + 8) * ps - 6 * ps),
ps,
ps
);
});
});
}
// โโโ Animation loop โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let animFrame = 0;
let lastFrameTime = 0;
const FRAME_INTERVAL = 120; // ms per walk frame
let charY = 0; // slight bob
function animate(time) {
if (!canvas.width || !canvas.height) {
requestAnimationFrame(animate);
return;
}
const w = canvas.width;
const h = canvas.height;
// Clear
ctx.clearRect(0, 0, w, h);
// Deep space gradient bg
const grad = ctx.createLinearGradient(0, 0, 0, h);
grad.addColorStop(0, "#0a0a1a");
grad.addColorStop(1, "#0f0f2a");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// Stars
drawStars(time / 1000);
// Subtle ground grid
if (!reducedMotion) {
ctx.strokeStyle = "rgba(78, 205, 196, 0.07)";
ctx.lineWidth = 1;
const gridSize = 40;
const scrollOffset = (time * 0.02) % gridSize;
for (let x = 0; x < w + gridSize; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x - scrollOffset, h * 0.7);
ctx.lineTo(x - scrollOffset, h);
ctx.stroke();
}
for (let y = h * 0.7; y < h; y += gridSize * 0.5) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
}
// Advance walk frame
if (!reducedMotion && time - lastFrameTime > FRAME_INTERVAL) {
animFrame = (animFrame + 1) % 8;
lastFrameTime = time;
}
// Character bob
if (!reducedMotion) {
charY = Math.sin(time / 500) * 3;
}
// Draw character at center, slightly below mid
const charX = w / 2;
const charCy = h * 0.65 + charY;
drawPixelChar(charX, charCy, animFrame);
// Glow under character
const glowGrad = ctx.createRadialGradient(charX, charCy + 20, 0, charX, charCy + 20, 40);
glowGrad.addColorStop(0, "rgba(78, 205, 196, 0.15)");
glowGrad.addColorStop(1, "transparent");
ctx.fillStyle = glowGrad;
ctx.fillRect(charX - 40, charCy, 80, 40);
requestAnimationFrame(animate);
}
// โโโ Score counter โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const scoreEl = document.getElementById("scoreVal");
let score = 0;
function tickScore() {
if (reducedMotion) return;
score += Math.floor(Math.random() * 37 + 1);
if (score > 999999) score = 0;
scoreEl.textContent = String(score).padStart(6, "0");
setTimeout(tickScore, 80 + Math.random() * 140);
}
// โโโ Pixel Nav visibility โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const nav = document.getElementById("pixelNav");
const heroSection = document.getElementById("hero");
ScrollTrigger.create({
trigger: heroSection,
start: "bottom 80%",
onEnter: () => nav.classList.add("visible"),
onLeaveBack: () => nav.classList.remove("visible"),
});
// โโโ Active nav link on scroll โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const sections = ["hero", "stats", "games", "tech", "contact"];
sections.forEach((id) => {
ScrollTrigger.create({
trigger: `#${id}`,
start: "top 50%",
end: "bottom 50%",
onToggle: ({ isActive }) => {
const link = document.querySelector(`.nav-link[data-section="${id}"]`);
if (link) link.classList.toggle("active", isActive);
},
});
});
// โโโ View Transition helper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const vtOverlay = document.getElementById("vtOverlay");
function triggerViewTransition(targetId) {
if (reducedMotion) {
document.getElementById(targetId)?.scrollIntoView({ behavior: "smooth" });
return;
}
const supportsVT = typeof document.startViewTransition === "function";
if (supportsVT) {
document.startViewTransition(() => {
document.getElementById(targetId)?.scrollIntoView({ behavior: "instant" });
});
} else {
// Fallback: manual overlay
vtOverlay.classList.add("active");
setTimeout(() => {
document.getElementById(targetId)?.scrollIntoView({ behavior: "instant" });
setTimeout(() => vtOverlay.classList.remove("active"), 300);
}, 250);
}
}
// Nav link click handler
document.querySelectorAll(".nav-link").forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
const target = link.dataset.section;
triggerViewTransition(target);
});
});
// โโโ Stat bars โ animate on scroll enter โโโโโโโโโโโโโโโโโโโโโโโโโ
function initStatAnimations() {
const statCards = document.querySelectorAll(".stat-card");
statCards.forEach((card) => {
const bar = card.querySelector(".stat-bar");
const numEl = card.querySelector(".stat-num");
const barWidth = parseInt(bar?.dataset.width || "0", 10);
const numTarget = parseInt(numEl?.dataset.target || "0", 10);
ScrollTrigger.create({
trigger: card,
start: "top 80%",
once: true,
onEnter: () => {
if (reducedMotion) {
if (bar) bar.style.width = barWidth + "%";
if (numEl) numEl.textContent = numTarget;
return;
}
// Animate bar
if (bar) {
gsap.to(bar, {
width: barWidth + "%",
duration: 1.2,
ease: "steps(20)",
});
}
// Count up number
if (numEl) {
gsap.to(
{ val: 0 },
{
val: numTarget,
duration: 1.4,
ease: "steps(20)",
onUpdate: function () {
numEl.textContent = Math.round(this.targets()[0].val);
},
}
);
}
},
});
});
// XP bar
const xpBar = document.querySelector(".xp-bar-inner");
const xpNum = document.querySelector(".xp-pct-num");
if (xpBar) {
ScrollTrigger.create({
trigger: xpBar,
start: "top 85%",
once: true,
onEnter: () => {
const target = parseInt(xpBar.dataset.xp || "0", 10);
if (reducedMotion) {
xpBar.style.width = target + "%";
if (xpNum) xpNum.textContent = target;
return;
}
gsap.to(xpBar, { width: target + "%", duration: 1.5, ease: "steps(25)" });
if (xpNum) {
gsap.to(
{ val: 0 },
{
val: target,
duration: 1.5,
ease: "steps(25)",
onUpdate: function () {
xpNum.textContent = Math.round(this.targets()[0].val);
},
}
);
}
},
});
}
}
// โโโ Section reveal animations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function initSectionReveals() {
if (reducedMotion) return;
// Stat cards stagger
gsap.from(".stat-card", {
scrollTrigger: {
trigger: ".stats-grid",
start: "top 85%",
},
y: 20,
opacity: 0,
duration: 0.4,
ease: "steps(4)",
stagger: 0.08,
});
// Game cards
gsap.from(".game-card", {
scrollTrigger: {
trigger: ".games-grid",
start: "top 85%",
},
y: 30,
opacity: 0,
duration: 0.5,
ease: "steps(5)",
stagger: 0.12,
});
// Inventory items
gsap.from(".inv-item", {
scrollTrigger: {
trigger: ".inventory-grid",
start: "top 85%",
},
scale: 0.9,
opacity: 0,
duration: 0.3,
ease: "steps(3)",
stagger: 0.06,
});
// Contact section
gsap.from(".contact-screen > *", {
scrollTrigger: {
trigger: ".contact-screen",
start: "top 85%",
},
y: 16,
opacity: 0,
duration: 0.4,
ease: "steps(4)",
stagger: 0.1,
});
// Section headers
document.querySelectorAll(".section-header-pixel").forEach((header) => {
gsap.from(header, {
scrollTrigger: { trigger: header, start: "top 90%" },
x: -20,
opacity: 0,
duration: 0.5,
ease: "steps(5)",
});
});
}
// โโโ Hero entrance animation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function animateHeroEntrance() {
if (reducedMotion) return;
const tl = gsap.timeline({ delay: 0.2 });
tl.from(".hero-eyebrow", { opacity: 0, y: -10, duration: 0.4, ease: "steps(4)" })
.from(".hero-name", { opacity: 0, y: 20, duration: 0.5, ease: "steps(5)" }, "-=0.1")
.from(".hero-subtitle", { opacity: 0, duration: 0.3, ease: "steps(3)" }, "-=0.1")
.from(
".hero-tags .hero-tag",
{
opacity: 0,
y: 8,
duration: 0.3,
ease: "steps(3)",
stagger: 0.07,
},
"-=0.1"
)
.from(".hero-scroll-cue", { opacity: 0, duration: 0.4, ease: "steps(4)" }, "-=0.1")
.from(".hud-corner", { opacity: 0, duration: 0.3, ease: "steps(3)", stagger: 0.15 }, "-=0.2")
.from(".hud-score", { opacity: 0, duration: 0.3, ease: "steps(3)" }, "-=0.1");
}
// โโโ "No" button easter egg โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const btnNo = document.getElementById("btnNo");
let noClicks = 0;
const noMessages = [
"โ ARE YOU SURE?",
"โ REALLY SURE?",
"โ FINAL ANSWER?",
"โ WELL OK THEN",
"โ YOUR LOSS!",
"โ ...JUST KIDDING!",
];
if (btnNo) {
btnNo.addEventListener("click", () => {
noClicks = Math.min(noClicks + 1, noMessages.length - 1);
btnNo.textContent = noMessages[noClicks];
if (noClicks === noMessages.length - 1) {
setTimeout(() => {
noClicks = 0;
btnNo.textContent = "โ NO / QUIT";
}, 2000);
}
});
}
// โโโ Keyboard: any key โ scroll to stats โโโโโโโโโโโโโโโโโโโโโโโโโ
let hasScrolled = false;
function onAnyKey(e) {
if (hasScrolled) return;
if (["Tab", "Shift", "Control", "Alt", "Meta"].includes(e.key)) return;
hasScrolled = true;
triggerViewTransition("stats");
window.removeEventListener("keydown", onAnyKey);
}
window.addEventListener("keydown", onAnyKey);
// โโโ Boot sequence โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function boot() {
await runLoadingScreen();
// Start canvas animation loop
requestAnimationFrame(animate);
// Start score counter
tickScore();
// GSAP animations
initSectionReveals();
initStatAnimations();
animateHeroEntrance();
}
boot();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kai Nishida โ Game Developer Portfolio</title>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
<script type="importmap">{"imports":{"gsap":"https://esm.sh/gsap@3.13.0","gsap/ScrollTrigger":"https://esm.sh/gsap@3.13.0/ScrollTrigger","gsap/SplitText":"https://esm.sh/gsap@3.13.0/SplitText","gsap/Flip":"https://esm.sh/gsap@3.13.0/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/gsap@3.13.0/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/gsap@3.13.0/TextPlugin","gsap/all":"https://esm.sh/gsap@3.13.0/all","gsap/":"https://esm.sh/gsap@3.13.0/","lenis":"https://esm.sh/lenis@1.1.13/dist/lenis.mjs","three":"https://esm.sh/three@0.171.0","three/addons/":"https://esm.sh/three@0.171.0/examples/jsm/"}}</script>
</head>
<body>
<!-- โโโ LOADING OVERLAY โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<div class="loading-overlay" id="loadingOverlay" aria-hidden="true">
<div class="loading-inner">
<div class="loading-logo">โ KN โ</div>
<div class="loading-text">LOADING<span class="loading-dots"></span></div>
<div class="loading-bar-wrap">
<div class="loading-bar" id="loadingBar"></div>
</div>
<div class="loading-pct" id="loadingPct">0%</div>
</div>
</div>
<!-- โโโ PIXEL NAV โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<nav class="pixel-nav" id="pixelNav" aria-label="Section navigation">
<div class="nav-inner">
<span class="nav-logo">KN</span>
<div class="nav-links">
<a href="#hero" class="nav-link" data-section="hero">โถ HOME</a>
<a href="#stats" class="nav-link" data-section="stats">โถ STATS</a>
<a href="#games" class="nav-link" data-section="games">โถ GAMES</a>
<a href="#tech" class="nav-link" data-section="tech">โถ INV</a>
<a href="#contact" class="nav-link" data-section="contact">โถ END</a>
</div>
</div>
</nav>
<!-- โโโ HERO โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="hero" id="hero">
<canvas class="hero-canvas" id="heroCanvas" aria-hidden="true"></canvas>
<!-- HUD corner decorations -->
<div class="hud-corner hud-tl" aria-hidden="true">
<div class="hud-hp">
<span class="hud-label">HP</span>
<div class="hud-bar"><div class="hud-bar-fill hud-bar-hp"></div></div>
<span class="hud-val">100/100</span>
</div>
<div class="hud-mp">
<span class="hud-label">MP</span>
<div class="hud-bar"><div class="hud-bar-fill hud-bar-mp"></div></div>
<span class="hud-val">88/88</span>
</div>
</div>
<div class="hud-corner hud-tr" aria-hidden="true">
<div class="hud-xp-label">XP</div>
<div class="hud-xp-bar-wrap">
<div class="hud-xp-bar"></div>
</div>
<div class="hud-level">LV <span class="hud-level-num">42</span></div>
</div>
<div class="hero-content">
<div class="hero-eyebrow">[ PLAYER ONE ]</div>
<h1 class="hero-name">KAI NISHIDA</h1>
<p class="hero-subtitle">Game Developer</p>
<div class="hero-tags">
<span class="hero-tag">UNITY</span>
<span class="hero-tag">UNREAL</span>
<span class="hero-tag">GODOT</span>
<span class="hero-tag">OPENGL</span>
</div>
<div class="hero-scroll-cue">
<span class="blink">โผ PRESS ANY KEY TO CONTINUE โผ</span>
</div>
</div>
<!-- Score display -->
<div class="hud-score" aria-hidden="true">
<div class="score-label">SCORE</div>
<div class="score-val" id="scoreVal">000000</div>
</div>
</section>
<!-- โโโ STATS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="section stats-section" id="stats">
<div class="section-inner">
<div class="section-header-pixel">
<span class="section-num">01.</span>
<h2 class="section-title">CHARACTER STATS</h2>
<span class="section-deco">[ SHEET ]</span>
</div>
<div class="stats-grid">
<!-- Stat card -->
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">STR</span>
<span class="stat-name">C++ / Systems</span>
<span class="stat-num" data-target="92">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="92"></div>
</div>
<div class="stat-desc">Low-level game engine architecture & memory systems</div>
</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">INT</span>
<span class="stat-name">Game Design</span>
<span class="stat-num" data-target="88">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="88"></div>
</div>
<div class="stat-desc">Systems design, balancing, narrative & level architecture</div>
</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">DEX</span>
<span class="stat-name">Perf Optim</span>
<span class="stat-num" data-target="95">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="95"></div>
</div>
<div class="stat-desc">GPU profiling, draw call batching, LOD systems</div>
</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">WIS</span>
<span class="stat-name">Shaders / GLSL</span>
<span class="stat-num" data-target="79">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="79"></div>
</div>
<div class="stat-desc">Custom shaders, post-processing pipelines, VFX graphs</div>
</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">CHA</span>
<span class="stat-name">Team Lead</span>
<span class="stat-num" data-target="84">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="84"></div>
</div>
<div class="stat-desc">Led 8-person indie teams, sprint planning, code reviews</div>
</div>
<div class="stat-card">
<div class="stat-row">
<span class="stat-abbr">LCK</span>
<span class="stat-name">Shipped Games</span>
<span class="stat-num" data-target="12">0</span>
</div>
<div class="stat-bar-wrap">
<div class="stat-bar" data-width="72"></div>
</div>
<div class="stat-desc">12 released titles across PC, console, and mobile</div>
</div>
</div>
<!-- XP progress -->
<div class="xp-progress">
<div class="xp-label">TOTAL XP TO NEXT LEVEL</div>
<div class="xp-bar-outer">
<div class="xp-bar-inner" data-xp="73"></div>
</div>
<div class="xp-pct"><span class="xp-pct-num" data-target="73">0</span>%</div>
</div>
</div>
</section>
<!-- โโโ GAMES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="section games-section" id="games">
<div class="section-inner">
<div class="section-header-pixel">
<span class="section-num">02.</span>
<h2 class="section-title">GAME LIBRARY</h2>
<span class="section-deco">[ SELECT TITLE ]</span>
</div>
<div class="games-grid">
<!-- Game 1 -->
<article class="game-card" tabindex="0">
<div class="cartridge-label" style="--cart-color: #ffdd57; --cart-color2: #e6c400;">
<div class="cart-stripe"></div>
<div class="cart-body">
<div class="cart-genre">โ RPG โ</div>
<div class="cart-title">ECHO REALM</div>
<div class="cart-sub">Chronicles of the Void</div>
<div class="cart-platforms">๐ป ๐ฎ</div>
</div>
<div class="cart-bottom"></div>
</div>
<div class="game-info">
<div class="game-meta-row">
<span class="game-year">2024</span>
<span class="game-engine">Unity 2023</span>
<span class="game-rating">โ
โ
โ
โ
โ
</span>
</div>
<p class="game-desc">Open-world RPG with procedural dungeon generation, custom dialogue system, and HDRP visuals. 50k+ Steam wishlist.</p>
<div class="game-tags">
<span class="gtag">Procedural Gen</span>
<span class="gtag">HDRP</span>
<span class="gtag">Online Co-op</span>
</div>
</div>
</article>
<!-- Game 2 -->
<article class="game-card" tabindex="0">
<div class="cartridge-label" style="--cart-color: #ff6b9d; --cart-color2: #d45080;">
<div class="cart-stripe"></div>
<div class="cart-body">
<div class="cart-genre">โ FPS โ</div>
<div class="cart-title">NEON BREACH</div>
<div class="cart-sub">Tactical Cyberpunk Shooter</div>
<div class="cart-platforms">๐ป ๐ฑ๏ธ</div>
</div>
<div class="cart-bottom"></div>
</div>
<div class="game-info">
<div class="game-meta-row">
<span class="game-year">2023</span>
<span class="game-engine">Unreal 5</span>
<span class="game-rating">โ
โ
โ
โ
โ</span>
</div>
<p class="game-desc">Competitive FPS set in a cyberpunk megacity. Nanite environments, Lumen GI, custom netcode with sub-20ms latency.</p>
<div class="game-tags">
<span class="gtag">Nanite</span>
<span class="gtag">Lumen</span>
<span class="gtag">Multiplayer</span>
</div>
</div>
</article>
<!-- Game 3 -->
<article class="game-card" tabindex="0">
<div class="cartridge-label" style="--cart-color: #4ecdc4; --cart-color2: #2fa89f;">
<div class="cart-stripe"></div>
<div class="cart-body">
<div class="cart-genre">โ PUZZLE โ</div>
<div class="cart-title">VOID LOGIC</div>
<div class="cart-sub">Dimension Bending Puzzles</div>
<div class="cart-platforms">๐ป ๐ฑ</div>
</div>
<div class="cart-bottom"></div>
</div>
<div class="game-info">
<div class="game-meta-row">
<span class="game-year">2022</span>
<span class="game-engine">Godot 4</span>
<span class="game-rating">โ
โ
โ
โ
โ
</span>
</div>
<p class="game-desc">Mind-bending puzzle game using non-Euclidean geometry. Custom GDScript spatial engine. 300k downloads on itch.io.</p>
<div class="game-tags">
<span class="gtag">Non-Euclidean</span>
<span class="gtag">Custom Engine</span>
<span class="gtag">Cross-platform</span>
</div>
</div>
</article>
</div>
</div>
</section>
<!-- โโโ TECH / INVENTORY โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="section tech-section" id="tech">
<div class="section-inner">
<div class="section-header-pixel">
<span class="section-num">03.</span>
<h2 class="section-title">INVENTORY</h2>
<span class="section-deco">[ EQUIPPED ITEMS ]</span>
</div>
<div class="inventory-grid">
<div class="inv-item" data-rarity="legendary" tabindex="0">
<div class="inv-icon inv-icon--cpp"></div>
<div class="inv-details">
<div class="inv-name">C++17</div>
<div class="inv-rarity rarity-legendary">โ LEGENDARY</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="legendary" tabindex="0">
<div class="inv-icon inv-icon--unity"></div>
<div class="inv-details">
<div class="inv-name">Unity</div>
<div class="inv-rarity rarity-legendary">โ LEGENDARY</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="legendary" tabindex="0">
<div class="inv-icon inv-icon--unreal"></div>
<div class="inv-details">
<div class="inv-name">Unreal 5</div>
<div class="inv-rarity rarity-legendary">โ LEGENDARY</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="rare" tabindex="0">
<div class="inv-icon inv-icon--glsl"></div>
<div class="inv-details">
<div class="inv-name">GLSL / HLSL</div>
<div class="inv-rarity rarity-rare">โ RARE</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="rare" tabindex="0">
<div class="inv-icon inv-icon--godot"></div>
<div class="inv-details">
<div class="inv-name">Godot 4</div>
<div class="inv-rarity rarity-rare">โ RARE</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="rare" tabindex="0">
<div class="inv-icon inv-icon--vulkan"></div>
<div class="inv-details">
<div class="inv-name">Vulkan API</div>
<div class="inv-rarity rarity-rare">โ RARE</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="common" tabindex="0">
<div class="inv-icon inv-icon--python"></div>
<div class="inv-details">
<div class="inv-name">Python</div>
<div class="inv-rarity rarity-common">โ COMMON</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="common" tabindex="0">
<div class="inv-icon inv-icon--blender"></div>
<div class="inv-details">
<div class="inv-name">Blender</div>
<div class="inv-rarity rarity-common">โ COMMON</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
<div class="inv-item" data-rarity="common" tabindex="0">
<div class="inv-icon inv-icon--git"></div>
<div class="inv-details">
<div class="inv-name">Git / Perforce</div>
<div class="inv-rarity rarity-common">โ COMMON</div>
</div>
<div class="inv-sparkle" aria-hidden="true">โฆ</div>
</div>
</div>
</div>
</section>
<!-- โโโ CONTACT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="section contact-section" id="contact">
<div class="section-inner">
<div class="contact-screen">
<div class="contact-title">GAME OVER?</div>
<div class="contact-sub">OR CONTINUE?</div>
<div class="contact-prompt">
<div class="contact-question">WORK TOGETHER?</div>
<div class="contact-buttons">
<a href="mailto:kai@example.com" class="pixel-btn pixel-btn--yes">
<span class="btn-cursor">โถ</span> YES / CONTINUE
</a>
<button class="pixel-btn pixel-btn--no" id="btnNo">
โ NO / QUIT
</button>
</div>
</div>
<div class="contact-links">
<a href="#" class="contact-link">
<span class="contact-link-icon">โ</span>
<span class="contact-link-label">GitHub</span>
<span class="contact-link-val">@kainishida</span>
</a>
<a href="#" class="contact-link">
<span class="contact-link-icon">โ</span>
<span class="contact-link-label">Twitter/X</span>
<span class="contact-link-val">@kainishida_dev</span>
</a>
<a href="#" class="contact-link">
<span class="contact-link-icon">โ</span>
<span class="contact-link-label">itch.io</span>
<span class="contact-link-val">kainishida.itch.io</span>
</a>
</div>
<div class="contact-email">
<span class="contact-email-label">INSERT COIN:</span>
<a href="mailto:kai@example.com" class="contact-email-addr blink">kai@example.com</a>
</div>
<div class="credits">
<div class="credits-line">ยฉ 2025 KAI NISHIDA GAMES</div>
<div class="credits-line">ALL RIGHTS RESERVED</div>
<div class="credits-line muted">MADE WITH โฅ AND TOO MUCH COFFEE</div>
</div>
</div>
</div>
</section>
<!-- โโโ SECTION TRANSITION OVERLAY โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<div class="vt-overlay" id="vtOverlay" aria-hidden="true">
<div class="vt-text">LOADING<span class="loading-dots">...</span></div>
</div>
<script type="module" src="script.js"></script>
</body>
</html>Game Developer Portfolio
Retro pixel art game developer portfolio with CRT scanlines, canvas starfield + animated pixel character, HUD-style stat bars, game cartridge cards, and inventory skill items.
Source
- Repository:
libs-genclaude - Original demo id:
35-gamedev-portfolio
Notes
Retro pixel art game developer portfolio with CRT scanlines, canvas starfield + animated pixel character, HUD-style stat bars, game cartridge cards, and inventory skill items.