Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/projects/more/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "More steamers on the next cart · 敬請期待"
title: "More work coming soon"
summary: More work is on the way — this slot exists so the room to grow is visible.
date: 2025-12-31
tags:
Expand Down
2 changes: 2 additions & 0 deletions layouts/_partials/hooks/head-end/custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@
}
}
</style>
<link rel="stylesheet" href="/dimension-switch.css">
<script defer src="/dimension-switch.js"></script>
131 changes: 131 additions & 0 deletions static/dimension-switch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
.dimension-corner-link {
position: fixed;
z-index: 1000;
top: 76px;
right: 20px;
display: inline-flex;
min-height: 38px;
align-items: center;
padding: 0 16px;
border: 1px solid rgba(99, 102, 241, 0.28);
border-radius: 999px;
color: #3730a3;
background: rgba(255, 255, 255, 0.93);
box-shadow: 0 8px 25px rgba(79, 70, 229, 0.14);
font-size: 12px;
font-weight: 700;
text-decoration: none;
backdrop-filter: blur(12px);
}

.dimension-corner-link:hover {
color: #fff;
background: #4f46e5;
}

.dimension-corner-link:focus-visible,
.dimension-choice-action:focus-visible {
outline: 3px solid #4f46e5;
outline-offset: 3px;
}

.dimension-choice {
position: fixed;
inset: 0;
width: min(680px, calc(100% - 32px));
max-width: none;
max-height: calc(100% - 32px);
margin: auto;
padding: clamp(28px, 6vw, 54px);
overflow: auto;
border: 1px solid rgba(255, 255, 255, 0.95);
border-radius: 24px;
color: #252044;
background: linear-gradient(145deg, #fff, #f5f3ff 64%, #e8f5ff);
box-shadow: 0 30px 90px rgba(20, 20, 65, 0.33);
}

.dimension-choice::backdrop {
background: rgba(18, 20, 50, 0.7);
backdrop-filter: blur(8px);
}

.dimension-choice [hidden] {
display: none;
}

.dimension-choice-kicker {
margin: 0 0 24px;
color: #5046bd;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
}

.dimension-choice h1 {
margin: 0;
color: #251d59;
font-size: clamp(36px, 6vw, 56px);
font-weight: 750;
line-height: 1.08;
}

.dimension-choice-lead {
margin: 22px 0 0;
color: #514d70;
font-size: 17px;
line-height: 1.7;
}

.dimension-choice-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 32px;
}

.dimension-choice-actions.welcome {
grid-template-columns: minmax(0, 270px);
}

.dimension-choice-action {
display: flex;
min-height: 58px;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 16px;
border-radius: 14px;
font: inherit;
font-size: 14px;
font-weight: 700;
line-height: 1.35;
text-align: center;
text-decoration: none;
cursor: pointer;
}

.dimension-choice-action.primary {
border: 1px solid #4f46e5;
color: #fff;
background: linear-gradient(135deg, #6366f1, #4338ca);
}

.dimension-choice-action.secondary {
border: 1px solid #b8b4df;
color: #3730a3;
background: #fff;
}

.dimension-choice-note {
margin: 22px 0 0;
color: #716c8a;
font-size: 12px;
}

@media (max-width: 600px) {
.dimension-corner-link { top: 68px; right: 12px; padding: 0 12px; }
.dimension-choice-actions,
.dimension-choice-actions.welcome { grid-template-columns: 1fr; }
.dimension-choice-lead { font-size: 15px; }
}
63 changes: 63 additions & 0 deletions static/dimension-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
(() => {
const carUrl = "https://car.jackiectl.com/?view=3d";

function init() {
const switchLink = document.createElement("a");
switchLink.className = "dimension-corner-link";
switchLink.href = carUrl;
switchLink.textContent = "3D Website ↗";
switchLink.setAttribute("aria-label", "Switch to the 3D website");
document.body.appendChild(switchLink);

const url = new URL(window.location.href);
const enter2d = url.searchParams.get("view") === "2d";
if (enter2d) {
url.searchParams.delete("view");
window.history.replaceState(window.history.state, "", url.pathname + url.search + url.hash);
}

const isHome = window.location.pathname === "/" || window.location.pathname === "/index.html";
if (!isHome || enter2d) return;

const dialog = document.createElement("dialog");
dialog.className = "dimension-choice";
dialog.setAttribute("aria-labelledby", "dimension-welcome-title");
dialog.innerHTML = `
<section data-stage="welcome">
<p class="dimension-choice-kicker">TIANLANG (JACKIE) CHEN · PERSONAL WEBSITE</p>
<h1 id="dimension-welcome-title">Welcome to Jackie's Website.</h1>
<p class="dimension-choice-lead">This is my personal website. You can explore it in 3D or 2D.</p>
<div class="dimension-choice-actions welcome">
<button class="dimension-choice-action primary" type="button" data-continue>Continue <span aria-hidden="true">→</span></button>
</div>
</section>
<section data-stage="choice" hidden>
<p class="dimension-choice-kicker">TIANLANG (JACKIE) CHEN · PERSONAL WEBSITE</p>
<h1 id="dimension-choice-title" tabindex="-1">Choose a version.</h1>
<p class="dimension-choice-lead">How would you like to explore my website?</p>
<div class="dimension-choice-actions">
<a class="dimension-choice-action primary" href="${carUrl}">Enter 3D Website <span aria-hidden="true">↗</span></a>
<button class="dimension-choice-action secondary" type="button" data-enter-2d>Enter 2D Website <span aria-hidden="true">→</span></button>
</div>
</section>
`;
document.body.appendChild(dialog);
dialog.querySelector("[data-continue]").addEventListener("click", () => {
dialog.querySelector('[data-stage="welcome"]').hidden = true;
dialog.querySelector('[data-stage="choice"]').hidden = false;
dialog.setAttribute("aria-labelledby", "dimension-choice-title");
dialog.querySelector("#dimension-choice-title").focus();
});
dialog.querySelector("[data-enter-2d]").addEventListener("click", () => {
dialog.close();
dialog.remove();
});
dialog.showModal();
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init, { once: true });
} else {
init();
}
})();
Loading