diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-26 20:02:20 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-05-26 20:02:20 -0700 |
| commit | c91b67479fb8a6ba1835ba493da013a6c9730ee4 (patch) | |
| tree | 3094535ec52d209b467e13cc350f1477573a264a /index.html | |
| parent | 66802e23d77a23b72ac786d049a6ce653c5150eb (diff) | |
browser: add article reader window with themed fetch+inject
- new Articles icon (π) on desktop opens w-browser window
- browser window has URL bar + bookmark bar (library, music list, software)
- fetch()es article HTML, strips Bootstrap stylesheet + navbar, preserves content
- blink tags unwrapped (colored spans preserved for retro feel)
- full aero + chrome theme CSS for url-bar, bookmarks, article typography
- uses DOMParser to cleanly extract body content without iframes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'index.html')
| -rwxr-xr-x | index.html | 116 |
1 files changed, 113 insertions, 3 deletions
@@ -113,6 +113,69 @@ .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; } + + /* ===== BROWSER / ARTICLE READER ===== */ + .browser-toolbar { + display: flex; align-items: center; gap: 8px; + padding: 6px 12px; + background: rgba(255,255,255,0.22); + border-bottom: 1px solid rgba(200,220,240,0.45); + } + .browser-url-bar { + flex: 1; height: 22px; padding: 0 10px; + border-radius: 11px; + background: rgba(255,255,255,0.62); border: 1px solid rgba(180,200,220,0.45); + font: 400 11px 'IBM Plex Mono', monospace; color: oklch(30% 0.05 230); + display: flex; align-items: center; overflow: hidden; + white-space: nowrap; text-overflow: ellipsis; + } + .browser-bookmarks { + display: flex; gap: 5px; padding: 5px 12px; + background: rgba(255,255,255,0.15); + border-bottom: 1px solid rgba(200,220,240,0.35); + flex-wrap: wrap; + } + .browser-bm { + height: 20px; padding: 0 10px; + border-radius: 10px; border: 1px solid rgba(180,200,220,0.4); + background: rgba(255,255,255,0.55); + font: 600 10px 'Plus Jakarta Sans', sans-serif; color: oklch(30% 0.08 230); + cursor: pointer; letter-spacing: 0.3px; + transition: background 150ms, border-color 150ms; + } + .browser-bm:hover { background: rgba(255,255,255,0.88); } + .browser-bm.active { background: rgba(255,255,255,0.92); border-color: oklch(55% 0.12 220); color: oklch(22% 0.10 230); } + .browser-body { + overflow-y: auto; max-height: 440px; + padding: 16px 20px; border-radius: 0 0 18px 18px; + } + /* article typography inside the browser */ + .browser-body h1 { font-size: 17px; font-weight: 700; margin: 0 0 10px; color: oklch(24% 0.08 230); } + .browser-body h2 { font-size: 14px; font-weight: 700; margin: 16px 0 6px; color: oklch(27% 0.08 230); border-bottom: 1px solid rgba(120,160,200,0.3); padding-bottom: 4px; } + .browser-body h3 { font-size: 13px; font-weight: 600; margin: 12px 0 5px; color: oklch(29% 0.07 230); } + .browser-body h4 { font-size: 12px; font-weight: 600; margin: 10px 0 4px; color: oklch(31% 0.06 230); font-style: italic; } + .browser-body p { margin: 0 0 9px; font-size: 12px; line-height: 1.65; color: oklch(22% 0.04 240); } + .browser-body ul, .browser-body ol { padding-left: 18px; margin: 0 0 9px; } + .browser-body li { font-size: 12px; line-height: 1.7; color: oklch(22% 0.04 240); } + .browser-body img { max-width: 160px; border-radius: 8px; margin: 6px 0; display: block; box-shadow: 0 2px 8px rgba(40,80,140,0.15); } + .browser-body figure { margin: 8px 0; } + .browser-body figcaption { font-size: 10px; opacity: 0.65; margin-top: 3px; } + .browser-body a { color: oklch(42% 0.16 255); text-decoration: none; } + .browser-body a:hover { text-decoration: underline; } + .browser-body pre, .browser-body .body > p { font: 12px 'IBM Plex Mono', monospace; line-height: 1.65; white-space: pre-wrap; color: oklch(22% 0.04 240); } + .browser-body center { text-align: center; } + /* chrome theme overrides for browser */ + body[data-theme="chrome"] .browser-url-bar { background: rgba(195,202,215,0.5); border-color: oklch(50% 0.04 250); color: oklch(14% 0.03 250); } + body[data-theme="chrome"] .browser-bookmarks { background: rgba(195,202,215,0.15); border-bottom-color: oklch(60% 0.03 250); } + body[data-theme="chrome"] .browser-bm { font-family: 'Michroma', sans-serif; font-size: 8px; text-transform: uppercase; letter-spacing: 1px; border-color: oklch(55% 0.04 250); background: linear-gradient(to bottom, oklch(92% 0.01 240), oklch(78% 0.02 240)); color: oklch(18% 0.04 250); } + body[data-theme="chrome"] .browser-bm:hover { background: linear-gradient(to bottom, oklch(97% 0.01 240), oklch(86% 0.02 240)); } + body[data-theme="chrome"] .browser-bm.active { background: linear-gradient(to bottom, oklch(99% 0.01 240), oklch(90% 0.02 240)); border-color: oklch(40% 0.10 280); } + body[data-theme="chrome"] .browser-body h1, + body[data-theme="chrome"] .browser-body h2, + body[data-theme="chrome"] .browser-body h3, + body[data-theme="chrome"] .browser-body h4 { font-family: 'Michroma', sans-serif; text-transform: uppercase; letter-spacing: 0.5px; } + body[data-theme="chrome"] .browser-body p, + body[data-theme="chrome"] .browser-body li { font-family: 'Space Grotesk', sans-serif; } </style> </head> <body> @@ -131,6 +194,7 @@ <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 class="icon" data-open="browser"><div class="glyph" style="background: var(--icon-silver)">π</div><div class="label">Articles</div></div> </div> <!-- WINDOWS --> @@ -151,7 +215,7 @@ </div> </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 0 8px;">Hi! I'm Tyler, 24, Vietnamese-American, and this little corner of the internet is where I keep the un-LinkedIn version of myself. Banking pays the bills, but tinkering with servers, chasing chord voicings, and cooking dishes 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> @@ -171,7 +235,7 @@ </div> <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="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 β trollshotlol</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> @@ -312,6 +376,22 @@ </div> </div> + <!-- BROWSER / ARTICLE READER --> + <div class="win glass" id="w-browser" style="left: 200px; top: 80px; width: 580px; display: none;"> + <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> + <div class="browser-url-bar no-drag" id="browser-url">about:blank</div> + </div> + <div class="browser-bookmarks no-drag"> + <button class="browser-bm no-drag" data-src="articles/library.html" data-title="Personal Library">π library</button> + <button class="browser-bm no-drag" data-src="articles/music.html" data-title="Music List">π΅ music list</button> + <button class="browser-bm no-drag" data-src="articles/software.html" data-title="Software & Hardware">π» software</button> + </div> + <div class="body browser-body" id="browser-content"> + <div style="opacity:0.5;text-align:center;padding:40px 0;font-size:12px;">β click a bookmark to load an article</div> + </div> + </div> + <!-- TASKBAR --> <div class="taskbar"> <div class="start"><span style="font-size:16px;">β</span> tyler</div> @@ -393,7 +473,7 @@ // music toggle + volume slider (theme-aware) const MUSIC = { aero: { src: '/mus/bazaar-theme.mp3', label: 'βͺ a-dog β bazaar theme' }, - chrome: { src: '/CoolMan - WhoIsUsingThisComputer_.mp3', label: 'βͺ coolman β who is using this computer?' }, + chrome: { src: '/mus/CoolMan - WhoIsUsingThisComputer_.mp3', label: 'βͺ coolman β who is using this computer?' }, }; document.getElementById('mt').innerHTML = Aero.musicToggleHTML(); const mtDiv = document.getElementById('mt'); @@ -517,6 +597,36 @@ }); } + // browser article loader + function browserLoad(src) { + const content = document.getElementById('browser-content'); + const urlBar = document.getElementById('browser-url'); + document.querySelectorAll('.browser-bm').forEach(b => b.classList.toggle('active', b.dataset.src === src)); + urlBar.textContent = 'fun.tylerhoang.xyz/' + src; + content.innerHTML = '<div style="opacity:0.5;text-align:center;padding:30px 0;font-size:12px;">loadingβ¦</div>'; + fetch(src) + .then(r => { if (!r.ok) throw new Error(r.status); return r.text(); }) + .then(html => { + const doc = new DOMParser().parseFromString(html, 'text/html'); + // strip stylesheets, scripts, old styles + doc.querySelectorAll('link[rel="stylesheet"], style, script').forEach(el => el.remove()); + // strip Bootstrap navbar component (the nav bar itself, not the page wrapper) + doc.querySelectorAll('div.navbar').forEach(el => el.remove()); + // strip blink tags, preserving inner HTML (colored spans etc.) + doc.querySelectorAll('blink').forEach(el => { + el.replaceWith(...Array.from(el.childNodes)); + }); + content.innerHTML = doc.body.innerHTML; + content.scrollTop = 0; + }) + .catch(() => { + content.innerHTML = '<div style="opacity:0.5;text-align:center;padding:30px 0;font-size:12px;">couldn\'t load article</div>'; + }); + } + document.querySelectorAll('.browser-bm').forEach(bm => { + bm.addEventListener('click', () => browserLoad(bm.dataset.src)); + }); + // fetch films Aero.fetchFilms() .then(data => { |
