diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-26 00:33:22 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-26 00:33:22 -0700 |
| commit | b2827329a32d3fe627a62bd4ff4191b2a3e4407f (patch) | |
| tree | 2d5ef329e579340d1e6b64b179a8d723c1da9b54 /index.html | |
| parent | 1c9ad19a5ff2e3c1463ba34ee5d707b93ebf8960 (diff) | |
redesign: frutiger aero faux-OS desktop
replaces the old enter/index pages with a draggable-windows desktop
metaphor. wires last.fm now-playing, films.tylerhoang.xyz diary, and
a php visitor counter; keeps background music via /mus/mmt.mp3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'index.html')
| -rwxr-xr-x | index.html | 640 |
1 files changed, 466 insertions, 174 deletions
@@ -1,205 +1,497 @@ <!DOCTYPE html> -<html lang='en'> +<html lang="en"> +<head> +<meta charset="UTF-8" /> +<title>tyler.xyz Β· Aqua Desktop</title> +<link rel="stylesheet" href="aero.css" /> +<style> + /* ============= LOCKED PALETTE ============= */ + :root { + --sky: url("img/wallpaper.png") center / cover no-repeat, linear-gradient(180deg, oklch(78% 0.10 215) 0%, oklch(88% 0.14 145) 100%); + --sun: radial-gradient(circle, oklch(99% 0.02 215) 0%, oklch(94% 0.05 215 / 0) 60%); + --icon-blue: linear-gradient(135deg, oklch(92% 0.06 215), oklch(72% 0.13 220) 60%, oklch(48% 0.13 230)); + --icon-orange: linear-gradient(135deg, oklch(94% 0.10 145), oklch(78% 0.18 145) 55%, oklch(52% 0.16 150)); + --icon-green: linear-gradient(135deg, oklch(94% 0.12 130), oklch(76% 0.18 140) 55%, oklch(50% 0.16 150)); + --icon-pink: linear-gradient(135deg, oklch(88% 0.08 195), oklch(68% 0.13 200) 60%, oklch(45% 0.13 210)); + --icon-silver: linear-gradient(135deg, oklch(98% 0.005 220), oklch(85% 0.015 220) 60%, oklch(62% 0.03 225)); + --title-bar: linear-gradient(to bottom, oklch(94% 0.05 195), oklch(78% 0.10 200) 50%, oklch(60% 0.12 215)); + --start-btn: linear-gradient(to bottom, oklch(94% 0.10 145) 0%, oklch(75% 0.18 145) 48%, oklch(50% 0.16 150) 52%, oklch(68% 0.18 145) 100%); + --start-border: oklch(40% 0.14 150); + } + .desk { position: fixed; inset: 0; overflow: hidden; background: var(--sky); background-size: cover; background-position: center; } + .icons { position: absolute; left: 24px; top: 24px; display: grid; grid-template-columns: 1fr; gap: 18px; } + .icon { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 80px; cursor: pointer; text-align: center; } + .icon .glyph { + width: 56px; height: 56px; border-radius: 14px; + background: linear-gradient(135deg, oklch(88% 0.10 220), oklch(70% 0.14 230) 60%, oklch(50% 0.13 240)); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), inset 0 -3px 6px rgba(40,80,140,0.4), 0 4px 14px rgba(40,80,140,0.35); + display: flex; align-items: center; justify-content: center; + font-size: 26px; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.4); + position: relative; + } + .icon .glyph::before { content: ""; position: absolute; left: 4px; right: 4px; top: 3px; height: 40%; border-radius: 12px; background: linear-gradient(to bottom, rgba(255,255,255,0.75), transparent); } + .icon .glyph.orange { background: linear-gradient(135deg, oklch(92% 0.08 70), oklch(75% 0.16 55) 60%, oklch(55% 0.15 35)); } + .icon .glyph.green { background: linear-gradient(135deg, oklch(90% 0.10 145), oklch(75% 0.15 145) 60%, oklch(50% 0.13 155)); } + .icon .glyph.pink { background: linear-gradient(135deg, oklch(90% 0.10 350), oklch(75% 0.16 350) 60%, oklch(55% 0.16 340)); } + .icon .glyph.silver { background: linear-gradient(135deg, oklch(95% 0.01 240), oklch(78% 0.03 240) 60%, oklch(55% 0.04 240)); } + .icon .label { font-size: 12px; color: white; text-shadow: 0 1px 3px rgba(0,0,0,0.6); font-weight: 500; } + .icon:hover .glyph { transform: translateY(-2px) scale(1.04); transition: transform 200ms; } - <head> - <title>Tyler's Website</title> - <link rel='stylesheet' type='text/css' href='css/bootstrap.css'> - <link rel='stylesheet' type='text/css' href='css/style.css'> - <meta charset='utf-8'/> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - </head> + .win { position: absolute; min-width: 320px; } + .win .titlebar { + height: 32px; padding: 0 12px; display: flex; align-items: center; gap: 8px; + border-radius: 18px 18px 0 0; + background: var(--title-bar); + color: var(--title-fg, white); font-size: 13px; font-weight: 600; text-shadow: 0 1px 2px var(--title-shadow, rgba(0,0,0,0.3)); + cursor: grab; user-select: none; + border-bottom: 1px solid rgba(0,0,0,0.15); + } + .win .titlebar .dots { display: flex; gap: 6px; margin-right: 8px; } + .win .titlebar .dot { width: 13px; height: 13px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.35); cursor: pointer; box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); } + .win .titlebar .dot.r { background: radial-gradient(circle at 35% 30%, oklch(85% 0.18 30), oklch(55% 0.18 30)); } + .win .titlebar .dot.y { background: radial-gradient(circle at 35% 30%, oklch(95% 0.15 95), oklch(70% 0.18 80)); } + .win .titlebar .dot.g { background: radial-gradient(circle at 35% 30%, oklch(90% 0.18 145), oklch(60% 0.18 150)); } + .win .body { padding: 16px; font-size: 13px; line-height: 1.55; color: oklch(22% 0.04 240); border-radius: 0 0 18px 18px; } - <body><a name="top"></a> - <section id="navbar" style="padding-top: 0"> - <div class="navbar"> - <div class="navbar-inner"> - <div class="container" style="width: auto;"> - <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - </a> - <a class="brand" href="#">Tyler's Website</a> - <div class="nav-collapse"> - <ul class="nav"> - <li><a href="#about-me">About Me</a></li> - <li><a href="#lifestyle">Lifestyle</a></li> - <li><a href="#personal">Public Sites</a></li> - <li><a href="#misc">Miscellaneous</a></li> - <li><a href="#contact">Contact</a></li> - </ul> - </div><!-- /.nav-collapse --> - </div> - </div><!-- /navbar-inner --> - </div><!-- /navbar --> - </section> - <center> - <img src="/img/static/welcome.gif"> - <h1><img src="/img/static/anipiano.gif"> <blink><FONT COLOR="#FF0000">T</FONT><FONT COLOR="#FF5A00">y</FONT><FONT COLOR="#FFB400">l</FONT><FONT COLOR="#FFff00">e</FONT><FONT COLOR="#A5ff00">r</FONT><FONT COLOR="#4Bff00">'</FONT><FONT COLOR="#00ff00">s</FONT><FONT COLOR="#00ff5A"> </FONT><FONT COLOR="#00ffB4">W</FONT><FONT COLOR="#00ffff">e</FONT><FONT COLOR="#00B4ff">b</FONT><FONT COLOR="#005Aff">s</FONT><FONT COLOR="#0000ff">i</FONT><FONT COLOR="#4B00ff">t</FONT><FONT COLOR="#A500ff">e</FONT></blink><img src='/img/static/construction2_1_.gif'></h1> + .taskbar { + position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); + height: 56px; padding: 0 12px; display: flex; align-items: center; gap: 10px; + border-radius: 28px; + background: linear-gradient(to bottom, rgba(255,255,255,0.55), rgba(180,210,240,0.45)); + backdrop-filter: blur(20px) saturate(180%); + border: 1px solid rgba(255,255,255,0.85); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.95), 0 12px 36px rgba(40,80,140,0.3); + z-index: 200; + } + .taskbar .start { + height: 40px; padding: 0 18px 0 14px; display: inline-flex; align-items: center; gap: 8px; + border-radius: 20px; + background: var(--start-btn); + color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.4); font-weight: 700; font-size: 13px; + border: 1px solid var(--start-border); cursor: pointer; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.85), 0 3px 10px rgba(180,90,40,0.3); + } + .taskbar .sep { width: 1px; height: 32px; background: linear-gradient(to bottom, transparent, rgba(60,100,160,0.4), transparent); } + .tray { display: inline-flex; align-items: center; gap: 8px; padding: 0 12px; font-size: 12px; color: oklch(25% 0.05 240); } + .clock { font-family: "Segoe UI", Tahoma; font-weight: 600; } + /* SERVER ROW */ + .srv-row { + display: flex; align-items: center; gap: 10px; padding: 7px 0; + border-bottom: 1px dotted oklch(72% 0.05 220); font-size: 12px; + } + .srv-row:last-child { border-bottom: 0; } + .srv-led { + width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); + animation: led-pulse 2.2s ease-in-out infinite; + } + .srv-led.ok { background: radial-gradient(circle at 30% 30%, oklch(96% 0.18 145), oklch(60% 0.18 150)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 0 8px oklch(70% 0.18 145 / 0.7); } + .srv-led.warn { background: radial-gradient(circle at 30% 30%, oklch(95% 0.16 85), oklch(70% 0.18 75)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 0 8px oklch(70% 0.18 80 / 0.6); } + @keyframes led-pulse { 50% { opacity: 0.55; } } + .srv-host { font-family: 'Courier New', monospace; flex: 1; color: oklch(25% 0.05 230); } + .srv-meta { font-size: 10px; opacity: 0.7; } - <img src='/img/static/usanimatedflag.gif'> - <img src='/img/static/portrait.jpg' width='150' style="border-radius: 0%";> - <img src='/img/static/vn-flag1.gif'> + /* FILM DIARY ROW */ + .film-row { + display: flex; gap: 10px; padding: 8px 0; + border-bottom: 1px dotted oklch(72% 0.05 220); + } + .film-row:last-child { border-bottom: 0; } + .film-poster { + width: 38px; height: 56px; border-radius: 4px; flex-shrink: 0; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 2px 6px rgba(40,80,140,0.2); + } + .film-meta { flex: 1; min-width: 0; font-size: 12px; line-height: 1.45; } + .film-title { font-weight: 700; color: oklch(25% 0.06 230); font-size: 13px; } + .film-year { font-weight: 400; opacity: 0.6; font-size: 11px; margin-left: 3px; } + .film-rating { font-size: 11px; margin: 1px 0 2px; } + .film-rating .star { color: oklch(80% 0.04 230); } + .film-rating .star.on { color: oklch(72% 0.16 60); text-shadow: 0 0 4px oklch(80% 0.18 70 / 0.5); } + .film-rating .star.half { background: linear-gradient(90deg, oklch(72% 0.16 60) 50%, oklch(80% 0.04 230) 50%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } + .film-note { font-size: 11px; opacity: 0.75; font-style: italic; } +</style> +</head> +<body> + <div class="desk" id="desk"> + <div class="sun" style="background: var(--sun);"></div> + <div class="lens-flare"></div> + <div class="clouds" id="clouds"></div> - </center> + <!-- Desktop icons --> + <div class="icons"> + <div class="icon" data-open="about"><div class="glyph" style="background: var(--icon-blue)">π€</div><div class="label">About Me</div></div> + <div class="icon" data-open="now"><div class="glyph" style="background: var(--icon-orange)">β</div><div class="label">Now.txt</div></div> + <div class="icon" data-open="music"><div class="glyph" style="background: var(--icon-pink)">βͺ</div><div class="label">last.fm</div></div> + <div class="icon" data-open="servers"><div class="glyph" style="background: var(--icon-green)">π₯</div><div class="label">My Servers</div></div> + <div class="icon" data-open="podcast"><div class="glyph" style="background: var(--icon-silver)">π</div><div class="label">REEL MOUTH</div></div> + <div class="icon" data-open="films"><div class="glyph" style="background: var(--icon-pink)">π</div><div class="label">Films</div></div> + <div class="icon" data-open="guestbook"><div class="glyph" style="background: var(--icon-blue)">β</div><div class="label">Contact</div></div> + </div> - <section id="about-me" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>About Me</h1> - </div> - <center> - <img src="/img/static/snoopy.gif" width="70"> - <img src="/img/static/snoopy.gif" width="70"> - <img src="/img/static/snoopy.gif" width="70"> - <center> - <a href="#top">Back to top of page</a> - </center> - </center> + <!-- WINDOWS --> + <div class="win glass" id="w-about" style="left: 180px; top: 60px; width: 440px;"> + <div class="titlebar"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>About Me β tyler.txt</div> + <div class="body"> + <div style="display: flex; gap: 14px; margin-bottom: 12px;"> + <div class="photo" style="width: 110px; height: 130px; background: none; padding: 0;"><img src="/img/static/portrait.jpg" alt="portrait" style="width:100%;height:100%;object-fit:cover;border-radius:10px;" /></div> + <div> + <div style="font-size: 22px; font-weight: 700; line-height: 1.1; color: oklch(28% 0.10 240);">Tyler Hoang</div> + <div style="font-size: 12px; opacity: 0.7; margin-top: 2px;">a.k.a. Thuy Β· Tiger Β· Train</div> + <div style="margin-top: 10px; font-size: 12px; line-height: 1.6;"> + <div>π¦ banker @ Chase</div> + <div>π M.S. Financial Analytics, CSULB</div> + <div>πΉ hobbyist jazz pianist</div> + <div>π§ linux sysadmin for fun</div> + <div>π married to Trinh</div> </div> - <p>Hi, my name is Tyler, some might know me as Thuy, Tiger, or Train; and welcome to my personal website. Here you'll find various links to services that I host, along with just random things I feel like sharing with people. Please enjoy your stay here.</p> - <p>I'm Vietnamese and I immigrated to the United States when I was 6. I am married to my lovely wife Trinh.</p> - <p>I'm 23 years old and currently attending Cal State Long Beach to get my Master's degree in Financial Analytics. My hobbies include cooking, playing jazz piano, and tinkering with servers on Linux.</p> + </div> + </div> + <p style="margin: 0 0 8px;">Hi! I'm 23, Vietnamese-American, and this little corner of the internet is where I keep the un-LinkedIn version of myself. Quant finance pays the bills, but tinkering with servers and chasing chord voicings is what I do for fun.</p> + <p style="margin: 0;">Poke around β drag windows, open icons, click the bubbles. The professional site is <a class="aero-link" href="https://tylerhoang.xyz">elsewhere</a>; this one's all play.</p> + </div> + </div> - <p>Currently I work as an associate banker at JPMorgan Chase and a full time student. You can find my film podcast <b>REEL MOUTH</b> on most major platforms and also at <a href="https://reelmouth.tv">reelmouth.tv</a></p> - </section> + <div class="win glass warm" id="w-now" style="left: 660px; top: 100px; width: 320px;"> + <div class="titlebar" style="background: var(--title-bar);"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>Now.txt</div> + <div class="body"> + <div style="font-size: 11px; opacity: 0.7; margin-bottom: 8px;">updated 3 days ago Β· from sunny long beach</div> + <ul style="margin: 0; padding-left: 18px; line-height: 1.7;"> + <li>finishing my master's thesis (please end)</li> + <li>learning Cherokee by Bud Powell</li> + <li>rebuilding my Nextcloud on a new mini-PC</li> + <li>cooking through every <em>bΓΊn</em> recipe my mom texts me</li> + <li>3 movies behind on the REEL MOUTH backlog</li> + </ul> + </div> + </div> - <section id="personal" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>Public Sites</h1> - </div> - <center> - <img src="/img/static/mchammer.gif"> - <img src="/img/static/mchammer.gif"> - <img src="/img/static/mchammer.gif"> - <center> - <a href="#top">Back to top of page</a> - </center> - </center> - </div> - <h4>Here are all of the public services that I host for others to use. I do keep logs. Basically, I log your IP address, user-agent, what you looked at, and when. Most of the time though, I'm too busy to look through them anyways, and I only look through them if someone's abusing the services. All of the software used to host these services are open source also.</h4> + <div class="win glass blue" id="w-music" style="left: 380px; top: 360px; width: 360px;"> + <div class="titlebar"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>last.fm β tylertrains</div> + <div class="body" id="np-host"> + <div id="np-card"></div> + <div style="margin-top: 14px; font-size: 11px; opacity: 0.75; text-transform: uppercase; letter-spacing: 1px;">recent</div> + <div id="np-recent" style="margin-top: 6px; display: flex; flex-direction: column; gap: 6px; font-size: 12px;"> + <div style="display:flex;justify-content:space-between;"><span>Stella by Starlight Β· Bill Evans</span><span style="opacity:0.6">2m</span></div> + <div style="display:flex;justify-content:space-between;"><span>Body and Soul Β· Coleman Hawkins</span><span style="opacity:0.6">9m</span></div> + <div style="display:flex;justify-content:space-between;"><span>Flamingo Β· Kero Kero Bonito</span><span style="opacity:0.6">38m</span></div> + </div> + </div> + </div> + + <!-- SERVERS --> + <div class="win glass" id="w-servers" style="left: 740px; top: 390px; width: 380px; display: none;"> + <div class="titlebar" style="background: var(--title-bar);"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>My Servers β uptime.sh</div> + <div class="body"> + <div style="font-size: 11px; opacity: 0.7; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1.5px;">home lab status Β· auto-poll 30s</div> + <div id="srv-list" style="display: flex; flex-direction: column; gap: 0;"> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">drive.tylerhoang.xyz</div><div class="srv-meta">Nextcloud</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">up.tylerhoang.xyz</div><div class="srv-meta">Jenniesafe</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">git.tylerhoang.xyz</div><div class="srv-meta">Forgejo</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">tylerhoang.xyz</div><div class="srv-meta">portfolio</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">fun.tylerhoang.xyz</div><div class="srv-meta">this site</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">films.tylerhoang.xyz</div><div class="srv-meta">film diary</div></div> + <div class="srv-row"><div class="srv-led warn"></div><div class="srv-host">reelmouth.tv</div><div class="srv-meta">cdn lag Β· 0.4s</div></div> + <div class="srv-row"><div class="srv-led ok"></div><div class="srv-host">*.onion mirrors</div><div class="srv-meta">tor</div></div> + </div> + <div style="margin-top: 12px; padding-top: 10px; border-top: 1px dotted oklch(70% 0.05 220); font-size: 11px; opacity: 0.7; line-height: 1.6; font-family: 'Courier New', monospace;"> + host: beelink ser5 Β· debian 12<br/> + 4c / 8g / 1tb nvme Β· π± carbon-neutral via vultr<br/> + logs kept 30d. i basically never read them. + </div> + </div> + </div> - <p><a class="btn btn-primary btn-large" href=http://4otyc4grbhz35kzgmwt5g3hxplr5rtrn66vgmhddqagxxwh7l2q42vid.onion>Tor Site</a> - Here's this exact same site, just mirrored on the tor network. + <!-- PODCAST --> + <div class="win glass" id="w-podcast" style="left: 220px; top: 420px; width: 340px; display: none;"> + <div class="titlebar" style="background: var(--title-bar);"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>REEL MOUTH β film pod</div> + <div class="body"> + <div style="display: flex; gap: 12px; align-items: flex-start; margin-bottom: 12px;"> + <div class="photo" style="width: 84px; height: 84px;"><span>[ pod art ]</span></div> + <div style="font-size: 12px; line-height: 1.5;"> + <div style="font-size: 16px; font-weight: 700; color: oklch(28% 0.08 230); letter-spacing: -0.3px;">REEL MOUTH</div> + <div style="opacity: 0.7; margin-bottom: 4px;">a film podcast Β· since 2022</div> + <div>Tyler + 2 friends arguing about movies for ~90 minutes a week. We agree maybe 30% of the time.</div> + </div> + </div> + <div style="font-size: 11px; opacity: 0.7; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 6px;">latest episodes</div> + <!-- TODO: wire to podcast RSS via iTunes lookup API once we know the Apple Podcasts ID --> + <div style="display: flex; flex-direction: column; gap: 4px; font-size: 12px; line-height: 1.5;"> + <div style="display:flex;justify-content:space-between;gap:8px;"><span>#84 β every wong kar-wai, ranked</span><span style="opacity:0.6;flex-shrink:0;">1:47</span></div> + <div style="display:flex;justify-content:space-between;gap:8px;"><span>#83 β paul thomas anderson cinematic universe</span><span style="opacity:0.6;flex-shrink:0;">1:32</span></div> + <div style="display:flex;justify-content:space-between;gap:8px;"><span>#82 β vietnamese cinema is real, actually</span><span style="opacity:0.6;flex-shrink:0;">1:58</span></div> + <div style="display:flex;justify-content:space-between;gap:8px;"><span>#81 β the criterion sale haul</span><span style="opacity:0.6;flex-shrink:0;">1:12</span></div> + </div> + <div style="margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap;"> + <a class="aqua sm" href="https://reelmouth.tv" style="text-decoration:none;">βΆ reelmouth.tv</a> + <a class="aqua sm" href="#" style="text-decoration:none;">apple</a> + <a class="aqua sm" href="#" style="text-decoration:none;">spotify</a> + <a class="aqua sm" href="#" style="text-decoration:none;">rss</a> + </div> + </div> + </div> + + <!-- GUESTBOOK / CONTACT --> + <div class="win glass" id="w-guestbook" style="left: 540px; top: 200px; width: 360px; display: none;"> + <div class="titlebar" style="background: var(--title-bar);"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>Contact β say hi.txt</div> + <div class="body"> + <p style="margin: 0 0 10px;">No newsletter, no analytics. Just one email I actually read.</p> + <div style="padding: 10px 14px; border-radius: 12px; background: rgba(255,255,255,0.55); border: 1px solid rgba(255,255,255,0.85); font-family: 'Courier New', monospace; font-size: 13px; margin-bottom: 14px;"> + π§ <a class="aero-link" href="mailto:tyler@tylerhoang.xyz">tyler@tylerhoang.xyz</a> + </div> + <div style="font-size: 12px; line-height: 1.7;"> + <div>π <a class="aero-link" href="/files/gpg-main.txt">GPG public key</a> β encrypt if you can</div> + <div>π <a class="aero-link" href="https://github.com/tyhoang">github.com/tyhoang</a></div> + <div>π <a class="aero-link" href="https://git.tylerhoang.xyz">git.tylerhoang.xyz</a></div> + <div>π <a class="aero-link" href="https://letterboxd.com/trainytrain">letterboxd.com/trainytrain</a></div> + </div> + <div style="margin-top: 14px; padding-top: 10px; border-top: 1px dotted oklch(70% 0.05 220);"> + <div style="font-size: 10px; opacity: 0.65; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 6px;">β leave a quick note β</div> + <textarea id="gb-msg" placeholder="say hiβ¦" style="width: 100%; min-height: 56px; resize: vertical; padding: 8px 10px; border-radius: 10px; border: 1px solid rgba(120,160,200,0.4); background: rgba(255,255,255,0.7); font: 12px 'Segoe UI', Tahoma, sans-serif; color: oklch(25% 0.05 230); box-sizing: border-box;"></textarea> + <div style="display:flex; justify-content: space-between; align-items: center; margin-top: 6px;"> + <span id="gb-status" style="font-size: 10px; opacity: 0.65; font-style: italic;">β takes ~2 days for a reply β</span> + <button class="aqua sm no-drag" id="gb-send">send</button> + </div> + </div> + </div> + </div> - <p><a class="btn btn-primary btn-large" href=https://drive.tylerhoang.xyz>Nextcloud</a> - My Nextcloud instance. Honestly, this is mainly used just for me personally, but if you really want an account on it just send a text or an email and I'll probably make you one. + <!-- FILMS --> + <div class="win glass" id="w-films" style="left: 460px; top: 280px; width: 400px; display: none;"> + <div class="titlebar" style="background: var(--title-bar);"><div class="dots"><div class="dot r no-drag" onclick="this.closest('.win').style.display='none'"></div><div class="dot y"></div><div class="dot g"></div></div>Films β films.tylerhoang.xyz</div> + <div class="body"> + <div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px;"> + <div> + <div style="font-size: 14px; font-weight: 700; color: oklch(28% 0.08 230);">my film diary</div> + <div style="font-size: 11px; opacity: 0.7;">a self-hosted Letterboxd, sort of. since '16.</div> + </div> + <div id="films-stats" style="font-size: 10px; text-align: right; opacity: 0.7; line-height: 1.4;"></div> + </div> + <div style="font-size: 11px; opacity: 0.7; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 6px;">recent watches</div> + <div id="films-list" style="display: flex; flex-direction: column;"> + <div class="film-row"> + <div class="film-poster" style="background: linear-gradient(135deg, oklch(70% 0.14 30), oklch(40% 0.10 280));"></div> + <div class="film-meta"> + <div class="film-title">Chungking Express <span class="film-year">1994</span></div> + <div class="film-rating"><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span style="opacity:0.6;margin-left:8px;">2d ago Β· rewatch #11</span></div> + <div class="film-note">still cry at the pineapples. wong's best.</div> + </div> + </div> + <div class="film-row"> + <div class="film-poster" style="background: linear-gradient(135deg, oklch(85% 0.10 80), oklch(55% 0.12 30));"></div> + <div class="film-meta"> + <div class="film-title">The Scent of Green Papaya <span class="film-year">1993</span></div> + <div class="film-rating"><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star">β
</span><span style="opacity:0.6;margin-left:8px;">5d ago</span></div> + <div class="film-note">trinh picked it. so quiet you can hear yourself think.</div> + </div> + </div> + <div class="film-row"> + <div class="film-poster" style="background: linear-gradient(135deg, oklch(45% 0.10 250), oklch(25% 0.06 260));"></div> + <div class="film-meta"> + <div class="film-title">Stalker <span class="film-year">1979</span></div> + <div class="film-rating"><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star">β
</span><span style="opacity:0.6;margin-left:8px;">1w ago</span></div> + <div class="film-note">3 hours of soviet vibes. felt every minute, in a good way.</div> + </div> + </div> + <div class="film-row"> + <div class="film-poster" style="background: linear-gradient(135deg, oklch(75% 0.16 35), oklch(50% 0.14 20));"></div> + <div class="film-meta"> + <div class="film-title">Perfect Days <span class="film-year">2023</span></div> + <div class="film-rating"><span class="star on">β
</span><span class="star on">β
</span><span class="star on">β
</span><span class="star on half">β
</span><span class="star">β
</span><span style="opacity:0.6;margin-left:8px;">2w ago</span></div> + <div class="film-note">koji yakusho cleaning toilets is more cinema than most cinema.</div> + </div> + </div> + </div> + <div style="margin-top: 12px; display: flex; gap: 8px; align-items: center; justify-content: space-between;"> + <span style="font-size: 11px; opacity: 0.7;">also mirrored on <a class="aero-link" href="https://letterboxd.com/trainytrain">letterboxd</a></span> + <a class="aqua sm" href="https://films.tylerhoang.xyz" style="text-decoration:none;">see all β</a> + </div> + </div> + </div> - <p><a class="btn btn-primary btn-large" href=https://up.tylerhoang.xyz>Jenniesafe</a> - My fork of sakisafe, a fast and easy to use drag and drop file sharing site. If someone hosted abusive material on my site, please let me know ASAP so I can take appropriate action. <a href="http://ddxsewiy7ylr6kqqe5m2xmiztvuug5g4s426qbnwde7of64xncqwmvad.onion">Tor mirror.</a> + <!-- TASKBAR --> + <div class="taskbar"> + <div class="start"><span style="font-size:16px;">β</span> tyler</div> + <div class="sep"></div> + <div id="mt"></div> + <div class="sep"></div> + <div id="cc"></div> + <div class="sep"></div> + <div class="tray"> + <span class="clock" id="clock"></span> + <span style="opacity:0.6">|</span> + <span>πΆ β 73Β°F</span> + </div> + </div> - <!-- <p><a class="btn btn-primary btn-large" href=https://yt.tylerhoang.xyz>Invidious</a> - An alternative open source front-end for YouTube. --> - </section> + <audio id="bgm" src="/mus/mmt.mp3" loop preload="none"></audio> + </div> - <p><a class="btn btn-primary btn-large" href='https://git.tylerhoang.xyz'>Git</a> - My git repo, although there's basically nothing on it except for some dotfiles and the source code for this site. I'm not a programmer or a scripter, so please don't ask me to help you with those sort of things. +<script src="aero.js"></script> +<script> + const desk = document.getElementById('desk'); + Aero.makeClouds(document.getElementById('clouds')); + Aero.spawnBubbles(desk, 24); + Aero.sparkleCursor(); - <section id="lifestyle" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>Lifestyle</h1> - </div> - <center> - <img src="/img/static/dancingmouse.gif" width="90"> - <img src="/img/static/dancingmouse.gif" width="90"> - <img src="/img/static/dancingmouse.gif" width="90"> - </center> - <center> - <a href="#top">Back to top of page</a> - </center> - </div> - <h4>Stuff about my interests.</h4> - <p><a class="btn btn-large" href=./articles/software.html>Software & Equipment</a> - A list of hardware and software that I personally use on my machine. + document.querySelectorAll('.win').forEach(w => { + Aero.makeDraggable(w, w.querySelector('.titlebar')); + }); - <p><a class="btn btn-large" href=./articles/music.html>Music Recommendations</a> - My personal list of all the music that I've listened to. I find new music to listen to all the time, and this list might not be up to date, but I'll try and update it somewhat often.</p> + document.querySelectorAll('.icon').forEach(ic => { + ic.addEventListener('dblclick', () => { + const key = ic.dataset.open; + const w = document.getElementById('w-' + key); + if (w) { w.style.display = ''; w.style.zIndex = (++window.__zTop || (window.__zTop = 100)); } + }); + ic.addEventListener('click', () => { + const key = ic.dataset.open; + const w = document.getElementById('w-' + key); + if (w) { w.style.display = ''; w.style.zIndex = (++window.__zTop || (window.__zTop = 100)); } + }); + }); - <p><a class="btn btn-large" href=https://letterboxd.com/trainytrain/>Letterboxd</a> - A list of movies that I have watched from 2016 onward.</p> + // music toggle + document.getElementById('mt').innerHTML = Aero.musicToggleHTML(); + const mtDiv = document.getElementById('mt'); + const mtBtn = mtDiv.querySelector('.mt-btn'); + const mtLabel = mtDiv.querySelector('.mt-label'); + const bgm = document.getElementById('bgm'); + let musicOn = false; + mtBtn.addEventListener('click', () => { + musicOn = !musicOn; + if (musicOn) { + bgm.volume = 0.15; + bgm.play(); + mtBtn.textContent = 'ββ'; + mtLabel.textContent = 'βͺ kero kero bonito β flamingo'; + mtBtn.style.background = 'radial-gradient(circle at 30% 25%,white,oklch(75% 0.14 55) 60%,oklch(55% 0.15 35))'; + } else { + bgm.pause(); + mtBtn.textContent = 'βΆ'; + mtLabel.textContent = 'music off'; + mtBtn.style.background = 'radial-gradient(circle at 30% 25%,white,oklch(82% 0.12 220) 60%,oklch(50% 0.13 240))'; + } + }); - <p><a class="btn btn-large" href=./articles/library.html>Personal Library</a> - A collection of books that I've collected and my thoughts on them.</p> + // counter + document.getElementById('cc').innerHTML = Aero.counterHTML(0, 'visitors'); + Aero.fetchVisitorCount() + .then(n => { + document.getElementById('cc').innerHTML = Aero.counterHTML(n, 'visitors'); + }) + .catch(() => {}); - </section> + // now playing + document.getElementById('np-card').innerHTML = Aero.nowPlayingHTML(false); + Aero.animateEq(document.getElementById('np-card')); - <section id="misc" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>Miscellaneous</h1> - </div> - <center> - <img src="/img/static/dancing_girl.gif" width="60"> - <img src="/img/static/dancing_girl.gif" width="60"> - <img src="/img/static/dancing_girl.gif" width="60"> - </center> - <center> - <a href="#top">Back to top of page</a> - </center> - </div> - <p><a class="btn btn-large" href=./files/gpg-main.txt>GPG Key</a> - My public GPG key. I highly recommend that everyone use encryption whenever possible, and emails are no exception.</p> + // fetch last.fm + Aero.fetchLastFm() + .then(tracks => { + if (tracks && tracks.length > 0) { + document.getElementById('np-card').innerHTML = Aero.nowPlayingHTML(false, tracks[0]); + Aero.animateEq(document.getElementById('np-card')); - <p><a class="btn btn-large" href=./files/resume.pdf>Resume</a> - My resume.</p> + const recentDiv = document.getElementById('np-recent'); + if (tracks.length > 1) { + recentDiv.innerHTML = tracks.slice(1, 4).map(t => { + const ago = t.when ? Math.floor((Date.now() - t.when) / 60000) : 0; + const timeStr = ago < 60 ? ago + 'm' : Math.floor(ago / 60) + 'h'; + return `<div style="display:flex;justify-content:space-between;"><span>${t.artist ? t.artist + ' β ' : ''}${t.name}</span><span style="opacity:0.6">${timeStr}</span></div>`; + }).join(''); + } + } + }) + .catch(() => {}); - <p><a class="btn btn-large" href=https://github.com/tyhoang>Github</a> - My Github. I don't really use Github that much, and everything just mirrors my personal git repo from above..</p> + // clock + function tick() { + const d = new Date(); + document.getElementById('clock').textContent = + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); + } + tick(); setInterval(tick, 30000); - </section> + // guestbook send (fake but cute) + const gbBtn = document.getElementById('gb-send'); + if (gbBtn) { + gbBtn.addEventListener('click', () => { + const msg = document.getElementById('gb-msg').value.trim(); + const status = document.getElementById('gb-status'); + if (!msg) { status.textContent = 'β type something first β'; return; } + status.textContent = 'β sent! talk soon'; + document.getElementById('gb-msg').value = ''; + setTimeout(() => { status.textContent = 'β takes ~2 days for a reply β'; }, 4000); + }); + } - <!-- - <section id="donate" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>Donate</h1> - </div> - <center> - <img src="/img/static/panda_dancing.gif" width="60"> - <img src="/img/static/panda_dancing.gif" width="60"> - <img src="/img/static/panda_dancing.gif" width="60"> - </center> - <center> - <a href="#top">Back to top of page</a> - </center> - </div> - <h4>If you like the website and found it useful, maybe consider donating a few bucks and allow me to gourge myself on some Taco Bell.</h4> + // fetch films + Aero.fetchFilms() + .then(data => { + try { + const films = Array.isArray(data) ? data : (data.films || data.data || []); + const filmsList = document.getElementById('films-list'); - <ul> - <li><a class="btn btn-medium" href=https://paypal.me/tyhoang69>Paypal: tyler@tylerhoang.xyz</a> - If you use PayPal, you can donate to my email address.</li> - </ul> + if (films.length > 0) { + filmsList.innerHTML = films.map((film, idx) => { + const stars = Array.from({ length: 5 }, (_, i) => { + const fullStars = Math.floor(film.rating || 0); + const hasHalf = (film.rating || 0) % 1 !== 0 && i + 1 === Math.ceil(film.rating || 0); + if (i + 1 <= fullStars) return '<span class="star on">β
</span>'; + if (hasHalf) return '<span class="star half">β
</span>'; + return '<span class="star">β
</span>'; + }).join(''); - <h4>If you prefer to donate to me using cryptocurrencies, look below.</h4> - <ul> - <li><a class="btn btn-medium">Monero</a> (<a href=/img/static/xmr.png>XMR QR</a>). <code style="font-size:small;overflow-wrap:break-word">473TZBZff6L7NKcdH8deMoeCRMQ5YWXUVVghHp5zoJ2WYd7mYmYgbUVgJZdPW5g4au2s3DiRs769hHQ5Rax6Vr57Mpk4YSD</code></li> - <li><a class="btn btn-medium">Ripple</a> (<a href=/img/static/xrp.png>XRP QR</a>). <code style="font-size:small;overflow-wrap:break-word">rMdG3ju8pgyVh29ELPWaDuA74CpWW6Fxns</code>. Destination Tag: <code>3963998596</code></li> - <li><a class="btn btn-medium">BAT</a> - If you use the Brave browser, you can tip me some BAT at any of my websites listed above.</li> - <li><a class="btn btn-medium">Uphold: tyler@tylerhoang.xyz</a> - If you use Uphold to trade crypto, you can send any crypto they support to my email address.</li> - </ul> + let when = 'unknown'; + if (film.watchedAt) { + const d = new Date(film.watchedAt); + if (!isNaN(d)) { + const diff = Date.now() - d; + const days = Math.floor(diff / 86400000); + if (days === 0) when = 'today'; + else if (days === 1) when = '1d ago'; + else if (days < 7) when = days + 'd ago'; + else when = Math.floor(days / 7) + 'w ago'; + } else { + when = String(film.watchedAt); + } + } - </section> - --> + let posterStyle = `background: linear-gradient(135deg, oklch(${50 + idx * 10}% 0.12 ${30 + idx * 60}), oklch(${30 + idx * 8}% 0.08 ${280 - idx * 40}));`; + if (film.posterUrl) { + posterStyle = `background: url(${JSON.stringify(film.posterUrl)}) center / cover;`; + } - <section id="contact" style="padding-top: 0"> - <div class="page-header"> - <div class="well"> - <h1>Contact</h1> + return ` + <div class="film-row"> + <div class="film-poster" style="${posterStyle}"></div> + <div class="film-meta"> + <div class="film-title">${film.title} <span class="film-year">${film.year || ''}</span></div> + <div class="film-rating">${stars}<span style="opacity:0.6;margin-left:8px;">${when}${film.note ? '' : ''}</span></div> + ${film.note ? `<div class="film-note">${film.note}</div>` : ''} </div> - <center> - <img src="/img/static/dancing_baby.gif"> - <img src="/img/static/dancing_baby.gif"> - <img src="/img/static/dancing_baby.gif"> - </center> - <center> - <a href="#top">Back to top of page</a> - </center> - </div> - <p><a class="btn btn-large" href=mailto:tyler@tylerhoang.xyz>E-Mail: tyler@tylerhoang.xyz</a> - This is my public/professional email that I don't mind giving away to other people. If you want to contact me this is the primary way to do it. I'll probably respond within a day or two. As always, if you can, please encrypt the email with my GPG key. - <!-- <p><a class="btn btn-large" href=xmpp:tyler@tylerhoang.xyz>XMPP: tyler@tylerhoang.xyz</a> - My personal XMPP account hosted on my own XMPP server. If you do decide to message me this way, please use a client that supports OMEMO encryption and enable it before messaging me. I recommend using Gajim/Dino for Desktop, and Conversations for Android.--> - </section> - <div class='text-center'> - <audio id="audio" controls autoplay loop style="display: none"> - <source src='/mus/mmt.mp3' type='audio/mpeg'> - </audio> - </div> + </div> + `; + }).join(''); + } - </body> - <script> - document.getElementsByTagName('audio')[0].volume = 0.15; - </script> - <footer style="text-align: right"> - <p><b>Tyler Hoang Β© 2025</b></p> - <p></b>Hosted on <a href='https://www.vultr.com/?ref=8840975'><img src='/img/static/vultr.webp' width=75></a></b></p> - </footer> - <html lang="en"> + const statsDiv = document.getElementById('films-stats'); + if (data.total !== undefined || data.count !== undefined) { + const total = data.total || data.count; + let statsHtml = `<div><strong style="color: oklch(28% 0.08 230); font-size: 13px;">${total}</strong> watched</div>`; + if (data.thisYear !== undefined) { + statsHtml += `<div><strong style="color: oklch(28% 0.08 230); font-size: 13px;">${data.thisYear}</strong> this year</div>`; + } + statsDiv.innerHTML = statsHtml; + } + } catch (err) { + console.error('Films parse error:', err); + } + }) + .catch(err => { + console.error('Films fetch error:', err); + }); +</script> +</body> +</html> |
