Skip to content

Commit 21633c9

Browse files
Bikram GoleBikram Gole
authored andcommitted
Add Material 3 Android mascot with BS date badge
1 parent b3b4d09 commit 21633c9

5 files changed

Lines changed: 201 additions & 11 deletions

File tree

about.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
if (selected !== savedTheme) {
3030
window.localStorage.setItem(themeKey, selected);
3131
}
32+
if (validThemes.has(savedTheme) && selected !== urlTheme) {
33+
const url = new URL(window.location.href);
34+
url.searchParams.set("theme", selected);
35+
window.history.replaceState(null, "", url.toString());
36+
}
3237
}
3338
} catch (error) {
3439
// ignore

contact.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
if (selected !== savedTheme) {
3030
window.localStorage.setItem(themeKey, selected);
3131
}
32+
if (validThemes.has(savedTheme) && selected !== urlTheme) {
33+
const url = new URL(window.location.href);
34+
url.searchParams.set("theme", selected);
35+
window.history.replaceState(null, "", url.toString());
36+
}
3237
}
3338
} catch (error) {
3439
// ignore

index.html

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
if (selected !== savedTheme) {
3030
window.localStorage.setItem(themeKey, selected);
3131
}
32+
if (validThemes.has(savedTheme) && selected !== urlTheme) {
33+
const url = new URL(window.location.href);
34+
url.searchParams.set("theme", selected);
35+
window.history.replaceState(null, "", url.toString());
36+
}
3237
}
3338
} catch (error) {
3439
// ignore
@@ -89,7 +94,7 @@ <h1 id="hero-name" data-name="Bikram Gole">Bikram Gole</h1>
8994
</div>
9095
</div>
9196

92-
<aside class="avatar-card tilt" aria-label="penguin avatar">
97+
<aside class="avatar-card tilt" aria-label="mascot avatar">
9398
<div class="avatar-glow"></div>
9499
<div class="penguin-avatar">
95100
<div class="penguin-body"></div>
@@ -107,6 +112,21 @@ <h1 id="hero-name" data-name="Bikram Gole">Bikram Gole</h1>
107112
<div class="penguin-foot left"></div>
108113
<div class="penguin-foot right"></div>
109114
</div>
115+
<div class="android-avatar" aria-hidden="true">
116+
<div class="android-head">
117+
<span class="android-antenna left"></span>
118+
<span class="android-antenna right"></span>
119+
<span class="android-eye left"></span>
120+
<span class="android-eye right"></span>
121+
</div>
122+
<div class="android-body">
123+
<div class="android-date-badge">BS --/--/--</div>
124+
</div>
125+
<span class="android-arm left"></span>
126+
<span class="android-arm right"></span>
127+
<span class="android-leg left"></span>
128+
<span class="android-leg right"></span>
129+
</div>
110130
</aside>
111131
</div>
112132
</section>

script.js

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ const quoteOutput = document.getElementById("quote-output");
145145
const signalCount = document.getElementById("signal-count");
146146
const penguinAvatar = document.querySelector(".penguin-avatar");
147147
const penguinBelly = document.querySelector(".penguin-belly");
148+
const androidDateBadge = document.querySelector(".android-date-badge");
148149
const paletteOpenBtn = document.getElementById("palette-open");
149150
let commandPalette = document.getElementById("command-palette");
150151
let commandBackdrop = document.getElementById("command-backdrop");
@@ -698,7 +699,7 @@ function initTerminalFontFallbackMode() {
698699
}
699700

700701
function initPenguinDateBadge() {
701-
if (!penguinBelly) return;
702+
if (!penguinBelly && !androidDateBadge) return;
702703

703704
let lastAdDate = "";
704705

@@ -716,14 +717,24 @@ function initPenguinDateBadge() {
716717
return `${year}-${month}-${day}`;
717718
};
718719

719-
const parseBsDay = (value) => {
720+
const parseBsDate = (value) => {
720721
if (typeof value === "string") {
721722
const match = value.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/);
722-
if (match) return String(Number.parseInt(match[3], 10));
723+
if (match) {
724+
return {
725+
year: Number.parseInt(match[1], 10),
726+
month: Number.parseInt(match[2], 10),
727+
day: Number.parseInt(match[3], 10),
728+
};
729+
}
723730
}
724731

725732
if (value && typeof value === "object" && "day" in value) {
726-
return String(Number.parseInt(value.day, 10));
733+
return {
734+
year: Number.parseInt(value.year, 10),
735+
month: Number.parseInt(value.month, 10),
736+
day: Number.parseInt(value.day, 10),
737+
};
727738
}
728739

729740
return null;
@@ -752,21 +763,31 @@ function initPenguinDateBadge() {
752763

753764
const tick = async () => {
754765
const adDate = getNepalAdDate();
755-
if (adDate === lastAdDate && penguinBelly.dataset.day) return;
766+
if (adDate === lastAdDate && (penguinBelly?.dataset.day || androidDateBadge?.dataset.bs)) return;
756767

757-
let bsDay = null;
768+
let bsDate = null;
758769
const converter = await loadBsConverter();
759770

760771
if (converter) {
761772
try {
762773
const bsValue = converter(adDate);
763-
bsDay = parseBsDay(bsValue);
774+
bsDate = parseBsDate(bsValue);
764775
} catch (error) {
765-
bsDay = null;
776+
bsDate = null;
766777
}
767778
}
768779

769-
penguinBelly.dataset.day = bsDay || getFallbackDay();
780+
const fallbackDay = getFallbackDay();
781+
if (penguinBelly) {
782+
penguinBelly.dataset.day = String(bsDate?.day || fallbackDay);
783+
}
784+
if (androidDateBadge) {
785+
const bsText = bsDate
786+
? `BS ${String(bsDate.year).padStart(4, "0")}/${String(bsDate.month).padStart(2, "0")}/${String(bsDate.day).padStart(2, "0")}`
787+
: `BS day ${fallbackDay}`;
788+
androidDateBadge.textContent = bsText;
789+
androidDateBadge.dataset.bs = bsText;
790+
}
770791
lastAdDate = adDate;
771792
};
772793

@@ -823,7 +844,8 @@ function initThemeSwitcher() {
823844
window.addEventListener("pageshow", () => {
824845
let latestTheme = "neo";
825846
try {
826-
latestTheme = window.localStorage.getItem(THEME_STORAGE_KEY) || getThemeFromUrl() || "neo";
847+
const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY);
848+
latestTheme = storedTheme || getThemeFromUrl() || "neo";
827849
} catch (error) {
828850
latestTheme = "neo";
829851
}
@@ -849,6 +871,24 @@ function initThemeSwitcher() {
849871
});
850872
}
851873

874+
function initNavThemeGuard() {
875+
const navLinks = document.querySelectorAll('a[href$=".html"], a[href*=".html?"]');
876+
navLinks.forEach((link) => {
877+
link.addEventListener("click", () => {
878+
try {
879+
const theme = THEME_OPTIONS.includes(currentTheme) ? currentTheme : "neo";
880+
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
881+
const href = link.getAttribute("href");
882+
if (href) {
883+
link.setAttribute("href", applyThemeToUrl(href, theme));
884+
}
885+
} catch (error) {
886+
// Ignore storage/link update failures.
887+
}
888+
});
889+
});
890+
}
891+
852892
function spawnBlackflagShot(startX, startY, side, targetX = null, targetY = null) {
853893
const bullet = document.createElement("span");
854894
bullet.className = "gun-bullet";
@@ -1732,6 +1772,7 @@ function initPersonaQuiz() {
17321772

17331773
initPenguinDateBadge();
17341774
initThemeSwitcher();
1775+
initNavThemeGuard();
17351776
initHeroTypewriters();
17361777
initBlackflagGunfire();
17371778
initRuntimeCompatibility();

styles.css

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,6 +1928,125 @@ body.browser-vivaldi .penguin-glasses {
19281928
}
19291929
}
19301930

1931+
.android-avatar {
1932+
display: none;
1933+
position: relative;
1934+
width: 146px;
1935+
height: 170px;
1936+
z-index: 2;
1937+
transform-origin: center 82%;
1938+
animation: android-idle 4.4s ease-in-out infinite;
1939+
filter: drop-shadow(0 12px 18px rgba(24, 94, 74, 0.3));
1940+
}
1941+
1942+
.android-head {
1943+
position: absolute;
1944+
left: 33px;
1945+
top: 18px;
1946+
width: 80px;
1947+
height: 52px;
1948+
border-radius: 36px 36px 22px 22px;
1949+
background: linear-gradient(175deg, #8ceec2 0%, #43c27e 62%, #2ca569 100%);
1950+
box-shadow: inset 0 5px 8px rgba(219, 255, 240, 0.38), inset 0 -6px 9px rgba(0, 0, 0, 0.15);
1951+
}
1952+
1953+
.android-antenna {
1954+
position: absolute;
1955+
top: -12px;
1956+
width: 3px;
1957+
height: 14px;
1958+
border-radius: 999px;
1959+
background: #8ceec2;
1960+
}
1961+
1962+
.android-antenna.left { left: 20px; transform: rotate(-22deg); }
1963+
.android-antenna.right { right: 20px; transform: rotate(22deg); }
1964+
1965+
.android-eye {
1966+
position: absolute;
1967+
top: 20px;
1968+
width: 7px;
1969+
height: 7px;
1970+
border-radius: 999px;
1971+
background: #0f3829;
1972+
}
1973+
1974+
.android-eye.left { left: 24px; }
1975+
.android-eye.right { right: 24px; }
1976+
1977+
.android-body {
1978+
position: absolute;
1979+
left: 27px;
1980+
top: 72px;
1981+
width: 92px;
1982+
height: 72px;
1983+
border-radius: 18px;
1984+
background: linear-gradient(175deg, #7ce6b6 0%, #3dbb78 62%, #2d9f66 100%);
1985+
box-shadow: inset 0 6px 8px rgba(217, 255, 238, 0.36), inset 0 -8px 10px rgba(0, 0, 0, 0.18);
1986+
}
1987+
1988+
.android-date-badge {
1989+
position: absolute;
1990+
left: 50%;
1991+
top: 24px;
1992+
transform: translateX(-50%);
1993+
min-width: 76px;
1994+
text-align: center;
1995+
border-radius: 999px;
1996+
border: 1px solid rgba(186, 255, 226, 0.76);
1997+
background: rgba(10, 57, 40, 0.78);
1998+
color: #d6ffe9;
1999+
font-family: "JetBrains Mono", "Fira Mono", "Courier New", monospace;
2000+
font-size: 10px;
2001+
font-weight: 700;
2002+
letter-spacing: 0.02em;
2003+
padding: 2px 7px;
2004+
box-shadow: 0 0 9px rgba(97, 235, 172, 0.4);
2005+
}
2006+
2007+
.android-arm,
2008+
.android-leg {
2009+
position: absolute;
2010+
border-radius: 999px;
2011+
background: linear-gradient(175deg, #76dfaf 0%, #35af71 100%);
2012+
}
2013+
2014+
.android-arm {
2015+
top: 76px;
2016+
width: 14px;
2017+
height: 52px;
2018+
}
2019+
2020+
.android-arm.left { left: 12px; }
2021+
.android-arm.right { right: 12px; }
2022+
2023+
.android-leg {
2024+
top: 136px;
2025+
width: 16px;
2026+
height: 26px;
2027+
}
2028+
2029+
.android-leg.left { left: 51px; }
2030+
.android-leg.right { left: 79px; }
2031+
2032+
body[data-theme="material3"] .avatar-glow {
2033+
background: radial-gradient(circle, rgba(144, 238, 186, 0.46) 0%, rgba(89, 214, 157, 0.17) 58%, transparent 100%);
2034+
}
2035+
2036+
body[data-theme="material3"] .penguin-avatar {
2037+
display: none;
2038+
}
2039+
2040+
body[data-theme="material3"] .android-avatar {
2041+
display: block;
2042+
}
2043+
2044+
@keyframes android-idle {
2045+
0%,
2046+
100% { transform: translateY(0) rotate(0deg); }
2047+
50% { transform: translateY(-3px) rotate(-1deg); }
2048+
}
2049+
19312050
.terminal {
19322051
margin-top: 0.52rem;
19332052
border: 1px solid rgba(255, 151, 64, 0.35);

0 commit comments

Comments
 (0)