diff --git a/public/about.html b/public/about.html
index 2c9ce78..24bd479 100644
--- a/public/about.html
+++ b/public/about.html
@@ -3,7 +3,7 @@
{% block extra_title %}About{% endblock %}
{% block description %}Nerconeについて{% endblock %}
{% block content %}
-
ただのアクセスカウンターです。
誰かがこのサイトのページへのアクセスに成功した場合に自動的に1増えます。(pngなどのファイルへのアクセスは加算されません)
-
詳しくはこのサーバーのソースコードへ ※キリ番報告は不要です
+
詳しくはこのサーバーのソースコードへ ※キリ番報告は不要です
{% endblock %}
diff --git a/public/assets/css/style.css b/public/assets/css/style.css
new file mode 100644
index 0000000..797193b
--- /dev/null
+++ b/public/assets/css/style.css
@@ -0,0 +1,402 @@
+html {
+ color: #E0E0E0;
+ background-color: #1A1A1A;
+ margin: 0;
+ padding: 0;
+ padding-top: 0;
+ padding-bottom: 0;
+ font-family: "Inter", "Noto Sans JP", "Noto Sans TC", "Noto Sans SC", sans-serif;
+ font-size: 12pt;
+ font-optical-sizing: auto;
+ font-weight: 200;
+ font-style: normal;
+}
+body {
+ margin: 0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 24px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ z-index: 10;
+ overflow: hidden;
+}
+header::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ backdrop-filter: blur(12px);
+ background: linear-gradient(to bottom, #1A1A1A 0%, transparent 50%), #1A1A1A80;
+ mask-image: linear-gradient(to bottom,
+ black 0%,
+ black 40%,
+ transparent 100%
+ );
+}
+main {
+ view-transition-name: main-content;
+ padding: 24px;
+ padding-top: 60px;
+ padding-bottom: 0px;
+ flex: 1;
+}
+footer {
+ view-transition-name: footer-content;
+ padding: 24px;
+ padding-top: 0px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ z-index: 10;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #FFFFFF;
+}
+
+/* Layout */
+section {
+ padding-top: 4px;
+ padding-bottom: 4px;
+}
+.block {
+ background-color: #272727;
+ border-radius: 6px;
+ padding-top: 4px;
+ padding-left: 24px;
+ padding-right: 24px;
+ padding-bottom: 4px;
+ margin-bottom: 16px;
+}
+
+.flex {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+.flex > * {
+ margin: 0;
+}
+.flex-1 {
+ flex: 1;
+}
+.flex-vertical {
+ flex-direction: column;
+}
+
+/* Code */
+pre {
+ background-color: #272727;
+ border-radius: 4px;
+ padding: 8px;
+ padding-left: 16px;
+ overflow-x: auto;
+ white-space: pre;
+ line-height: 24px;
+ font-size: small;
+ font-family: "MesloLGS Nerd Font", monospace, sans-serif;
+ font-optical-sizing: auto;
+ font-style: normal;
+}
+.block pre {
+ margin-bottom: 20px;
+}
+
+.code {
+ padding: 4px;
+ font-family: "MesloLGS Nerd Font", monospace, sans-serif;
+ font-optical-sizing: auto;
+ font-style: normal;
+}
+
+/* Fonts */
+@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Noto+Sans+JP:wght@100..900&family=Noto+Sans+SC:wght@100..900&family=Noto+Sans+TC:wght@100..900&family=Noto+Sans+KR:wght@100..900&display=swap');
+@font-face {
+ font-family: "MesloLGS Nerd Font";
+ font-style: normal;
+ font-weight: 400;
+ src:
+ local("MesloLGS Nerd Font"),
+ url("https://nercone.dev/assets/fonts/MesloLGSNerdFont-Regular.woff2") format("woff2")
+}
+@font-face {
+ font-family: "MesloLGS Nerd Font";
+ font-style: normal;
+ font-weight: 700;
+ src:
+ local("MesloLGS Nerd Font"),
+ url("https://nercone.dev/assets/fonts/MesloLGSNerdFont-Bold.woff2") format("woff2")
+}
+@font-face {
+ font-family: "MesloLGS Nerd Font";
+ font-style: italic;
+ font-weight: 400;
+ src:
+ local("MesloLGS Nerd Font"),
+ url("https://nercone.dev/assets/fonts/MesloLGSNerdFont-Italic.woff2") format("woff2")
+}
+@font-face {
+ font-family: "MesloLGS Nerd Font";
+ font-style: italic;
+ font-weight: 700;
+ src:
+ local("MesloLGS Nerd Font"),
+ url("https://nercone.dev/assets/fonts/MesloLGSNerdFont-BoldItalic.woff2") format("woff2")
+}
+
+a {
+ text-decoration: underline;
+ color: inherit;
+}
+h1 {
+ font-weight: 400;
+ line-height: 8pt;
+}
+h2 {
+ font-weight: 300;
+}
+section h2, section h3 {
+ line-height: 8pt;
+}
+
+.font-inter {
+ font-family: "Inter";
+}
+.font-nsajp {
+ font-family: "Noto Sans JP";
+}
+.font-nsatc {
+ font-family: "Noto Sans TC";
+}
+.font-nsasc {
+ font-family: "Noto Sans SC";
+}
+.font-nsakr {
+ font-family: "Noto Sans KR";
+}
+.font-meslo {
+ font-family: "MesloLGS Nerd Font";
+}
+
+.font-small {
+ font-size: small;
+}
+.font-bold {
+ font-weight: 400;
+}
+
+.text-no-decoration {
+ text-decoration: none;
+}
+.text-underline {
+ text-decoration: underline;
+}
+
+/* Colors */
+.text-white {
+ color: #FFFFFF;
+}
+
+.text-bg {
+ color: #1A1A1A;
+}
+.text-bg-alt {
+ color: #272727;
+}
+.text-tx {
+ color: #E0E0E0;
+}
+.text-tx-alt {
+ color: #939393;
+}
+
+.text-red {
+ color: #A03333;
+}
+.text-yellow {
+ color: #CCA000;
+}
+.text-green {
+ color: #00A050;
+}
+.text-teal {
+ color: #00A0A0;
+}
+.text-blue {
+ color: #0080C0;
+}
+.text-orange {
+ color: #C86000;
+}
+.text-brown {
+ color: #A07033;
+}
+.text-purple {
+ color: #7844A0;
+}
+.text-magenta {
+ color: #A043A0;
+}
+.text-indigo {
+ color: #334DA0;
+}
+
+.text-bright-red {
+ color: #C84040;
+}
+.text-bright-yellow {
+ color: #FFC800;
+}
+.text-bright-green {
+ color: #00C878;
+}
+.text-bright-teal {
+ color: #00C8C8;
+}
+.text-bright-blue {
+ color: #00C0FF;
+}
+.text-bright-orange {
+ color: #FA7800;
+}
+.text-bright-brown {
+ color: #C88C40;
+}
+.text-bright-purple {
+ color: #C84040;
+}
+.text-bright-magenta {
+ color: #C854C8;
+}
+.text-bright-indigo {
+ color: #4060C8;
+}
+
+/* Responsive Design */
+.hide {
+ display: none;
+}
+@media (max-width: 1000px) {
+ .bold-on-small {
+ font-weight: 400;
+ }
+ .hide.show-on-small {
+ display: block;
+ }
+}
+@media (min-width: 1000px) and (max-width: 1200px) {
+ .hide.show-on-medium {
+ display: block;
+ }
+}
+@media (min-width: 1200px) {
+ .hide.show-on-large {
+ display: block;
+ }
+}
+
+/* View Transition */
+@keyframes vt-fade-out {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+@keyframes vt-fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+@keyframes vt-blur-out {
+ from { filter: blur(0px); }
+ to { filter: blur(6px); }
+}
+@keyframes vt-blur-in {
+ from { filter: blur(6px); }
+ to { filter: blur(0px); }
+}
+@media (prefers-reduced-motion: reduce) {
+ ::view-transition-old(header-content),
+ ::view-transition-new(header-content) {
+ z-index: 1;
+ pointer-events: none;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ ::view-transition-old(main-content),
+ ::view-transition-new(main-content) {
+ z-index: 1;
+ pointer-events: none;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ ::view-transition-old(footer-content),
+ ::view-transition-new(footer-content) {
+ z-index: 1;
+ pointer-events: none;
+ }
+}
+
+/* Cursor */
+* { cursor: none; }
+#cursor {
+ view-transition-name: none;
+ position: fixed;
+ z-index: 99999;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+ background: #FFFFFFC0;
+ pointer-events: none;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ transition:
+ left 0.08s ease-out,
+ top 0.08s ease-out,
+ width 0.15s cubic-bezier(0.22, 1, 0.36, 1),
+ height 0.15s cubic-bezier(0.22, 1, 0.36, 1),
+ border-radius 0.15s cubic-bezier(0.22, 1, 0.36, 1),
+ background 0.15s ease,
+ transform 0.08s ease-out,
+ opacity 0.3s ease;
+}
+#cursor.visible {
+ opacity: 1;
+}
+#cursor.on-text {
+ width: 3px;
+ border-radius: 1.5px;
+ background: #FFFFFFC0;
+}
+#cursor.on-link {
+ border-radius: 6px;
+ background: #FFFFFF40;
+ transform: translate(0, 0);
+}
+
+/* Miscellaneous */
+.unselectable {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.banner {
+ height: 50px;
+ width: auto;
+ border-radius: 6px;
+}
+.social-icons {
+ width: 12pt;
+ height: 12pt;
+ display: block;
+}
diff --git a/public/assets/js/script.js b/public/assets/js/script.js
new file mode 100644
index 0000000..2e37af7
--- /dev/null
+++ b/public/assets/js/script.js
@@ -0,0 +1,179 @@
+(() => {
+ const textSelectors = 'p, h1, h2, h3, h4, h5, h6, span, li, label, td, th, pre, .code';
+ const linkSelectors = 'a, button, [role="button"], input[type="submit"], input[type="button"]';
+ const padding = 6;
+
+ let mouseX = 0, mouseY = 0;
+ let currentLinkEl = null;
+ let rafId = null;
+ let cursor = null;
+ let cursorVisible = false;
+ let lastTouchTime = 0;
+ let isMouseDown = false;
+ const TOUCH_MOUSE_GUARD_MS = 800;
+
+ function showCursor() {
+ if (!cursorVisible && cursor) {
+ cursorVisible = true;
+ cursor.classList.add('visible');
+ }
+ }
+
+ function hideCursor() {
+ if (cursor) {
+ cursorVisible = false;
+ cursor.classList.remove('visible');
+ currentLinkEl = null;
+ if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
+ cursor.classList.remove('on-link', 'on-text');
+ }
+ }
+
+ function isSyntheticMouse() {
+ return Date.now() - lastTouchTime < TOUCH_MOUSE_GUARD_MS;
+ }
+
+ function updateCursorForLink(el) {
+ const rect = el.getBoundingClientRect();
+ cursor.classList.remove('on-text');
+ cursor.classList.add('on-link');
+ cursor.style.transform = 'none';
+ cursor.style.left = (rect.left - padding) + 'px';
+ cursor.style.top = (rect.top - padding) + 'px';
+ cursor.style.width = (rect.width + padding * 2) + 'px';
+ cursor.style.height = (rect.height + padding * 2) + 'px';
+ }
+
+ function trackLink() {
+ if (currentLinkEl) {
+ updateCursorForLink(currentLinkEl);
+ rafId = requestAnimationFrame(trackLink);
+ }
+ }
+
+ document.addEventListener('DOMContentLoaded', () => {
+ cursor = document.getElementById('cursor');
+ if (!cursor) return;
+
+ document.addEventListener('touchstart', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true });
+ document.addEventListener('touchmove', () => { lastTouchTime = Date.now(); hideCursor(); }, { passive: true });
+ document.addEventListener('touchend', () => { lastTouchTime = Date.now(); }, { passive: true });
+
+ document.addEventListener('mousemove', (e) => {
+ if (isSyntheticMouse()) return;
+ if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) return;
+
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+
+ showCursor();
+
+ const el = document.elementFromPoint(mouseX, mouseY);
+ const linkEl = el ? el.closest(linkSelectors) : null;
+
+ if (linkEl) {
+ if (currentLinkEl !== linkEl) {
+ currentLinkEl = linkEl;
+ if (rafId) cancelAnimationFrame(rafId);
+ rafId = requestAnimationFrame(trackLink);
+ }
+ } else {
+ if (currentLinkEl) {
+ currentLinkEl = null;
+ if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
+ }
+ cursor.classList.remove('on-link');
+ cursor.style.transform = isMouseDown ? 'translate(-50%, -50%) scale(0.9)' : 'translate(-50%, -50%)';
+ cursor.style.left = mouseX + 'px';
+ cursor.style.top = mouseY + 'px';
+ cursor.style.width = '';
+ cursor.style.height = '';
+
+ if (el && el.closest(textSelectors)) {
+ cursor.classList.add('on-text');
+ } else {
+ cursor.classList.remove('on-text');
+ }
+ }
+ });
+
+ document.addEventListener('mousedown', () => {
+ isMouseDown = true;
+ cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(0.9)';
+ });
+ document.addEventListener('mouseup', () => {
+ isMouseDown = false;
+ cursor.style.transform = currentLinkEl ? 'none' : 'translate(-50%, -50%) scale(1)';
+ });
+
+ window.addEventListener('scroll', () => {
+ if (currentLinkEl) updateCursorForLink(currentLinkEl);
+ }, { passive: true });
+ });
+
+ if (document.startViewTransition) {
+ document.addEventListener("click", (event) => {
+ const link = event.target.closest("a");
+ if (!link || link.hasAttribute('download')) return;
+
+ const url = new URL(link.href, location.href);
+ if (url.origin !== location.origin) return;
+ if (link.target || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
+
+ if (url.hash && url.pathname === location.pathname) {
+ event.preventDefault();
+ const target = document.querySelector(url.hash);
+ if (target) {
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ history.pushState(null, '', url.hash);
+ }
+ return;
+ }
+
+ event.preventDefault();
+
+ document.startViewTransition(async () => {
+ const response = await fetch(url.href, {
+ headers: { "X-Requested-With": "view-transition" }
+ });
+ const html = await response.text();
+ const doc = new DOMParser().parseFromString(html, "text/html");
+
+ for (const tag of ['head', 'header', 'main', 'footer']) {
+ const newEl = doc.querySelector(tag);
+ const curEl = document.querySelector(tag);
+ if (!newEl || !curEl) { location.href = url.href; return; }
+ curEl.innerHTML = newEl.innerHTML;
+ curEl.querySelectorAll('script').forEach(old => {
+ const s = document.createElement('script');
+ [...old.attributes].forEach(a => s.setAttribute(a.name, a.value));
+ s.textContent = old.textContent;
+ old.replaceWith(s);
+ });
+ }
+
+ history.pushState(null, "", url.href);
+
+ currentLinkEl = null;
+ if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
+ cursor.classList.remove('on-link', 'on-text');
+ cursor.style.transform = 'translate(-50%, -50%)';
+ cursor.style.left = mouseX + 'px';
+ cursor.style.top = mouseY + 'px';
+ cursor.style.width = '';
+ cursor.style.height = '';
+
+ const el = document.elementFromPoint(mouseX, mouseY);
+ const newLinkEl = el ? el.closest(linkSelectors) : null;
+ if (newLinkEl) {
+ currentLinkEl = newLinkEl;
+ rafId = requestAnimationFrame(trackLink);
+ } else if (el && el.closest(textSelectors)) {
+ cursor.classList.add('on-text');
+ }
+ });
+ });
+
+ window.addEventListener("popstate", () => location.reload());
+ }
+})();
diff --git a/public/base.html b/public/base.html
index 6d1115c..61aedd9 100644
--- a/public/base.html
+++ b/public/base.html
@@ -36,513 +36,38 @@