From 8ad516ce67e37b27f0c68f7131949aa91b63a364 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 04:44:41 +0000 Subject: [PATCH] Rebuild static site with Sakura iced-tea theme and custom 404 Co-authored-by: VloStudios <198949883+VloStudios@users.noreply.github.com> --- README.md | 66 +++------ static/404.html | 86 ++++++++++++ static/css/style.css | 311 +++++++++++++++++++------------------------ static/index.html | 118 ++++++++-------- static/js/main.js | 282 ++++++++++++++++----------------------- 5 files changed, 408 insertions(+), 455 deletions(-) create mode 100644 static/404.html diff --git a/README.md b/README.md index 1de67fb..d720aa3 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,29 @@ -# Vlo Studios Site +# VloStudios Website (Sakura Iced Tea Remake) -A developer portfolio site for Vlo, featuring: -- Projects synced from GitHub (`VloStudios`) -- Sakura-themed CV presentation -- Iced-tea-inspired design accents -- Background ambient music toggle -- **Scrollable card layout** with GitHub-powered profile/repo data +This repository now contains a full static-first remake of the site with a brand-new structure and visual direction: -## Setup +- Sakura + iced-tea themed landing page +- Animated "raining iced tea" effect +- Mini CV section powered by live GitHub profile/repo data for `VloStudios` +- Custom themed `404.html` for GitHub Pages -1. Clone or copy this directory. -2. Create a virtual environment (optional): - ```bash - python -m venv venv - source venv/bin/activate # Windows: venv\Scripts\activate - ``` -3. Install dependencies: - ```bash - pip install -r requirements.txt - ``` -4. Run the Flask app: - ```bash - python app.py - ``` -5. Visit `http://localhost:5001` in your browser. +## Project layout -## Features +- `/static/index.html` — primary GitHub Pages entrypoint +- `/static/css/style.css` — complete redesign styles +- `/static/js/main.js` — animation + GitHub data loading + CV/repo rendering +- `/static/404.html` — custom Sakura iced-tea 404 page +- `/app.py` — optional local Flask serving -- **CV Snapshot**: Pulls profile details from `https://api.github.com/users/VloStudios`. -- **Projects Section**: Fetches public repositories and displays the most recently updated. -- **Sakura UI**: Soft pink card-based visuals with blossom-inspired background elements. -- **Ambient Background Music**: Browser-based synth music with play/pause controls. -- **Iced Tea Theme**: Dedicated section and palette accents for a calm tea vibe. +## Run locally -## Customization +```bash +pip install -r /home/runner/work/website/website/requirements.txt +python /home/runner/work/website/website/app.py +``` -- Edit `static/js/main.js` to change profile username or CV rendering behavior. -- Adjust styling in `static/css/style.css`. -- Modify the Flask app in `app.py` if you want server-rendered data endpoints. +Then open `http://localhost:5001`. -## Deployment +## GitHub Pages compatibility -The site is configured to deploy automatically to GitHub Pages via the GitHub Actions workflow (`.github/workflows/deploy.yml`). Upon each push to the `main` branch, the workflow builds and deploys the site. - -To deploy manually: -1. Push your changes to the `main` branch. -2. Go to the **Actions** tab in your repository to monitor the deployment. -3. Once completed, your site will be available at `https://.github.io//` (or custom domain if configured). - -## Custom Domain - -A `CNAME` file is included with the value `vlostudios.dev`. Ensure you configure your DNS settings accordingly. - -Enjoy the chill vibe and happy coding! \ No newline at end of file +GitHub Pages deploys from `/static` in this repo's workflow, so all published assets (including `404.html`, `CNAME`, and `.nojekyll`) are kept in `/static`. diff --git a/static/404.html b/static/404.html new file mode 100644 index 0000000..df1b414 --- /dev/null +++ b/static/404.html @@ -0,0 +1,86 @@ + + + + + + 404 — Tea Storm + + + + + + + +
+

