init elas atelier #1
@@ -14,8 +14,10 @@
|
|||||||
<div class="cs-fx-halftone"></div>
|
<div class="cs-fx-halftone"></div>
|
||||||
<div class="cs-fx-wire"></div>
|
<div class="cs-fx-wire"></div>
|
||||||
<div class="cs-fx-tear"></div>
|
<div class="cs-fx-tear"></div>
|
||||||
<i class="cs-fx-edge cs-fx-edge--l"></i>
|
<i class="cs-fx-corner cs-fx-corner--tl"></i>
|
||||||
<i class="cs-fx-edge cs-fx-edge--r"></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--br"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -27,25 +29,23 @@
|
|||||||
|
|
||||||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
/* ─── Tall sigil vines pinned to the left/right screen edges ─── */
|
/* ─── Generated sigil growths in the four corners ─── */
|
||||||
/* Built once and shared by both edges so left/right are symmetric;
|
/* One sigil per page load; the four corner transforms in global.css
|
||||||
* the right edge is flipped in CSS (scaleX(-1)). Edge-mode crops the
|
* (scaleX / scaleY / scale) splay it into each corner. */
|
||||||
* SVG to the inward half + stretches it to fill the ribbon, so it
|
const corners = document.querySelectorAll<HTMLElement>('.cs-fx-corner');
|
||||||
* reads as an inward-creeping border vine. */
|
if (corners.length) {
|
||||||
const edges = document.querySelectorAll<HTMLElement>('.cs-fx-edge');
|
const svg = buildCybersigil();
|
||||||
if (edges.length) {
|
corners.forEach((c) => {
|
||||||
const vine = buildCybersigil({ count: 22, spineWave: 4, edge: true });
|
if (c.classList.contains('cs-fx-corner--sig')) return;
|
||||||
edges.forEach((e) => {
|
c.innerHTML = svg;
|
||||||
if (e.classList.contains('cs-fx-edge--sig')) return;
|
c.classList.add('cs-fx-corner--sig');
|
||||||
e.innerHTML = vine;
|
|
||||||
e.classList.add('cs-fx-edge--sig');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── One slow-spinning sigil filling the background ─── */
|
/* ─── One slow-spinning sigil filling the background ─── */
|
||||||
const wire = document.querySelector<HTMLElement>('.cs-fx-wire');
|
const wire = document.querySelector<HTMLElement>('.cs-fx-wire');
|
||||||
if (wire && !wire.classList.contains('cs-fx-wire--sig')) {
|
if (wire && !wire.classList.contains('cs-fx-wire--sig')) {
|
||||||
wire.innerHTML = buildCybersigil({ count: 20 });
|
wire.innerHTML = buildCybersigil({ count: 9 });
|
||||||
wire.classList.add('cs-fx-wire--sig');
|
wire.classList.add('cs-fx-wire--sig');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
|
|||||||
<span>{formatYear(post.date)}</span>
|
<span>{formatYear(post.date)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PlateSigil />
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{post.tags && post.tags.length > 0 && (
|
{post.tags && post.tags.length > 0 && (
|
||||||
@@ -239,6 +237,8 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<PlateSigil />
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe('buildCybersigil', () => {
|
|||||||
const dense = buildCybersigil({ count: 9, rng: seeded(5) });
|
const dense = buildCybersigil({ count: 9, rng: seeded(5) });
|
||||||
const n = (s: string) => (s.match(/<path/g) ?? []).length;
|
const n = (s: string) => (s.match(/<path/g) ?? []).length;
|
||||||
expect(n(dense)).toBeGreaterThan(n(sparse));
|
expect(n(dense)).toBeGreaterThan(n(sparse));
|
||||||
expect(n(dense)).toBeLessThanOrEqual(440); // MAX_PATHS(200) mirrored + ornaments
|
expect(n(dense)).toBeLessThanOrEqual(260); // MAX_PATHS(110) mirrored + ornaments
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ships a non-empty glyph library with valid path data', () => {
|
it('ships a non-empty glyph library with valid path data', () => {
|
||||||
|
|||||||
@@ -36,25 +36,11 @@ export interface SigilOptions {
|
|||||||
count?: number;
|
count?: number;
|
||||||
/** injectable RNG (0..1); default Math.random */
|
/** injectable RNG (0..1); default Math.random */
|
||||||
rng?: () => number;
|
rng?: () => number;
|
||||||
/**
|
|
||||||
* Spine sinuosity. Default `1` keeps the original gentle one-sided bow
|
|
||||||
* (byte-identical output). Values >1 make the spine weave inward and
|
|
||||||
* back (staying ≥0 so it never crosses the edge clip) with amplitude
|
|
||||||
* ×spineWave — a serpentine vine for tall edge ribbons.
|
|
||||||
*/
|
|
||||||
spineWave?: number;
|
|
||||||
/**
|
|
||||||
* Edge mode: crop the viewBox to the inward half (spine pinned at x=0)
|
|
||||||
* and emit a single half with `preserveAspectRatio="none"`, so the
|
|
||||||
* figure stretches to fill a tall narrow border ribbon instead of
|
|
||||||
* scaling uniformly and overflowing it.
|
|
||||||
*/
|
|
||||||
edge?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const H = 200; // internal canvas height (viewBox scales it to fit)
|
const H = 200; // internal canvas height (viewBox scales it to fit)
|
||||||
const PAD = 14;
|
const PAD = 14;
|
||||||
const MAX_PATHS = 200; // safety ceiling; real density is bounded by the params
|
const MAX_PATHS = 110; // safety ceiling; real density is bounded by the params
|
||||||
|
|
||||||
type Pt = [number, number];
|
type Pt = [number, number];
|
||||||
|
|
||||||
@@ -78,7 +64,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
const emit = (d: string, cls: string) => {
|
const emit = (d: string, cls: string) => {
|
||||||
if (strokeCount >= MAX_PATHS) return;
|
if (strokeCount >= MAX_PATHS) return;
|
||||||
parts.push(
|
parts.push(
|
||||||
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 40}"/>`,
|
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 16}"/>`,
|
||||||
);
|
);
|
||||||
strokeCount++;
|
strokeCount++;
|
||||||
};
|
};
|
||||||
@@ -129,7 +115,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
track(at[0] + g.w * s);
|
track(at[0] + g.w * s);
|
||||||
parts.push(
|
parts.push(
|
||||||
`<g transform="translate(${n(at[0])} ${n(at[1])}) rotate(${n(deg)}) scale(${n(s)})">` +
|
`<g transform="translate(${n(at[0])} ${n(at[1])}) rotate(${n(deg)}) scale(${n(s)})">` +
|
||||||
`<path class="cs-sig-orn" d="${g.d}" pathLength="1" style="--i:${strokeCount % 40}"/></g>`,
|
`<path class="cs-sig-orn" d="${g.d}" pathLength="1" style="--i:${strokeCount % 16}"/></g>`,
|
||||||
);
|
);
|
||||||
strokeCount++;
|
strokeCount++;
|
||||||
};
|
};
|
||||||
@@ -139,7 +125,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
// with a motif.
|
// with a motif.
|
||||||
const limb = (ox: number, oy: number, ang: number, scale: number, depth: number) => {
|
const limb = (ox: number, oy: number, ang: number, scale: number, depth: number) => {
|
||||||
if (strokeCount >= MAX_PATHS) return;
|
if (strokeCount >= MAX_PATHS) return;
|
||||||
const L = scale * rnd(50, 92);
|
const L = scale * rnd(34, 64);
|
||||||
const dx = Math.cos(ang) * L;
|
const dx = Math.cos(ang) * L;
|
||||||
const dy = Math.sin(ang) * L;
|
const dy = Math.sin(ang) * L;
|
||||||
const peak: Pt = [ox + dx, oy + dy];
|
const peak: Pt = [ox + dx, oy + dy];
|
||||||
@@ -157,7 +143,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
emit(spline(pts), 'cs-sig-main');
|
emit(spline(pts), 'cs-sig-main');
|
||||||
|
|
||||||
// terminal spike off the outermost point
|
// terminal spike off the outermost point
|
||||||
barb(peak, ang + rnd(-0.5, 0.5), scale * rnd(12, 26));
|
barb(peak, ang + rnd(-0.5, 0.5), scale * rnd(8, 18));
|
||||||
|
|
||||||
// filament shadow trailing the main sweep
|
// filament shadow trailing the main sweep
|
||||||
if (rng() < 0.4) {
|
if (rng() < 0.4) {
|
||||||
@@ -183,7 +169,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
const tt = seg[2] as number;
|
const tt = seg[2] as number;
|
||||||
const base: Pt = [a[0] + (b[0] - a[0]) * tt, a[1] + (b[1] - a[1]) * tt];
|
const base: Pt = [a[0] + (b[0] - a[0]) * tt, a[1] + (b[1] - a[1]) * tt];
|
||||||
const side = k % 2 ? 1 : -1;
|
const side = k % 2 ? 1 : -1;
|
||||||
barb(base, ang + side * rnd(0.6, 1.3), scale * rnd(9, 22));
|
barb(base, ang + side * rnd(0.6, 1.3), scale * rnd(6, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
// recurse — one child curls off the mid/peak region
|
// recurse — one child curls off the mid/peak region
|
||||||
@@ -202,39 +188,16 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
if (depth === 0 && rng() < 0.3) ornament(peak, ang);
|
if (depth === 0 && rng() < 0.3) ornament(peak, ang);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Wavering spine: a curve from top to bottom. Default gently bows in
|
// ── Wavering spine: a curve from top to bottom, gently bowing in +x.
|
||||||
// +x; spineWave>1 makes it weave side-to-side (a serpentine edge vine).
|
|
||||||
const sw = opts.spineWave ?? 1;
|
|
||||||
const spineNodes = 5 + Math.floor(rng() * 3);
|
const spineNodes = 5 + Math.floor(rng() * 3);
|
||||||
const spinePts: Pt[] = [];
|
const spinePts: Pt[] = [];
|
||||||
for (let i = 0; i <= spineNodes; i++) {
|
for (let i = 0; i <= spineNodes; i++) {
|
||||||
const y = (H * i) / spineNodes;
|
const y = (H * i) / spineNodes;
|
||||||
const x =
|
const x = i === 0 || i === spineNodes ? 0 : rnd(0, 11);
|
||||||
i === 0 || i === spineNodes
|
|
||||||
? 0
|
|
||||||
: sw === 1
|
|
||||||
? rnd(0, 11) // unchanged default — center/corner/plate sigils
|
|
||||||
: i % 2
|
|
||||||
? rnd(2, 8) // near the edge
|
|
||||||
: rnd(9, 15) * sw; // inward bulge — one-sided weave, never < 0
|
|
||||||
spinePts.push([x, y]);
|
spinePts.push([x, y]);
|
||||||
}
|
}
|
||||||
emit(spline(spinePts), 'cs-sig-main');
|
emit(spline(spinePts), 'cs-sig-main');
|
||||||
|
|
||||||
// ── Terminal spike crown: long, straight, downward-converging needles
|
|
||||||
// off the spine tail — the dripping barbed point that ends the growth.
|
|
||||||
const tip = spinePts[spinePts.length - 1];
|
|
||||||
const fan = 4 + Math.floor(rng() * 3); // 4–6
|
|
||||||
for (let s = 0; s < fan; s++) {
|
|
||||||
const spread = (s / (fan - 1) - 0.5) * 1.0; // ~±0.5 rad — tighter dagger
|
|
||||||
const a = Math.PI / 2 + spread; // π/2 = +y (down)
|
|
||||||
const len = rnd(64, 104) * (1 - Math.abs(spread) * 0.35); // center longest
|
|
||||||
const base: Pt = [tip[0], tip[1] - rnd(0, 8)];
|
|
||||||
const tt: Pt = [base[0] + Math.cos(a) * len, base[1] + Math.sin(a) * len];
|
|
||||||
emit(`M${n(base[0])} ${n(base[1])}L${n(tt[0])} ${n(tt[1])}`, 'cs-sig-barb');
|
|
||||||
track(tt[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Branch nodes ride the spine and throw limbs outward. Nodes are
|
// ── Branch nodes ride the spine and throw limbs outward. Nodes are
|
||||||
// inset from the very ends and spread the full height so growth flows
|
// inset from the very ends and spread the full height so growth flows
|
||||||
// down the whole trunk rather than clumping at the top.
|
// down the whole trunk rather than clumping at the top.
|
||||||
@@ -249,30 +212,24 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
|||||||
const node: Pt = [a[0] + (b[0] - a[0]) * sf, a[1] + (b[1] - a[1]) * sf];
|
const node: Pt = [a[0] + (b[0] - a[0]) * sf, a[1] + (b[1] - a[1]) * sf];
|
||||||
// later nodes lean downward so the lower trunk fills out
|
// later nodes lean downward so the lower trunk fills out
|
||||||
const bias = -0.25 + tc * 0.7;
|
const bias = -0.25 + tc * 0.7;
|
||||||
// spike gradient: lower trunk grows longer, sharper barbs
|
|
||||||
const spike = 1 + tc * 1.4; // 1× top → ~2.3× bottom
|
|
||||||
const limbs = 1 + Math.floor(rng() * 2);
|
const limbs = 1 + Math.floor(rng() * 2);
|
||||||
for (let l = 0; l < limbs; l++) {
|
for (let l = 0; l < limbs; l++) {
|
||||||
const ang = bias + rnd(-0.55, 0.55);
|
const ang = bias + rnd(-0.55, 0.55);
|
||||||
limb(node[0], node[1], ang, rnd(0.65, 1.05) * (0.8 + tc * 0.5), 1);
|
limb(node[0], node[1], ang, rnd(0.65, 1.05), 1);
|
||||||
}
|
}
|
||||||
// the odd bare barb straight off the spine keeps the trunk prickly
|
// the odd bare barb straight off the spine keeps the trunk prickly
|
||||||
if (rng() < 0.55) barb(node, bias + rnd(-0.3, 0.3), rnd(11, 20) * spike);
|
if (rng() < 0.55) barb(node, bias + rnd(-0.3, 0.3), rnd(7, 14));
|
||||||
}
|
}
|
||||||
|
|
||||||
const half = parts.join('');
|
const half = parts.join('');
|
||||||
// Edge mode crops to the inward half (spine at x=0) and lets the ribbon
|
const minX = -(maxX + PAD);
|
||||||
// stretch it (none); normal mode is symmetric and uniformly fitted.
|
const vbW = 2 * (maxX + PAD);
|
||||||
const minX = opts.edge ? 0 : -(maxX + PAD);
|
|
||||||
const vbW = opts.edge ? maxX + PAD : 2 * (maxX + PAD);
|
|
||||||
const par = opts.edge ? 'none' : 'xMidYMid meet';
|
|
||||||
const body = opts.edge
|
|
||||||
? `<g class="cs-sig-half">${half}</g>`
|
|
||||||
: `<g class="cs-sig-half">${half}</g>` +
|
|
||||||
`<g class="cs-sig-half" transform="scale(-1 1)">${half}</g>`;
|
|
||||||
return (
|
return (
|
||||||
`<svg class="cs-sigil" viewBox="${n(minX)} ${-PAD} ${n(vbW)} ${H + 2 * PAD}" ` +
|
`<svg class="cs-sigil" viewBox="${n(minX)} ${-PAD} ${n(vbW)} ${H + 2 * PAD}" ` +
|
||||||
`preserveAspectRatio="${par}" aria-hidden="true" focusable="false" ` +
|
`preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" ` +
|
||||||
`xmlns="http://www.w3.org/2000/svg">${body}</svg>`
|
`xmlns="http://www.w3.org/2000/svg">` +
|
||||||
|
`<g class="cs-sig-half">${half}</g>` +
|
||||||
|
`<g class="cs-sig-half" transform="scale(-1 1)">${half}</g>` +
|
||||||
|
`</svg>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
.cybersigil {
|
.cybersigil {
|
||||||
--font-sans: 'Space Mono', 'Courier New', ui-monospace, monospace;
|
--font-sans: 'Space Mono', 'Courier New', ui-monospace, monospace;
|
||||||
--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-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>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ html.cybersigil body::after {
|
|||||||
.cybersigil .cs-fx-halftone,
|
.cybersigil .cs-fx-halftone,
|
||||||
.cybersigil .cs-fx-wire,
|
.cybersigil .cs-fx-wire,
|
||||||
.cybersigil .cs-fx-tear,
|
.cybersigil .cs-fx-tear,
|
||||||
.cybersigil .cs-fx-edge {
|
.cybersigil .cs-fx-corner {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -108,17 +109,14 @@ html.cybersigil body::after {
|
|||||||
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--sky) 35%, transparent));
|
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--sky) 35%, transparent));
|
||||||
}
|
}
|
||||||
.cybersigil .cs-fx-wire .cs-sigil path {
|
.cybersigil .cs-fx-wire .cs-sigil path {
|
||||||
/* slower than the edges + a tighter stagger: the dense background field
|
animation: cs-redraw 5.5s ease-in-out infinite;
|
||||||
* spends most of the cycle solid, so it no longer reads as dotted while
|
/* negative, per-stroke offset: the field is always mid-carve, never blank */
|
||||||
* many strokes wipe out of phase. */
|
animation-delay: calc(var(--i, 0) * -0.34s);
|
||||||
animation: cs-redraw 8s linear infinite;
|
|
||||||
animation-delay: calc(var(--i, 0) * -0.13s);
|
|
||||||
}
|
}
|
||||||
/* Continuous carve: draws on through the first half, wipes off through the
|
|
||||||
* second, no plateau — the stroke is always in motion. */
|
|
||||||
@keyframes cs-redraw {
|
@keyframes cs-redraw {
|
||||||
0% { stroke-dashoffset: 1; }
|
0% { stroke-dashoffset: 1; }
|
||||||
50% { stroke-dashoffset: 0; }
|
35% { stroke-dashoffset: 0; }
|
||||||
|
60% { stroke-dashoffset: 0; }
|
||||||
100% { stroke-dashoffset: -1; }
|
100% { stroke-dashoffset: -1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,33 +146,21 @@ html.cybersigil body::after {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Thorny sigil growths anchoring the four screen corners. */
|
||||||
/* Edge vines — a tall sigil growth pinned to each screen edge, spine on
|
.cybersigil .cs-fx-corner {
|
||||||
* the edge with its outer half clipped so it creeps inward only. Subtle
|
width: clamp(96px, 13vw, 188px);
|
||||||
* ambient opacity; same perpetual carve/wipe as the wire + corners. */
|
height: clamp(96px, 13vw, 188px);
|
||||||
.cybersigil .cs-fx-edge {
|
background-color: var(--sky);
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: clamp(96px, 12vw, 184px);
|
|
||||||
overflow: hidden;
|
|
||||||
opacity: 0.26;
|
opacity: 0.26;
|
||||||
|
-webkit-mask: var(--cs-corner) center / contain no-repeat;
|
||||||
|
mask: var(--cs-corner) center / contain no-repeat;
|
||||||
|
filter: drop-shadow(0 0 5px color-mix(in srgb, var(--sky) 45%, transparent));
|
||||||
|
animation: cs-flicker 7s linear infinite;
|
||||||
}
|
}
|
||||||
.cybersigil .cs-fx-edge--l { left: 0; }
|
.cybersigil .cs-fx-corner--tl { top: 0; left: 0; }
|
||||||
.cybersigil .cs-fx-edge--r { right: 0; transform: scaleX(-1); }
|
.cybersigil .cs-fx-corner--tr { top: 0; right: 0; transform: scaleX(-1); animation-delay: -1.7s; }
|
||||||
/* edge-mode SVG is pre-cropped to the inward half + preserveAspectRatio
|
.cybersigil .cs-fx-corner--bl { bottom: 0; left: 0; transform: scaleY(-1); animation-delay: -3.4s; }
|
||||||
* none, so it just fills the ribbon: full height, stretched to width. */
|
.cybersigil .cs-fx-corner--br { bottom: 0; right: 0; transform: scale(-1); animation-delay: -5.1s; }
|
||||||
.cybersigil .cs-fx-edge .cs-sigil {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
filter: drop-shadow(0 0 5px color-mix(in srgb, var(--sky) 35%, transparent));
|
|
||||||
}
|
|
||||||
.cybersigil .cs-fx-edge .cs-sigil path,
|
|
||||||
.cybersigil .cs-fx-edge .cs-sigil line {
|
|
||||||
animation: cs-redraw 6.5s linear infinite;
|
|
||||||
animation-delay: calc(var(--i, 0) * -0.13s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── Generated sigils (cs-sigil markup from lib/cybersigil.ts) ──────────
|
/* ─── Generated sigils (cs-sigil markup from lib/cybersigil.ts) ──────────
|
||||||
* Inert/hidden everywhere; only cybersigil draws them. Strokes start fully
|
* Inert/hidden everywhere; only cybersigil draws them. Strokes start fully
|
||||||
@@ -207,6 +193,19 @@ html.cybersigil body::after {
|
|||||||
to { stroke-dashoffset: 0; }
|
to { stroke-dashoffset: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Corner growths: swap the static mask box for the live SVG, keep the box's
|
||||||
|
* size / placement / flicker opacity. Strokes carve in and wipe out forever
|
||||||
|
* (same perpetual self-redraw as the background sigil). */
|
||||||
|
.cybersigil .cs-fx-corner--sig {
|
||||||
|
background: none;
|
||||||
|
-webkit-mask: none;
|
||||||
|
mask: none;
|
||||||
|
}
|
||||||
|
.cybersigil .cs-fx-corner--sig .cs-sigil path,
|
||||||
|
.cybersigil .cs-fx-corner--sig .cs-sigil line {
|
||||||
|
animation: cs-redraw 5.5s ease-in-out infinite;
|
||||||
|
animation-delay: calc(var(--i, 0) * -0.34s);
|
||||||
|
}
|
||||||
|
|
||||||
/* Selection — magenta block, bone glyph, cyan bleed. */
|
/* Selection — magenta block, bone glyph, cyan bleed. */
|
||||||
.cybersigil ::selection {
|
.cybersigil ::selection {
|
||||||
@@ -429,14 +428,14 @@ html.cybersigil body::after {
|
|||||||
stroke: color-mix(in srgb, var(--mauve) 68%, var(--sky));
|
stroke: color-mix(in srgb, var(--mauve) 68%, var(--sky));
|
||||||
filter: drop-shadow(0 0 4px color-mix(in srgb, var(--mauve) 55%, transparent));
|
filter: drop-shadow(0 0 4px color-mix(in srgb, var(--mauve) 55%, transparent));
|
||||||
}
|
}
|
||||||
.cybersigil .plate:hover .cs-plate-sig,
|
.cybersigil .plate-enter:hover .cs-plate-sig,
|
||||||
.cybersigil .plate:focus-visible .cs-plate-sig {
|
.cybersigil .plate-enter:focus-within .cs-plate-sig {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.cybersigil .plate:hover .cs-plate-sig .cs-sigil path,
|
.cybersigil .plate-enter:hover .cs-plate-sig .cs-sigil path,
|
||||||
.cybersigil .plate:hover .cs-plate-sig .cs-sigil line,
|
.cybersigil .plate-enter:hover .cs-plate-sig .cs-sigil line,
|
||||||
.cybersigil .plate:focus-visible .cs-plate-sig .cs-sigil path,
|
.cybersigil .plate-enter:focus-within .cs-plate-sig .cs-sigil path,
|
||||||
.cybersigil .plate:focus-visible .cs-plate-sig .cs-sigil line {
|
.cybersigil .plate-enter:focus-within .cs-plate-sig .cs-sigil line {
|
||||||
animation: cs-carve 600ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
animation: cs-carve 600ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
animation-delay: calc(var(--i, 0) * 55ms);
|
animation-delay: calc(var(--i, 0) * 55ms);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
0%, 49% { opacity: 1; }
|
0%, 49% { opacity: 1; }
|
||||||
50%, 100% { opacity: 0; }
|
50%, 100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
@keyframes cs-flicker {
|
||||||
|
0%, 8% { opacity: 0.26; }
|
||||||
|
9% { opacity: 0.46; }
|
||||||
|
10%, 70% { opacity: 0.26; }
|
||||||
|
71% { opacity: 0.08; }
|
||||||
|
72% { opacity: 0.34; }
|
||||||
|
73%, 100% { opacity: 0.26; }
|
||||||
|
}
|
||||||
@keyframes cs-tear {
|
@keyframes cs-tear {
|
||||||
0%, 21% { opacity: 0; top: 18%; }
|
0%, 21% { opacity: 0; top: 18%; }
|
||||||
22% { opacity: 0.85; top: 18%; transform: translateX(-7px); }
|
22% { opacity: 0.85; top: 18%; transform: translateX(-7px); }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
/* The looping sigils would otherwise collapse to their hidden end-state —
|
/* The looping sigils would otherwise collapse to their hidden end-state —
|
||||||
* pin them fully drawn instead so they stay visible, just still. */
|
* pin them fully drawn instead so they stay visible, just still. */
|
||||||
.cybersigil .cs-fx-wire .cs-sigil path,
|
.cybersigil .cs-fx-wire .cs-sigil path,
|
||||||
.cybersigil .cs-fx-edge .cs-sigil path {
|
.cybersigil .cs-fx-corner--sig .cs-sigil path {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
stroke-dashoffset: 0 !important;
|
stroke-dashoffset: 0 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user