daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

commit 8c3c29f871e2a1420d9930177fe0806845eacaf7
parent 9df0135341fbe2600674557c2526924abfad2d10
Author: $: DAΞMON <zer0sec.xp@icloud.com>
Date:   Wed, 30 Sep 2026 04:26:00 +0100

Open each page as a circle from the link that was pressed

Navigation crossfaded (Astro's ClientRouter default) while the theme
toggle wiped in a circle from the control. Pages now open with the
same circle, from the click point, matching the main site. The origin
has to go back on in astro:after-swap, because the swap replaces the
attributes on <html>, inline style included.

The transition overlay swallowed clicks for the full 960ms, so
::view-transition now passes pointer events through to the new page.

Tested: npm test (astro build + 4/4). In the dev server, the origin
matched the click and survived a replayed swap; the theme wipe stays
scoped to .theme-vt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Diffstat:
Msrc/layouts/Base.astro | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/styles/global.css | 35+++++++++++++++++++++++++++++++++--
2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro @@ -77,6 +77,63 @@ const canonical = new URL(Astro.url.pathname, Astro.site).href; document.addEventListener('astro:after-swap', apply); })(); </script> + <!-- Page iris. Every internal navigation opens the next page as a + circle from the link that was pressed: the theme wipe's gesture, + at page pace. The CSS is beside the theme wipe in global.css; + this only supplies the origin and the end radius. --> + <script is:inline> + (function () { + function centre(el) { + var r = el.getBoundingClientRect(); + return { x: r.left + r.width / 2, y: r.top + r.height / 2 }; + } + + /* Where the last click landed. Capture phase, so it is recorded + before the ClientRouter's own listener takes the click. A click + with detail 0 came from the keyboard and has no pointer + position, so it opens from the middle of the control. */ + var press = null; + document.addEventListener('click', function (e) { + var at = e.detail === 0 && e.target instanceof Element + ? centre(e.target) + : { x: e.clientX, y: e.clientY }; + at.t = Date.now(); + press = at; + }, true); + + var origin = null; + function paint() { + if (!origin) return; + var s = document.documentElement.style; + s.setProperty('--vt-x', origin.x + 'px'); + s.setProperty('--vt-y', origin.y + 'px'); + s.setProperty('--vt-r', origin.r + 'px'); + } + + /* A press older than a second did not cause this navigation (back, + forward, a scripted one), so it falls back to the link Astro + reports, then to the middle of the screen. The radius reaches + the farthest corner exactly, so the circle finishes as it + covers the page rather than part-way through an overshoot. */ + document.addEventListener('astro:before-preparation', function (e) { + var at = press && Date.now() - press.t < 1000 ? press + : e.sourceElement instanceof Element ? centre(e.sourceElement) + : { x: innerWidth / 2, y: innerHeight / 2 }; + origin = { + x: at.x, + y: at.y, + r: Math.hypot(Math.max(at.x, innerWidth - at.x), Math.max(at.y, innerHeight - at.y)), + }; + paint(); + }); + + /* The swap replaces <html>'s attributes with the new page's, and + the inline style carrying these three goes with them, so they + are put back before the new page is painted, for the same + reason as the theme note above. */ + document.addEventListener('astro:after-swap', paint); + })(); + </script> <ClientRouter /> </head> <body> diff --git a/src/styles/global.css b/src/styles/global.css @@ -1068,8 +1068,9 @@ main, .site-header, .site-footer { position: relative; z-index: 2; } .patt-src:hover { color: var(--iris); border-color: color-mix(in oklab, var(--iris) 50%, transparent); } /* ---- Theme toggle: circular-reveal wipe ---------------------------------- */ -/* Scoped to .theme-vt so page navigations keep Astro's default crossfade. - The toggle sets --vt-x/--vt-y (origin) and --vt-r (end radius) in JS. */ +/* Scoped to .theme-vt so it outranks the page iris below, which runs on + every navigation. The toggle sets --vt-x/--vt-y (origin) and --vt-r + (end radius) in JS. */ :root.theme-vt::view-transition-old(root), :root.theme-vt::view-transition-new(root) { animation: none; /* cancel the default fade */ @@ -1088,6 +1089,36 @@ main, .site-header, .site-footer { position: relative; z-index: 2; } } } +/* ---- Page iris: circular reveal on navigation -------------------------- */ +/* Every internal navigation (Astro's ClientRouter) opens the next page as a + circle from the link that was pressed, the theme wipe's gesture at page + pace. The inline script in Base.astro sets --vt-x/--vt-y and the exact + farthest-corner radius --vt-r. The theme wipe's rules above are scoped to + .theme-vt, which outranks these, so each keeps its own timing. */ +::view-transition-old(root), +::view-transition-new(root) { + animation: none; /* replace Astro's default crossfade */ + mix-blend-mode: normal; +} + +@media (prefers-reduced-motion: no-preference) { + ::view-transition-new(root) { + animation: page-iris 960ms cubic-bezier(0.4, 0, 0.2, 1) both; + } + @keyframes page-iris { + from { clip-path: circle(0 at var(--vt-x, 50%) var(--vt-y, 50%)); } + to { clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x, 50%) var(--vt-y, 50%)); } + } +} + +/* The transition layer sits over the whole page and, left alone, swallows + every click until it finishes: at 960ms, a page that ignores the reader + for a second. The live DOM underneath is already the new page, so clicks + go straight through to it. */ +::view-transition { + pointer-events: none; +} + /* ============================================================================ Cyberdeck integration — additive hooks that wire the shared chrome objects (`.corners`, `.scanlines`, `.plate-dawn`) into the page furniture. Kept in