404

+

Oops, this page drifted away in a Sakura iced-tea storm.

+

Catch a fresh cup by returning home.

+ Back to Homepage +
+ + + diff --git a/static/css/style.css b/static/css/style.css index 9b4d94f..435a2e2 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,238 +1,199 @@ :root { - --bg: #fff7fb; - --bg-soft: #ffeef4; - --ink: #4a2d3c; - --ink-soft: #7f5a6d; - --sakura: #f7b5c8; - --sakura-deep: #e98cab; - --tea: #d59f63; - --card: rgba(255, 255, 255, 0.82); - --border: rgba(233, 140, 171, 0.35); + --bg: #fff6fb; + --bg-2: #ffe8f2; + --ink: #4c2c3b; + --muted: #7f6070; + --accent: #f091b4; + --accent-strong: #db5f92; + --tea: #d9a564; + --glass: rgba(255, 255, 255, 0.82); + --line: rgba(219, 95, 146, 0.25); } -* { - box-sizing: border-box; -} +* { box-sizing: border-box; } + +html, body { min-height: 100%; } body { - margin: 0; - font-family: "Quicksand", sans-serif; - color: var(--ink); - background: - radial-gradient(circle at 10% 10%, #ffe7f0, transparent 45%), - radial-gradient(circle at 90% 80%, #ffe6d2, transparent 35%), - linear-gradient(150deg, var(--bg), var(--bg-soft)); - min-height: 100vh; + margin: 0; + font-family: "Outfit", system-ui, sans-serif; + color: var(--ink); + background: + radial-gradient(120% 100% at 0% 0%, #ffe0ee 0%, transparent 52%), + radial-gradient(90% 70% at 100% 15%, #ffe3cd 0%, transparent 45%), + linear-gradient(165deg, var(--bg), var(--bg-2)); + overflow-x: hidden; } -.petal-layer { - position: fixed; - inset: 0; - pointer-events: none; - opacity: 0.5; - background-image: - radial-gradient(circle at 20% 30%, rgba(247, 181, 200, 0.7) 0 6px, transparent 7px), - radial-gradient(circle at 80% 10%, rgba(247, 181, 200, 0.5) 0 8px, transparent 9px), - radial-gradient(circle at 60% 70%, rgba(233, 140, 171, 0.45) 0 7px, transparent 8px); - animation: drift 14s linear infinite alternate; +.rain { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 1; } -@keyframes drift { - from { transform: translateY(0px); } - to { transform: translateY(18px); } +.drop { + position: absolute; + top: -10vh; + font-size: clamp(0.8rem, 1.8vw, 1.15rem); + opacity: 0.75; + animation: pour linear infinite; } -.cv-shell { - width: min(960px, 92vw); - margin: 2.5rem auto; - display: grid; - gap: 1rem; +@keyframes pour { + to { + transform: translateY(120vh) rotate(18deg); + opacity: 0.2; + } } -.card { - background: var(--card); - border: 1px solid var(--border); - border-radius: 18px; - padding: 1.25rem; - backdrop-filter: blur(4px); - box-shadow: 0 10px 30px rgba(109, 46, 76, 0.08); +.hero, +.layout, +footer { + position: relative; + z-index: 2; } .hero { - display: flex; - gap: 1rem; - justify-content: space-between; - align-items: center; + width: min(1000px, 92vw); + margin: 2.2rem auto 1rem; + padding: 1.6rem; + border-radius: 1.2rem; + border: 1px solid var(--line); + background: var(--glass); + backdrop-filter: blur(4px); } -.eyebrow { - font-size: 0.85rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--ink-soft); - margin: 0; -} - -h1, h2 { - font-family: "Playfair Display", serif; - margin: 0.4rem 0; +.kicker { + margin: 0; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted); + font-weight: 700; + font-size: 0.8rem; } h1 { - font-size: clamp(2rem, 4vw, 2.8rem); -} - -h2 { - font-size: 1.4rem; -} - -p { - margin: 0.35rem 0; - line-height: 1.5; -} - -.avatar { - width: clamp(110px, 18vw, 150px); - height: clamp(110px, 18vw, 150px); - object-fit: cover; - border-radius: 50%; - border: 4px solid rgba(255, 255, 255, 0.9); - box-shadow: 0 0 0 5px rgba(233, 140, 171, 0.2); -} - -.chips { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin: 0.85rem 0; + margin: 0.35rem 0; + font-size: clamp(2rem, 7vw, 3.6rem); + font-family: "Zen Kurenaido", sans-serif; } -.chip { - background: rgba(247, 181, 200, 0.2); - border: 1px solid rgba(233, 140, 171, 0.35); - border-radius: 999px; - padding: 0.3rem 0.65rem; - font-size: 0.85rem; +.hero p { + margin: 0.2rem 0 0; } -.hero-links { - display: flex; - gap: 0.6rem; +.hero-actions { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.6rem; } .btn { - appearance: none; - border: none; - border-radius: 10px; - background: linear-gradient(135deg, var(--sakura), var(--sakura-deep)); - color: #fff; - padding: 0.55rem 0.9rem; - font-weight: 700; - cursor: pointer; - text-decoration: none; - display: inline-block; + border: 0; + border-radius: 999px; + padding: 0.55rem 0.95rem; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + color: #fff; + text-decoration: none; + font-weight: 700; } .btn.ghost { - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(233, 140, 171, 0.45); - color: var(--ink); + background: #fff; + color: var(--ink); + border: 1px solid var(--line); } -.stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); - gap: 0.7rem; +.layout { + width: min(1000px, 92vw); + margin: 0 auto 2rem; + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: 1rem; } -.stats article { - background: rgba(255, 255, 255, 0.66); - border-radius: 12px; - padding: 0.7rem; - border: 1px solid rgba(233, 140, 171, 0.2); +.panel { + background: var(--glass); + border: 1px solid var(--line); + border-radius: 1rem; + padding: 1rem; + box-shadow: 0 10px 25px rgba(115, 52, 78, 0.08); } -.stats h3 { - margin: 0; - font-size: 0.8rem; - color: var(--ink-soft); - text-transform: uppercase; - letter-spacing: 0.05em; +.panel h2 { + margin: 0 0 0.5rem; + font-size: 1.25rem; } -.stats p { - margin-top: 0.35rem; - font-size: 1.3rem; - font-weight: 700; +.tea-panel { + border-left: 0.4rem solid var(--tea); } -.tea-card { - border-left: 5px solid var(--tea); +.facts { + margin: 0.9rem 0 0; + display: grid; + gap: 0.5rem; } -.tea-note { - margin-top: 0.55rem; - background: rgba(213, 159, 99, 0.14); - border-radius: 10px; - padding: 0.65rem; +.facts div { + display: flex; + justify-content: space-between; + gap: 0.8rem; + border-bottom: 1px dashed rgba(76, 44, 59, 0.18); + padding-bottom: 0.3rem; } +.facts dt { color: var(--muted); } +.facts dd { margin: 0; font-weight: 700; text-align: right; } + .repo-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 0.7rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 0.7rem; } .repo-card { - background: rgba(255, 255, 255, 0.68); - border: 1px solid rgba(233, 140, 171, 0.25); - border-radius: 12px; - padding: 0.75rem; + border: 1px solid rgba(76, 44, 59, 0.14); + border-radius: 0.8rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.72); } .repo-card h3 { - margin: 0; - font-size: 1.05rem; + margin: 0 0 0.4rem; + font-size: 1rem; } -.repo-card a { - color: inherit; - text-decoration: none; +.repo-card h3 a { + color: inherit; + text-decoration: none; } -.repo-meta { - color: var(--ink-soft); - font-size: 0.85rem; +.meta, +.muted { + color: var(--muted); + font-size: 0.88rem; } -.small { - font-size: 0.88rem; - color: var(--ink-soft); +footer { + width: min(1000px, 92vw); + margin: 0 auto 2rem; + padding: 1rem; + text-align: center; + background: rgba(255, 255, 255, 0.7); + border: 1px solid var(--line); + border-radius: 1rem; } -.music-controls { - display: flex; - align-items: center; - gap: 0.7rem; +footer a { + color: var(--accent-strong); + font-weight: 700; } -.footer-card { - text-align: center; -} - -.footer-card a { - color: var(--sakura-deep); - font-weight: 700; -} - -@media (max-width: 720px) { - .hero { - flex-direction: column-reverse; - text-align: center; - } - - .hero-links, - .chips { - justify-content: center; - } +@media (max-width: 820px) { + .layout { + grid-template-columns: 1fr; + } } diff --git a/static/index.html b/static/index.html index 241cf09..5dd8ef1 100644 --- a/static/index.html +++ b/static/index.html @@ -1,79 +1,65 @@ - - - Vlo Studios | Sakura CV - - - - + + + VloStudios — Sakura Iced Tea + + + + + - + -
-
-
-

Sakura-themed portfolio

-

VloStudios

-

Brewing code like iced tea, one calm commit at a time.

-
- 🌸 Sakura Vibes - 🧋 Iced Tea Energy - 🎧 Background Music -
- -
- VloStudios avatar -
+
+

🌸 Sakura x Iced Tea

+

VloStudios

+

Refreshing projects, calm vibes, and a tiny rainstorm of iced tea.

+ +
-
-

CV Snapshot

-
-

Followers

-

-

Following

-

-

Public Repos

-

-

Location

Internet

-
-

Loading latest profile updates…

-
+
+
+

Mini CV

+

Loading profile from GitHub…

+
+
Username
-
+
Location
-
+
Followers
-
+
Public Repos
-
+
Top Languages
-
+
Profile Updated
-
+
+
-
-

Iced Tea Break

-

- Favorite workflow: chill lo-fi, clean commits, and a glass of iced tea while building cool things. -

-
Today's blend: Peach Green Tea + Cherry Blossom Mood ✨
-
+
+

Sakura Tea Notes

+
    +
  • Theme: Cherry blossom twilight + cold tea glow
  • +
  • Mood: Soft pink skies and lo-fi refresh
  • +
  • Forecast: 100% chance of raining iced tea 🧋
  • +
+
-
-

Selected GitHub Projects

-
-

Loading repositories…

-
-
+
+

Recent GitHub Projects

+
+

Loading repositories…

+
+
+
-
-

Background Music

-

Press play to start ambient lo-fi synth vibes.

-
- - Stopped -
-
+ - -
- - + diff --git a/static/js/main.js b/static/js/main.js index 562d1f8..a3c6cee 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,187 +1,133 @@ -const username = "VloStudios"; - -const el = { - name: document.getElementById("profile-name"), - bio: document.getElementById("profile-bio"), - avatar: document.getElementById("avatar"), - followers: document.getElementById("followers"), - following: document.getElementById("following"), - publicRepos: document.getElementById("public-repos"), - location: document.getElementById("location"), - profileUpdated: document.getElementById("profile-updated"), - repoGrid: document.getElementById("repo-grid"), - musicToggle: document.getElementById("music-toggle"), - musicStatus: document.getElementById("music-status") +const user = "VloStudios"; + +const ui = { + displayName: document.getElementById("display-name"), + tagline: document.getElementById("tagline"), + bio: document.getElementById("bio"), + username: document.getElementById("username"), + location: document.getElementById("location"), + followers: document.getElementById("followers"), + reposCount: document.getElementById("repos-count"), + topLanguages: document.getElementById("top-languages"), + updated: document.getElementById("updated"), + repoGrid: document.getElementById("repo-grid"), + rain: document.getElementById("rain") }; -let audioContext; -let masterGain; -let oscillators = []; -let lfo; - -function prettyDate(isoDate) { - if (!isoDate) return "Unknown"; - return new Date(isoDate).toLocaleDateString(undefined, { - year: "numeric", - month: "short", - day: "numeric" - }); +function esc(text) { + return String(text ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); } -function escapeHtml(text) { - return String(text) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); +function formatDate(iso) { + if (!iso) return "Unknown"; + return new Date(iso).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric" + }); } -async function loadProfile() { - try { - const response = await fetch(`https://api.github.com/users/${username}`); - if (!response.ok) throw new Error("Failed to load profile"); - const profile = await response.json(); - - el.name.textContent = profile.name || profile.login || username; - el.bio.textContent = profile.bio || "Builder, creator, and calm code enthusiast."; - el.avatar.src = profile.avatar_url || el.avatar.src; - el.avatar.alt = `${profile.login || username} avatar`; - el.followers.textContent = profile.followers ?? "-"; - el.following.textContent = profile.following ?? "-"; - el.publicRepos.textContent = profile.public_repos ?? "-"; - el.location.textContent = profile.location || "Online"; - el.profileUpdated.textContent = `Profile updated: ${prettyDate(profile.updated_at)}`; - } catch { - el.profileUpdated.textContent = "Could not load profile from GitHub right now."; - } +function makeRain() { + const symbols = ["🧋", "🌸", "💧"]; + const count = 36; + + for (let i = 0; i < count; i += 1) { + const d = document.createElement("span"); + d.className = "drop"; + d.textContent = symbols[i % symbols.length]; + d.style.left = `${Math.random() * 100}%`; + d.style.animationDuration = `${6 + Math.random() * 7}s`; + d.style.animationDelay = `${Math.random() * -12}s`; + ui.rain.appendChild(d); + } +} + +function topLanguagesFromRepos(repos) { + const tally = {}; + repos.forEach((r) => { + if (r?.fork || !r?.language) return; + tally[r.language] = (tally[r.language] || 0) + 1; + }); + + return Object.entries(tally) + .sort((a, b) => b[1] - a[1]) + .slice(0, 3) + .map(([name]) => name) + .join(", "); } function renderRepos(repos) { - const visible = repos - .filter((repo) => !repo.fork) - .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) - .slice(0, 6); - - if (visible.length === 0) { - el.repoGrid.innerHTML = '

No public repositories to show yet.

'; - return; - } + const cards = repos + .filter((r) => !r.fork) + .sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)) + .slice(0, 6) + .map((repo) => { + const name = esc(repo.name); + const link = esc(repo.html_url); + const description = esc(repo.description || "No description yet."); + const language = esc(repo.language || "Mixed"); + return ` +
+

${name}

+

${description}

+

${language} · ★ ${repo.stargazers_count || 0} · Updated ${formatDate(repo.updated_at)}

+
+ `; + }); - el.repoGrid.innerHTML = visible.map((repo) => { - const name = escapeHtml(repo.name); - const description = escapeHtml(repo.description || "No description provided."); - const language = escapeHtml(repo.language || "Mixed"); - const stars = Number(repo.stargazers_count || 0); - const url = escapeHtml(repo.html_url); - const updated = prettyDate(repo.updated_at); - - return ` -
-

${name}

-

${description}

-

${language} • ★ ${stars} • Updated ${updated}

-
- `; - }).join(""); -} + if (!cards.length) { + ui.repoGrid.innerHTML = '

No public repositories available right now.

'; + return; + } -async function loadRepos() { - try { - const response = await fetch(`https://api.github.com/users/${username}/repos?per_page=100`); - if (!response.ok) throw new Error("Failed to load repos"); - const repos = await response.json(); - renderRepos(repos); - } catch { - el.repoGrid.innerHTML = '

Could not load repositories right now.

'; - } + ui.repoGrid.innerHTML = cards.join(""); } -function setMusicUI(isPlaying) { - el.musicToggle.textContent = isPlaying ? "⏸ Pause Music" : "▶ Play Music"; - el.musicStatus.textContent = isPlaying ? "Playing" : "Stopped"; -} +async function loadData() { + let profile = null; + let repos = []; -function stopAmbient() { - oscillators.forEach((osc) => { - try { - osc.stop(); - } catch { - // no-op - } - }); - oscillators = []; - - if (lfo) { - try { - lfo.stop(); - } catch { - // no-op - } - lfo.disconnect(); - lfo = null; - } + try { + const [profileRes, reposRes] = await Promise.all([ + fetch(`https://api.github.com/users/${user}`), + fetch(`https://api.github.com/users/${user}/repos?per_page=100`) + ]); - if (masterGain) { - masterGain.disconnect(); - masterGain = null; + if (!profileRes.ok || !reposRes.ok) { + throw new Error("GitHub API unavailable"); } -} - -function startAmbient() { - audioContext = audioContext || new window.AudioContext(); - masterGain = audioContext.createGain(); - masterGain.gain.value = 0.04; - masterGain.connect(audioContext.destination); - - const notes = [220, 261.63, 329.63]; - oscillators = notes.map((frequency, index) => { - const osc = audioContext.createOscillator(); - const oscGain = audioContext.createGain(); - osc.type = index === 0 ? "sine" : "triangle"; - osc.frequency.value = frequency; - oscGain.gain.value = index === 0 ? 0.5 : 0.22; - osc.connect(oscGain); - oscGain.connect(masterGain); - osc.start(); - return osc; - }); - - lfo = audioContext.createOscillator(); - const lfoGain = audioContext.createGain(); - lfo.type = "sine"; - lfo.frequency.value = 0.18; - lfoGain.gain.value = 9; - lfo.connect(lfoGain); - lfoGain.connect(oscillators[1].frequency); - lfo.start(); -} -function setupMusic() { - let isPlaying = false; - setMusicUI(false); - - el.musicToggle.addEventListener("click", async () => { - if (!isPlaying) { - try { - if (audioContext && audioContext.state === "suspended") { - await audioContext.resume(); - } - startAmbient(); - isPlaying = true; - setMusicUI(true); - } catch { - el.musicStatus.textContent = "Audio blocked by browser."; - } - return; - } - - stopAmbient(); - isPlaying = false; - setMusicUI(false); - }); + profile = await profileRes.json(); + repos = await reposRes.json(); + } catch { + ui.bio.textContent = "Could not fetch GitHub live data right now, but the tea vibes remain immaculate."; + ui.username.textContent = user; + ui.location.textContent = "Internet"; + ui.followers.textContent = "-"; + ui.reposCount.textContent = "-"; + ui.topLanguages.textContent = "-"; + ui.updated.textContent = "Unavailable"; + ui.repoGrid.innerHTML = '

Unable to load repositories currently.

'; + return; + } + + ui.displayName.textContent = profile.name || profile.login || user; + ui.tagline.textContent = profile.bio || "Builder of internet things, brewed cold and served with Sakura."; + ui.bio.textContent = `${profile.name || profile.login || user} is a GitHub creator focused on shipping projects and experiments.`; + ui.username.textContent = profile.login || user; + ui.location.textContent = profile.location || "Online"; + ui.followers.textContent = String(profile.followers ?? "-"); + ui.reposCount.textContent = String(profile.public_repos ?? "-"); + ui.topLanguages.textContent = topLanguagesFromRepos(repos) || "Mixed"; + ui.updated.textContent = formatDate(profile.updated_at); + + renderRepos(repos); } -loadProfile(); -loadRepos(); -setupMusic(); +makeRain(); +loadData();