Add PIN lock screen and block search engines
DECK and HST frontends: - Add 4-digit PIN lock screen (default: 1234) - PIN stored in sessionStorage (persists during browser session) - Keypad with keyboard support - Add meta robots noindex/nofollow tags - Created robots.txt to disallow all crawlers Security note: This is client-side only, not cryptographically secure, but sufficient to prevent casual access and search engine indexing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>deck</title>
|
<title>deck</title>
|
||||||
<meta name="description" content="DECK Tag Management System">
|
<meta name="description" content="DECK Tag Management System">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="googlebot" content="noindex, nofollow">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
DECK FRONTEND v4.6 - EventBus decoupling
|
DECK FRONTEND v4.6 - EventBus decoupling
|
||||||
@@ -618,10 +620,88 @@ body {
|
|||||||
scroll-behavior: auto !important;
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------------
|
||||||
|
* 9. LOCK SCREEN
|
||||||
|
* ----------------------------------------------------------------------------- */
|
||||||
|
.lock-screen {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.lock-screen.hidden { display: none; }
|
||||||
|
.lock-logo { font-size: 2rem; font-weight: 700; color: var(--accent); margin-bottom: 2rem; letter-spacing: 0.1em; }
|
||||||
|
.lock-dots { display: flex; gap: 12px; margin-bottom: 2rem; }
|
||||||
|
.lock-dot {
|
||||||
|
width: 14px; height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border-color);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
.lock-dot.filled { background: var(--accent); }
|
||||||
|
.lock-dot.error { background: #ff4444; animation: shake 0.3s ease; }
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
25% { transform: translateX(-4px); }
|
||||||
|
75% { transform: translateX(4px); }
|
||||||
|
}
|
||||||
|
.lock-keypad {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 70px);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.lock-key {
|
||||||
|
width: 70px; height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.lock-key:hover { border-color: var(--accent); background: var(--bg-hover); }
|
||||||
|
.lock-key:active { transform: scale(0.95); }
|
||||||
|
.lock-key.empty { visibility: hidden; }
|
||||||
|
.lock-key.backspace { font-size: 1.2rem; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<!-- LOCK SCREEN -->
|
||||||
|
<div id="lock-screen" class="lock-screen">
|
||||||
|
<div class="lock-logo">DECK</div>
|
||||||
|
<div class="lock-dots">
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="lock-keypad">
|
||||||
|
<button class="lock-key" data-key="1">1</button>
|
||||||
|
<button class="lock-key" data-key="2">2</button>
|
||||||
|
<button class="lock-key" data-key="3">3</button>
|
||||||
|
<button class="lock-key" data-key="4">4</button>
|
||||||
|
<button class="lock-key" data-key="5">5</button>
|
||||||
|
<button class="lock-key" data-key="6">6</button>
|
||||||
|
<button class="lock-key" data-key="7">7</button>
|
||||||
|
<button class="lock-key" data-key="8">8</button>
|
||||||
|
<button class="lock-key" data-key="9">9</button>
|
||||||
|
<button class="lock-key empty"></button>
|
||||||
|
<button class="lock-key" data-key="0">0</button>
|
||||||
|
<button class="lock-key backspace" data-key="back">⌫</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════════════════════════════════
|
<!-- ══════════════════════════════════════════════════════════════════════════
|
||||||
HTML STRUCTURE
|
HTML STRUCTURE
|
||||||
══════════════════════════════════════════════════════════════════════════ -->
|
══════════════════════════════════════════════════════════════════════════ -->
|
||||||
@@ -730,6 +810,91 @@ body {
|
|||||||
* EventBus pattern for module decoupling
|
* EventBus pattern for module decoupling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 0. LOCK SCREEN
|
||||||
|
// =============================================================================
|
||||||
|
const Lock = {
|
||||||
|
PIN: "1234", // PIN code - can be changed
|
||||||
|
entered: "",
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (sessionStorage.getItem("deck_unlocked") === "true") {
|
||||||
|
this.unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.bind();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
bind() {
|
||||||
|
const keypad = document.querySelector(".lock-keypad");
|
||||||
|
if (!keypad) return;
|
||||||
|
|
||||||
|
keypad.addEventListener("click", (e) => {
|
||||||
|
const key = e.target.closest(".lock-key");
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const value = key.dataset.key;
|
||||||
|
if (value === "back") {
|
||||||
|
this.entered = this.entered.slice(0, -1);
|
||||||
|
} else if (value && this.entered.length < 4) {
|
||||||
|
this.entered += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateDots();
|
||||||
|
|
||||||
|
if (this.entered.length === 4) {
|
||||||
|
setTimeout(() => this.check(), 150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard support
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (document.getElementById("lock-screen")?.classList.contains("hidden")) return;
|
||||||
|
|
||||||
|
if (e.key >= "0" && e.key <= "9" && this.entered.length < 4) {
|
||||||
|
this.entered += e.key;
|
||||||
|
this.updateDots();
|
||||||
|
if (this.entered.length === 4) setTimeout(() => this.check(), 150);
|
||||||
|
} else if (e.key === "Backspace") {
|
||||||
|
this.entered = this.entered.slice(0, -1);
|
||||||
|
this.updateDots();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDots() {
|
||||||
|
const dots = document.querySelectorAll(".lock-dot");
|
||||||
|
dots.forEach((dot, i) => {
|
||||||
|
dot.classList.toggle("filled", i < this.entered.length);
|
||||||
|
dot.classList.remove("error");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
check() {
|
||||||
|
if (this.entered === this.PIN) {
|
||||||
|
sessionStorage.setItem("deck_unlocked", "true");
|
||||||
|
this.unlock();
|
||||||
|
} else {
|
||||||
|
this.showError();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showError() {
|
||||||
|
const dots = document.querySelectorAll(".lock-dot");
|
||||||
|
dots.forEach(dot => dot.classList.add("error"));
|
||||||
|
setTimeout(() => {
|
||||||
|
this.entered = "";
|
||||||
|
this.updateDots();
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
|
||||||
|
unlock() {
|
||||||
|
const screen = document.getElementById("lock-screen");
|
||||||
|
if (screen) screen.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 1. CONFIG
|
// 1. CONFIG
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1678,7 +1843,22 @@ const App = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => App.init());
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const unlocked = Lock.init();
|
||||||
|
if (unlocked) {
|
||||||
|
App.init();
|
||||||
|
} else {
|
||||||
|
// Watch for unlock to init app
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
const screen = document.getElementById("lock-screen");
|
||||||
|
if (screen?.classList.contains("hidden")) {
|
||||||
|
observer.disconnect();
|
||||||
|
App.init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(document.getElementById("lock-screen"), { attributes: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>hst</title>
|
<title>hst</title>
|
||||||
<meta name="description" content="DECK Tag Management System">
|
<meta name="description" content="HST Tag Management System">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="googlebot" content="noindex, nofollow">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
DECK FRONTEND v4.6 - EventBus decoupling
|
DECK FRONTEND v4.6 - EventBus decoupling
|
||||||
@@ -618,10 +620,88 @@ body {
|
|||||||
scroll-behavior: auto !important;
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------------------
|
||||||
|
* 9. LOCK SCREEN
|
||||||
|
* ----------------------------------------------------------------------------- */
|
||||||
|
.lock-screen {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.lock-screen.hidden { display: none; }
|
||||||
|
.lock-logo { font-size: 2rem; font-weight: 700; color: var(--accent); margin-bottom: 2rem; letter-spacing: 0.1em; }
|
||||||
|
.lock-dots { display: flex; gap: 12px; margin-bottom: 2rem; }
|
||||||
|
.lock-dot {
|
||||||
|
width: 14px; height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--border-color);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
.lock-dot.filled { background: var(--accent); }
|
||||||
|
.lock-dot.error { background: #ff4444; animation: shake 0.3s ease; }
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
25% { transform: translateX(-4px); }
|
||||||
|
75% { transform: translateX(4px); }
|
||||||
|
}
|
||||||
|
.lock-keypad {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 70px);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.lock-key {
|
||||||
|
width: 70px; height: 70px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.lock-key:hover { border-color: var(--accent); background: var(--bg-hover); }
|
||||||
|
.lock-key:active { transform: scale(0.95); }
|
||||||
|
.lock-key.empty { visibility: hidden; }
|
||||||
|
.lock-key.backspace { font-size: 1.2rem; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<!-- LOCK SCREEN -->
|
||||||
|
<div id="lock-screen" class="lock-screen">
|
||||||
|
<div class="lock-logo">HST</div>
|
||||||
|
<div class="lock-dots">
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
<div class="lock-dot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="lock-keypad">
|
||||||
|
<button class="lock-key" data-key="1">1</button>
|
||||||
|
<button class="lock-key" data-key="2">2</button>
|
||||||
|
<button class="lock-key" data-key="3">3</button>
|
||||||
|
<button class="lock-key" data-key="4">4</button>
|
||||||
|
<button class="lock-key" data-key="5">5</button>
|
||||||
|
<button class="lock-key" data-key="6">6</button>
|
||||||
|
<button class="lock-key" data-key="7">7</button>
|
||||||
|
<button class="lock-key" data-key="8">8</button>
|
||||||
|
<button class="lock-key" data-key="9">9</button>
|
||||||
|
<button class="lock-key empty"></button>
|
||||||
|
<button class="lock-key" data-key="0">0</button>
|
||||||
|
<button class="lock-key backspace" data-key="back">⌫</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ══════════════════════════════════════════════════════════════════════════
|
<!-- ══════════════════════════════════════════════════════════════════════════
|
||||||
HTML STRUCTURE
|
HTML STRUCTURE
|
||||||
══════════════════════════════════════════════════════════════════════════ -->
|
══════════════════════════════════════════════════════════════════════════ -->
|
||||||
@@ -713,6 +793,91 @@ body {
|
|||||||
* EventBus pattern for module decoupling
|
* EventBus pattern for module decoupling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 0. LOCK SCREEN
|
||||||
|
// =============================================================================
|
||||||
|
const Lock = {
|
||||||
|
PIN: "1234", // PIN code - can be changed
|
||||||
|
entered: "",
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (sessionStorage.getItem("hst_unlocked") === "true") {
|
||||||
|
this.unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.bind();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
bind() {
|
||||||
|
const keypad = document.querySelector(".lock-keypad");
|
||||||
|
if (!keypad) return;
|
||||||
|
|
||||||
|
keypad.addEventListener("click", (e) => {
|
||||||
|
const key = e.target.closest(".lock-key");
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const value = key.dataset.key;
|
||||||
|
if (value === "back") {
|
||||||
|
this.entered = this.entered.slice(0, -1);
|
||||||
|
} else if (value && this.entered.length < 4) {
|
||||||
|
this.entered += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateDots();
|
||||||
|
|
||||||
|
if (this.entered.length === 4) {
|
||||||
|
setTimeout(() => this.check(), 150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard support
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (document.getElementById("lock-screen")?.classList.contains("hidden")) return;
|
||||||
|
|
||||||
|
if (e.key >= "0" && e.key <= "9" && this.entered.length < 4) {
|
||||||
|
this.entered += e.key;
|
||||||
|
this.updateDots();
|
||||||
|
if (this.entered.length === 4) setTimeout(() => this.check(), 150);
|
||||||
|
} else if (e.key === "Backspace") {
|
||||||
|
this.entered = this.entered.slice(0, -1);
|
||||||
|
this.updateDots();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDots() {
|
||||||
|
const dots = document.querySelectorAll(".lock-dot");
|
||||||
|
dots.forEach((dot, i) => {
|
||||||
|
dot.classList.toggle("filled", i < this.entered.length);
|
||||||
|
dot.classList.remove("error");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
check() {
|
||||||
|
if (this.entered === this.PIN) {
|
||||||
|
sessionStorage.setItem("hst_unlocked", "true");
|
||||||
|
this.unlock();
|
||||||
|
} else {
|
||||||
|
this.showError();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showError() {
|
||||||
|
const dots = document.querySelectorAll(".lock-dot");
|
||||||
|
dots.forEach(dot => dot.classList.add("error"));
|
||||||
|
setTimeout(() => {
|
||||||
|
this.entered = "";
|
||||||
|
this.updateDots();
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
|
||||||
|
unlock() {
|
||||||
|
const screen = document.getElementById("lock-screen");
|
||||||
|
if (screen) screen.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// 1. CONFIG
|
// 1. CONFIG
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1645,7 +1810,22 @@ const App = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => App.init());
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const unlocked = Lock.init();
|
||||||
|
if (unlocked) {
|
||||||
|
App.init();
|
||||||
|
} else {
|
||||||
|
// Watch for unlock to init app
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
const screen = document.getElementById("lock-screen");
|
||||||
|
if (screen?.classList.contains("hidden")) {
|
||||||
|
observer.disconnect();
|
||||||
|
App.init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(document.getElementById("lock-screen"), { attributes: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user