fixed cursor + search

This commit is contained in:
2026-05-16 17:31:39 +02:00
parent 458ef7612d
commit a731a5f50f
3 changed files with 177 additions and 31 deletions
+13 -4
View File
@@ -19,7 +19,15 @@
<i class="cs-fx-corner cs-fx-corner--tr"></i> <i class="cs-fx-corner cs-fx-corner--tr"></i>
<i class="cs-fx-corner cs-fx-corner--bl"></i> <i class="cs-fx-corner cs-fx-corner--bl"></i>
<i class="cs-fx-corner cs-fx-corner--br"></i> <i class="cs-fx-corner cs-fx-corner--br"></i>
<div class="cs-cursor"><span class="cs-cursor-ring"></span></div> </div>
<!-- Cursor lives in its own top-level, un-contained, max-z root so it floats
above every modal/library (Search + AssetManager are z-[200]). -->
<div class="cs-cursor-root" aria-hidden="true">
<div class="cs-cursor">
<span class="cs-cursor-ring"></span>
<span class="cs-cursor-core"></span>
</div>
</div> </div>
<script> <script>
@@ -28,14 +36,15 @@
if (!root.classList.contains('cybersigil')) return; if (!root.classList.contains('cybersigil')) return;
const fx = document.querySelector('.cs-fx') as HTMLElement | null; const fx = document.querySelector('.cs-fx') as HTMLElement | null;
if (!fx) return; const cursorRoot = document.querySelector('.cs-cursor-root') as HTMLElement | null;
if (!fx || !cursorRoot) return;
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const finePointer = window.matchMedia('(pointer: fine)').matches; const finePointer = window.matchMedia('(pointer: fine)').matches;
const noHover = window.matchMedia('(hover: none)').matches; const noHover = window.matchMedia('(hover: none)').matches;
/* ─── Custom sigil cursor + fading trail ─── */ /* ─── Custom sigil cursor + fading trail ─── */
const cursor = fx.querySelector('.cs-cursor') as HTMLElement | null; const cursor = cursorRoot.querySelector('.cs-cursor') as HTMLElement | null;
if (cursor && finePointer && !noHover) { if (cursor && finePointer && !noHover) {
root.classList.add('cs-cursor-on'); root.classList.add('cs-cursor-on');
let cx = window.innerWidth / 2; let cx = window.innerWidth / 2;
@@ -65,7 +74,7 @@
dot.className = 'cs-trail'; dot.className = 'cs-trail';
dot.style.left = cx + 'px'; dot.style.left = cx + 'px';
dot.style.top = cy + 'px'; dot.style.top = cy + 'px';
fx!.appendChild(dot); cursorRoot!.appendChild(dot);
dot.addEventListener('animationend', () => dot.remove(), { once: true }); dot.addEventListener('animationend', () => dot.remove(), { once: true });
} }
}, },
+9 -9
View File
@@ -153,25 +153,25 @@ export default function Search() {
{open && ( {open && (
<div <div
className="fixed inset-0 z-[200] flex items-start justify-center pt-[10vh] md:pt-[15vh] px-4" className="search-overlay fixed inset-0 z-[200] flex items-start justify-center pt-[10vh] md:pt-[15vh] px-4"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
aria-label="Search the catalogue" aria-label="Search the catalogue"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
> >
<div <div
className="absolute inset-0 bg-[var(--crust)]/55 backdrop-blur-md" className="search-backdrop absolute inset-0 bg-[var(--crust)]/55 backdrop-blur-md"
aria-hidden="true" aria-hidden="true"
/> />
<div <div
className="relative w-full max-w-xl bg-[var(--base)] text-[var(--text)] border border-[var(--surface2)] overflow-hidden flex flex-col max-h-[70vh] animate-in fade-in slide-in-from-top-4 duration-200" className="search-panel relative w-full max-w-xl bg-[var(--base)] text-[var(--text)] border border-[var(--surface2)] overflow-hidden flex flex-col max-h-[70vh] animate-in fade-in slide-in-from-top-4 duration-200"
style={{ style={{
borderRadius: 2, borderRadius: 2,
boxShadow: '0 30px 60px -20px rgba(0,0,0,0.55), 0 8px 20px -8px rgba(0,0,0,0.3), inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent)', boxShadow: '0 30px 60px -20px rgba(0,0,0,0.55), 0 8px 20px -8px rgba(0,0,0,0.3), inset 0 0 0 1px color-mix(in srgb, var(--surface1) 50%, transparent)',
}} }}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
<div className="flex items-center gap-3 px-4 py-3 border-b border-[var(--surface2)]/60 bg-[var(--surface0)]/40"> <div className="search-bar flex items-center gap-3 px-4 py-3 border-b border-[var(--surface2)]/60 bg-[var(--surface0)]/40">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="18" width="18"
@@ -196,7 +196,7 @@ export default function Search() {
onKeyDown={onInputKey} onKeyDown={onInputKey}
placeholder="Search the catalogue…" placeholder="Search the catalogue…"
aria-label="Search query" aria-label="Search query"
className="flex-1 bg-transparent outline-none text-base text-[var(--text)] placeholder:text-[var(--subtext0)] font-display italic" className="search-input flex-1 bg-transparent outline-none text-base text-[var(--text)] placeholder:text-[var(--subtext0)] font-display italic"
/> />
<kbd className="text-[10px] font-mono text-[var(--subtext0)] px-1.5 py-0.5 border border-[var(--surface2)] bg-[var(--surface0)]/60" style={{ borderRadius: 1 }}> <kbd className="text-[10px] font-mono text-[var(--subtext0)] px-1.5 py-0.5 border border-[var(--surface2)] bg-[var(--surface0)]/60" style={{ borderRadius: 1 }}>
Esc Esc
@@ -216,7 +216,7 @@ export default function Search() {
</div> </div>
)} )}
{!loading && !error && results.length > 0 && ( {!loading && !error && results.length > 0 && (
<ul ref={listRef} className="py-1"> <ul ref={listRef} className="search-list py-1">
{results.map((p, i) => { {results.map((p, i) => {
const title = p.title || formatSlug(p.slug); const title = p.title || formatSlug(p.slug);
const active = i === activeIdx; const active = i === activeIdx;
@@ -225,9 +225,9 @@ export default function Search() {
key={p.slug} key={p.slug}
onMouseEnter={() => setActiveIdx(i)} onMouseEnter={() => setActiveIdx(i)}
onClick={() => navigate(p.slug)} onClick={() => navigate(p.slug)}
className={`px-4 py-3 cursor-pointer border-l-2 transition-colors ${ className={`search-result px-4 py-3 cursor-pointer border-l-2 transition-colors ${
active active
? 'bg-[var(--surface0)] border-[var(--mauve)]' ? 'search-result--active bg-[var(--surface0)] border-[var(--mauve)]'
: 'border-transparent hover:bg-[var(--surface0)]/60' : 'border-transparent hover:bg-[var(--surface0)]/60'
}`} }`}
> >
@@ -254,7 +254,7 @@ export default function Search() {
)} )}
</div> </div>
<div className="flex items-center justify-between px-4 py-2 border-t border-[var(--surface2)]/60 text-[10px] text-[var(--subtext0)] bg-[var(--surface0)]/40 font-display italic"> <div className="search-foot flex items-center justify-between px-4 py-2 border-t border-[var(--surface2)]/60 text-[10px] text-[var(--subtext0)] bg-[var(--surface0)]/40 font-display italic">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<kbd className="font-mono px-1 border border-[var(--surface2)] bg-[var(--surface0)]/60 not-italic" style={{ borderRadius: 1 }}></kbd> <kbd className="font-mono px-1 border border-[var(--surface2)] bg-[var(--surface0)]/60 not-italic" style={{ borderRadius: 1 }}></kbd>
+155 -18
View File
@@ -1890,7 +1890,7 @@ input[type="date"] { color-scheme: light; }
--font-display: 'VT323', 'Space Mono', 'Courier New', monospace; --font-display: 'VT323', 'Space Mono', 'Courier New', monospace;
--cs-corner: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round'><path stroke-width='2.4' d='M3 3 C 26 10 33 26 36 44 C 38 60 46 70 60 78 C 74 86 84 86 97 84 M16 7 C 22 2 30 1 40 2 M14 17 C 8 24 5 33 4 45 M35 40 C 45 36 56 37 66 41 M34 49 C 30 60 30 71 33 83 M52 73 C 60 66 70 64 82 65 M58 80 C 55 90 56 97 60 99'/><path stroke-width='1' opacity='0.8' d='M24 9 L30 3 M10 31 L3 34 M41 38 L47 31 M33 61 L26 66 M71 66 L79 61 M49 55 L44 49 M22 24 L17 20'/></svg>"); --cs-corner: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round'><path stroke-width='2.4' d='M3 3 C 26 10 33 26 36 44 C 38 60 46 70 60 78 C 74 86 84 86 97 84 M16 7 C 22 2 30 1 40 2 M14 17 C 8 24 5 33 4 45 M35 40 C 45 36 56 37 66 41 M34 49 C 30 60 30 71 33 83 M52 73 C 60 66 70 64 82 65 M58 80 C 55 90 56 97 60 99'/><path stroke-width='1' opacity='0.8' d='M24 9 L30 3 M10 31 L3 34 M41 38 L47 31 M33 61 L26 66 M71 66 L79 61 M49 55 L44 49 M22 24 L17 20'/></svg>");
--cs-barb: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 22' fill='none' stroke='%23fff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M2 11 C 24 4 40 18 60 11 C 80 4 96 18 118 11'/><path stroke-width='1.3' d='M18 8 L22 1 M30 13 L27 21 M48 12 L48 2 M60 11 L60 1 M72 12 L76 20 M90 9 L87 1 M104 13 L108 21'/></svg>"); --cs-barb: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 22' fill='none' stroke='%23fff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M2 11 C 24 4 40 18 60 11 C 80 4 96 18 118 11'/><path stroke-width='1.3' d='M18 8 L22 1 M30 13 L27 21 M48 12 L48 2 M60 11 L60 1 M72 12 L76 20 M90 9 L87 1 M104 13 L108 21'/></svg>");
--cs-cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 36' fill='none' stroke='%23fff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><path d='M18 1 L18 12 M18 24 L18 35 M1 18 L12 18 M24 18 L35 18 M18 1 L15 5 M18 1 L21 5 M18 35 L15 31 M18 35 L21 31 M1 18 L5 15 M1 18 L5 21 M35 18 L31 15 M35 18 L31 21'/><path d='M18 14 L22 18 L18 22 L14 18 Z'/></svg>"); --cs-cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 36 36' fill='none' stroke='%23fff' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'><path d='M18 1 L18 11 M18 25 L18 35 M1 18 L11 18 M25 18 L35 18 M18 1 L14 6 M18 1 L22 6 M18 35 L14 30 M18 35 L22 30 M1 18 L6 14 M1 18 L6 22 M35 18 L30 14 M35 18 L30 22'/><path fill='%23fff' stroke='none' d='M18 13 L23 18 L18 23 L13 18 Z'/></svg>");
} }
/* Heads — VT323 bitmap, hard, uppercase, slight tracking so the dot-matrix /* Heads — VT323 bitmap, hard, uppercase, slight tracking so the dot-matrix
@@ -1940,14 +1940,14 @@ html.cybersigil body::after {
* Inert everywhere; only the cybersigil theme switches it on. Decorative * Inert everywhere; only the cybersigil theme switches it on. Decorative
* layers ride above content at low opacity (pointer-events:none); the sigil * layers ride above content at low opacity (pointer-events:none); the sigil
* cursor sits on top of everything. */ * cursor sits on top of everything. */
.cs-fx { display: none; } .cs-fx,
.cs-cursor-root { display: none; }
.cybersigil .cs-fx { .cybersigil .cs-fx {
display: block; display: block;
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9; z-index: 9;
pointer-events: none; pointer-events: none;
contain: strict;
} }
.cybersigil .cs-fx-halftone, .cybersigil .cs-fx-halftone,
.cybersigil .cs-fx-wire, .cybersigil .cs-fx-wire,
@@ -2024,22 +2024,31 @@ html.cybersigil body::after {
.cybersigil .cs-fx-corner--bl { bottom: 0; left: 0; transform: scaleY(-1); animation-delay: -3.4s; } .cybersigil .cs-fx-corner--bl { bottom: 0; left: 0; transform: scaleY(-1); animation-delay: -3.4s; }
.cybersigil .cs-fx-corner--br { bottom: 0; right: 0; transform: scale(-1); animation-delay: -5.1s; } .cybersigil .cs-fx-corner--br { bottom: 0; right: 0; transform: scale(-1); animation-delay: -5.1s; }
/* ─── Sigil crosshair cursor + fading trail ─── */ /* ─── Sigil crosshair cursor + fading trail ───
* Lives in its own un-contained, max-z root (CyberFx.astro) so it floats
* above every modal/library (Search + AssetManager are z-[200]). A solid
* bright core dot gives a precise aim point; a black hairline + colored
* bloom keep the sigil legible on dark void AND light asset thumbnails. */
.cybersigil .cs-cursor-root {
display: block;
position: fixed;
inset: 0;
z-index: 2147483647;
pointer-events: none;
}
.cybersigil .cs-cursor { .cybersigil .cs-cursor {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 34px; width: 42px;
height: 34px; height: 42px;
margin: -17px 0 0 -17px; margin: -21px 0 0 -21px;
z-index: 99999;
opacity: 0; opacity: 0;
will-change: transform; will-change: transform;
transition: opacity 0.16s linear; transition: opacity 0.16s linear;
} }
html.cs-cursor-on.cybersigil, html.cs-cursor-on.cybersigil,
html.cs-cursor-on.cybersigil * { cursor: none !important; } html.cs-cursor-on.cybersigil * { cursor: none !important; }
html.cs-cursor-on .cybersigil .cs-cursor,
html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; } html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; }
.cybersigil .cs-cursor-ring { .cybersigil .cs-cursor-ring {
position: absolute; position: absolute;
@@ -2047,28 +2056,54 @@ html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; }
background-color: var(--sky); background-color: var(--sky);
-webkit-mask: var(--cs-cursor) center / contain no-repeat; -webkit-mask: var(--cs-cursor) center / contain no-repeat;
mask: var(--cs-cursor) center / contain no-repeat; mask: var(--cs-cursor) center / contain no-repeat;
filter: drop-shadow(0 0 4px color-mix(in srgb, var(--sky) 65%, transparent)); filter:
drop-shadow(0 0 1px #000)
drop-shadow(0 0 1px #000)
drop-shadow(0 0 6px color-mix(in srgb, var(--sky) 80%, transparent));
transition: transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1), transition: transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1),
background-color 0.12s linear, filter 0.12s linear; background-color 0.12s linear, filter 0.12s linear;
} }
.cybersigil .cs-cursor-core {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
background: var(--rosewater);
box-shadow:
0 0 0 1.5px var(--crust),
0 0 7px 1px var(--mauve);
transition: background-color 0.12s linear, box-shadow 0.12s linear,
transform 0.12s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cybersigil .cs-cursor--hot .cs-cursor-ring { .cybersigil .cs-cursor--hot .cs-cursor-ring {
background-color: var(--mauve); background-color: var(--mauve);
transform: scale(1.32) rotate(45deg); transform: scale(1.3) rotate(45deg);
filter: drop-shadow(0 0 7px color-mix(in srgb, var(--mauve) 75%, transparent)); filter:
drop-shadow(0 0 1px #000)
drop-shadow(0 0 1px #000)
drop-shadow(0 0 9px color-mix(in srgb, var(--mauve) 85%, transparent));
} }
.cybersigil .cs-cursor--down .cs-cursor-ring { transform: scale(0.78); } .cybersigil .cs-cursor--hot .cs-cursor-core {
background: var(--sky);
box-shadow: 0 0 0 1.5px var(--crust), 0 0 9px 2px var(--sky);
transform: scale(1.25);
}
.cybersigil .cs-cursor--down .cs-cursor-ring { transform: scale(0.8); }
.cybersigil .cs-cursor--hot.cs-cursor--down .cs-cursor-ring { transform: scale(1.05) rotate(45deg); } .cybersigil .cs-cursor--hot.cs-cursor--down .cs-cursor-ring { transform: scale(1.05) rotate(45deg); }
.cybersigil .cs-cursor--down .cs-cursor-core { transform: scale(0.7); }
.cybersigil .cs-cursor--gone { opacity: 0; } .cybersigil .cs-cursor--gone { opacity: 0; }
.cybersigil .cs-trail { .cybersigil .cs-trail {
position: fixed; position: fixed;
width: 5px; width: 6px;
height: 5px; height: 6px;
margin: -2.5px 0 0 -2.5px; margin: -3px 0 0 -3px;
z-index: 99998;
pointer-events: none; pointer-events: none;
background: var(--sky); background: var(--sky);
box-shadow: 0 0 5px 1px color-mix(in srgb, var(--sky) 70%, transparent);
transform: rotate(45deg); transform: rotate(45deg);
animation: cs-trail-fade 480ms linear forwards; animation: cs-trail-fade 460ms linear forwards;
} }
/* Selection — magenta block, bone glyph, cyan bleed. */ /* Selection — magenta block, bone glyph, cyan bleed. */
@@ -2667,6 +2702,108 @@ html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; }
} }
.cybersigil input[type="date"] { color-scheme: dark; } .cybersigil input[type="date"] { color-scheme: dark; }
/* Search palette — a terminal query console. Hard edges, ice→magenta cap
* bar, `> ` prompt input + live caret, scanline body, bracketed results. */
.cybersigil .search-backdrop {
background: color-mix(in srgb, var(--crust) 78%, transparent);
}
.cybersigil .search-panel {
border-radius: 0 !important;
border-color: var(--mauve);
background:
repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0) 0 2px,
color-mix(in srgb, var(--sky) 5%, transparent) 2px 3px,
rgba(0, 0, 0, 0) 3px 4px
),
var(--base);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--sky) 30%, transparent),
8px 8px 0 0 color-mix(in srgb, var(--mauve) 40%, var(--crust)),
0 30px 70px -22px rgba(0, 0, 0, 0.9) !important;
}
.cybersigil .search-panel::before {
content: "// SEARCH.CATALOGUE";
display: flex;
align-items: center;
height: 1.4rem;
padding: 0 0.7rem;
font-family: var(--font-sans);
font-size: 0.6rem;
letter-spacing: 0.2em;
color: var(--crust);
background: linear-gradient(90deg, var(--sky), var(--mauve));
}
.cybersigil .search-bar {
background: color-mix(in srgb, var(--crust) 55%, transparent);
border-bottom-color: color-mix(in srgb, var(--sky) 35%, transparent);
}
.cybersigil .search-bar svg { color: var(--sky) !important; }
.cybersigil .search-input {
font-family: var(--font-sans) !important;
font-style: normal !important;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.95rem !important;
color: var(--sky) !important;
caret-color: var(--mauve);
}
.cybersigil .search-bar::before {
content: ">";
order: -1;
color: var(--mauve);
font-family: var(--font-sans);
font-weight: 700;
}
.cybersigil .search-result {
border-left-width: 2px;
font-family: var(--font-sans);
}
.cybersigil .search-result:hover {
background: color-mix(in srgb, var(--sky) 9%, transparent) !important;
border-left-color: var(--sky) !important;
}
.cybersigil .search-result--active {
background: color-mix(in srgb, var(--mauve) 14%, transparent) !important;
border-left-color: var(--mauve) !important;
}
.cybersigil .search-result [class*="font-display"] {
font-family: var(--font-display) !important;
font-style: normal !important;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.cybersigil .search-result--active [class*="font-display"] {
text-shadow: -1px 0 0 var(--sky), 1px 0 0 var(--mauve);
}
.cybersigil .search-result--active::after {
content: "_";
color: var(--mauve);
font-family: var(--font-sans);
animation: cs-blink 1.05s steps(1, jump-none) infinite;
}
.cybersigil .search-result [class*="line-clamp"] {
font-family: var(--font-sans) !important;
font-style: normal !important;
}
.cybersigil .search-foot {
background: color-mix(in srgb, var(--crust) 55%, transparent);
border-top-color: color-mix(in srgb, var(--sky) 30%, transparent);
font-family: var(--font-sans) !important;
font-style: normal !important;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--subtext0);
}
.cybersigil .search-panel kbd {
border-radius: 0 !important;
border-color: var(--mauve) !important;
background: var(--crust) !important;
color: var(--sky) !important;
font-family: var(--font-sans) !important;
}
/* Confirm dialog — hard terminal halt prompt. */ /* Confirm dialog — hard terminal halt prompt. */
.cybersigil .cdialog-panel { .cybersigil .cdialog-panel {
border-radius: 0; border-radius: 0;