diff --git a/frontend/src/components/CyberFx.astro b/frontend/src/components/CyberFx.astro
index 2afe9f1..29124eb 100644
--- a/frontend/src/components/CyberFx.astro
+++ b/frontend/src/components/CyberFx.astro
@@ -103,6 +103,7 @@
const hudTL = fx.querySelector('.cs-hud--tl');
const hudTR = fx.querySelector('.cs-hud--tr');
+ const hudBL = fx.querySelector('.cs-hud--bl');
const apply = () => {
raf = 0;
@@ -118,6 +119,46 @@
if (hudTL) hudTL.textContent = `0x${Math.floor(depth * 255).toString(16).toUpperCase().padStart(2, '0')} // ADDR`;
if (hudTR) hudTR.textContent = `SYS.PTR // ${mx.toFixed(2)}, ${my.toFixed(2)}`;
};
+
+ /* ─── Terminal Command Echo ─── */
+ const onBtnClick = (e: MouseEvent) => {
+ const btn = (e.target as HTMLElement).closest('button, a.btn');
+ if (!btn || !hudBL) return;
+ const label = btn.textContent?.trim().slice(0, 12).toUpperCase() || 'NULL';
+ hudBL.textContent = `> CMD: [${label}] ... [OK]`;
+ hudBL.classList.remove('cs-hud-flicker');
+ void hudBL.offsetWidth;
+ hudBL.classList.add('cs-hud-flicker');
+ };
+ window.addEventListener('click', onBtnClick);
+ off.push(() => window.removeEventListener('click', onBtnClick));
+
+ /* ─── Character Scramble ─── */
+ const scrambleChars = '!@#$%^&*()_+{}:"<>?-=[];\',./';
+ const onHover = (e: MouseEvent) => {
+ const el = (e.target as HTMLElement).closest('.font-display, .btn, .prose h1, .prose h2');
+ if (!el || el.classList.contains('cs-is-scrambling')) return;
+
+ const original = el.textContent || '';
+ if (!original.trim()) return;
+
+ el.classList.add('cs-is-scrambling');
+ let iterations = 0;
+ const interval = setInterval(() => {
+ el.textContent = original.split('').map((char, index) => {
+ if (index < iterations) return original[index];
+ return scrambleChars[Math.floor(Math.random() * scrambleChars.length)];
+ }).join('');
+
+ if (iterations >= original.length) {
+ clearInterval(interval);
+ el.classList.remove('cs-is-scrambling');
+ }
+ iterations += 1 / 3;
+ }, 30);
+ };
+ window.addEventListener('mouseover', onHover);
+ off.push(() => window.removeEventListener('mouseover', onHover));
const schedule = () => { if (!raf) raf = requestAnimationFrame(apply); };
const onScroll = () => {
diff --git a/frontend/src/styles/partials/70-cybersigil.css b/frontend/src/styles/partials/70-cybersigil.css
index 815747a..b14e071 100644
--- a/frontend/src/styles/partials/70-cybersigil.css
+++ b/frontend/src/styles/partials/70-cybersigil.css
@@ -81,6 +81,24 @@ html.cybersigil body::after {
.cybersigil .cs-hud--bl { bottom: 1.5rem; left: 1.5rem; }
.cybersigil .cs-hud--br { bottom: 1.5rem; right: 1.5rem; }
+.cs-hud-flicker {
+ animation: cs-flicker 0.4s steps(4) 1;
+}
+
+/* Barbed Borders (Stitched Wire) */
+.cybersigil .plate,
+.cybersigil .btn,
+.cybersigil .glass {
+ border-image-source: url("data:image/svg+xml;utf8,");
+ border-image-slice: 10;
+ border-image-repeat: stretch;
+}
+.cybersigil .plate:hover,
+.cybersigil .btn:hover {
+ border-image-source: url("data:image/svg+xml;utf8,");
+ animation: cs-flicker 0.2s infinite;
+}
+
/* Boot Overlay */
.cybersigil .cs-boot {
position: fixed;
@@ -95,10 +113,12 @@ html.cybersigil body::after {
}
.cybersigil .cs-boot-log {
font-family: var(--font-display);
- font-size: 1.2rem;
+ font-size: clamp(0.9rem, 4vw, 1.2rem);
color: var(--sky);
line-height: 1.4;
text-shadow: 0 0 8px var(--sky);
+ max-width: 90vw;
+ padding: 1rem;
}
.cybersigil .cs-boot-log p {
overflow: hidden;
@@ -113,7 +133,8 @@ html.cybersigil body::after {
@keyframes cs-boot-fade {
0% { opacity: 1; visibility: visible; }
- 100% { opacity: 0; visibility: hidden; }
+ 99% { opacity: 0; visibility: visible; }
+ 100% { opacity: 0; visibility: hidden; display: none; }
}
@keyframes cs-boot-type {
to { width: 100%; }