/*
 * gpluo.cn — the page transition.
 *
 * Owner, 2026-10-21: "点击卡片打开新链接时都应该有一个过渡动画，具体效果是从整个点击处
 * 所在的卡片，逐渐扩散到整个页面，完成过渡：1）鼠标点击可点击的元素；2）新页面加载完成；
 * 3）卡片内侧，逐渐淡入新页面，与此同时，新页面从卡片边缘向外扩散到整个页面。"
 *
 * The whole feature is this file plus the inline script gpluo-tx.php prints in <head>.
 * The click side writes the rectangle and the four corner radii of the thing that was
 * pressed into sessionStorage and lets the browser navigate exactly as it always did — no
 * preventDefault, so middle-click, cmd-click, back/forward, the address bar and the history
 * are untouched. The arrival side reads that back before the first paint, puts the geometry
 * on <html> as custom properties, adds class gpluo-tx-in, and this file clips the arriving
 * page to that rectangle and animates the clip out to the whole window.
 *
 * The mechanism used to be four fixed bars of the backdrop colour, one per edge, collapsing
 * outward with a transform. It worked, and it was replaced on 2026-10-21 for one reason:
 * the owner's first complaint was that the window the bars cut out is a rectangle while the
 * card it grows out of is not ("初始框都是一个矩形，而不是和文章卡片/主题风格更贴近的圆角矩
 * 形"). A rounded hole cannot be made out of axis-aligned rectangles: the four quarter-discs
 * that a rounded rectangle leaves out of a sharp one lie INSIDE the window, so the frame
 * would have to gain material at each corner, and border-radius only takes material away.
 * Four extra corner pieces would have had to track two edges moving at two different speeds,
 * which means nesting them, which means twelve elements for four corners. One clip-path says
 * the same thing in one element, and it says the corners exactly.
 *
 * What is clipped is the container the new page actually arrived in: #page under the theme's
 * pjax, the body otherwise. Not the root element — the first version of this file rejected
 * clip-path on the root because the root would also clip the fixed header and the chat
 * window, and that objection still holds. Under pjax the header, the footer and the chat
 * window did not change, so they stay on screen while the content grows between them, which
 * is also why the fade under gpluo-tx-pjax belongs to #page and not to the body.
 *
 * The four edges still travel at the same speed rather than arriving together, which is the
 * shape the owner picked on 2026-10-21 ("匀速扩散吧，我担心同时结束不是所有窗口下都能正常运
 * 行"). One property now holds four insets, so the four durations are got back by animating
 * five registered custom properties — the four insets and the radius — each with its own
 * animation-name and duration, all on the same element. Registered because unregistered
 * custom properties animate discretely: the @property blocks at the top of this file are
 * what make those five values interpolate instead of jumping. A browser that does not know
 * @property still gets the fade and still gets the window, but the window gets there in two
 * steps rather than four hundred — which is the one degradation this rewrite accepts, and it
 * is a 2019-browser degradation, not a broken page.
 *
 * The insets are written as calc(100% - Npx) on the right and bottom so that 100% means
 * "however tall this page has become right now": the page is still growing while this runs
 * (lazysizes is swapping images in), and a bottom inset written in px would be measuring a
 * height that had already changed by the next frame. Each inset has two ends — the card and
 * the edge of the screen — because the element being clipped is the whole document, and a
 * window that opens all the way to the bottom of the document spends its duration travelling
 * somewhere nobody can see.
 *
 * The theme's pjax (2026-10-21, switched on in its own options) takes the new document
 * away: the library swaps the inside of #page in place, so there is no head script to run
 * and no sessionStorage to read. The same clip does the same thing, armed and released from
 * pjax:complete — armed at the arrival rather than at the click, for the reason in
 * gpluo-tx.php's pjax:send comment — with the fade moved to #page under gpluo-tx-pjax.
 *
 * Why the theme's own entrance animations are switched off underneath: style.css starts
 * fourteen of them on every page load — .headertop/.wrapper/.site-footer blur in over
 * 0.8s, #content slides up 50px over 1s, every .post-list-thumb slides up over 0.6s,
 * the logo and the header menu slide sideways over 1s. All of it at once, all of it
 * different directions, which is what the owner means by "目前动效反馈约等于不存在":
 * fifteen small motions do not read as one transition, they read as "the page appeared".
 * So while the reveal runs, those animation-names are cleared and the page holds still;
 * the single motion is the one that came out of the card.
 *
 * They are cleared by a class of their own, gpluo-tx-quiet, which outlives the reveal, and
 * that is the owner's third complaint: "过渡完了新页面还是会重刷一次". Removing a class that
 * sets animation-name to none does not quietly hand the element back to its stylesheet —
 * giving an element an animation-name it did not just have STARTS that animation, so the
 * frame after the reveal ended was the frame in which #content began its 1s slide-up and
 * every card its 0.6s one. A second full entrance, arriving immediately after the
 * transition that exists to replace it, reads as a page refresh, and the owner is right
 * that it defeats the point. So the clip and the backdrop are the short-lived part and the
 * quieting is the long-lived part: it stays on the root element for the life of this
 * document, which also means the theme's scroll reveal (.post-list-show, added by app.js as
 * cards come into view) stays off on an arrival that came through a card. One motion per
 * arrival is the deal; the owner picked it on 2026-10-21 over letting the theme's animations
 * play afterwards in sequence, and over a variant that would have kept the scroll reveal by
 * zeroing animation durations instead of names.
 *
 * prefers-reduced-motion keeps the fade and drops the geometry: the clip is not applied at
 * all, so the new page simply fades in. Same trade gpluo-ui.css section 1 makes for the
 * page-turn.
 *
 * Tuning, all of it here: --gpluo-tx-dur / --gpluo-tx-ease for the expansion,
 * --gpluo-tx-fade for the fade, --gpluo-tx-backdrop for what the unrevealed page looks
 * like. The head script writes where the window starts (--gpluo-tx-t0/r0/b0/l0) and its
 * corner radius (--gpluo-tx-rad0), where it ends (--gpluo-tx-t1/r1/b1/l1, which is the part
 * of the clipped container inside the viewport), and four ratios
 * (--gpluo-tx-kt/kb/kl/kr) — how far each of the four edges has to go, as a fraction of the
 * furthest one, which is what makes every edge move at the page's own speed no matter what
 * the window is.
 *
 * Delete gpluo-tx.php and gpluo-tx.css and the site navigates exactly as it did before: the
 * click side never writes its key, so the head script finds nothing and adds no class, and
 * nothing in this file applies.
 */


/* The five values the reveal animates. Registered so they interpolate: an unregistered
   custom property is animated discretely, which would take the window from the card to the
   screen in two jumps. <length-percentage> rather than <length> because the right and bottom
   insets are written as calc(100% - Npx) — see the header. The initial values are all 0,
   which is the state "no transition": an inset of 0 on every side is the whole element. */
@property --gpluo-tx-t {
	syntax: "<length-percentage>";
	inherits: true;
	initial-value: 0px;
}
@property --gpluo-tx-r {
	syntax: "<length-percentage>";
	inherits: true;
	initial-value: 0px;
}
@property --gpluo-tx-b {
	syntax: "<length-percentage>";
	inherits: true;
	initial-value: 0px;
}
@property --gpluo-tx-l {
	syntax: "<length-percentage>";
	inherits: true;
	initial-value: 0px;
}
@property --gpluo-tx-rad {
	syntax: "<length-percentage>";
	inherits: true;
	initial-value: 0px;
}


html.gpluo-tx-in {
	/* 560ms, up from 420ms, on the owner's second look at the transition (2026-10-22):
	   "这里闪一下是非常影响观感的，我需要更加无缝的过渡，过渡稍微慢一点其实无所谓". The frame
	   cadence is the reason this helps at all: tx_flash_probe.py measured the reveal running
	   inside a page whose arriving scripts own the main thread, and the painted frames came out
	   110-230ms apart. At 420ms that is two or three frames of animation for the whole reveal,
	   which is not an animation a person can follow — it is a before frame and an after frame.
	   At 560ms there are four or five, and the middle ones are the transition. */
	--gpluo-tx-dur: 560ms;
	--gpluo-tx-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
	--gpluo-tx-fade: 340ms;

	/* Per-edge duration factors: how far each edge has to travel, as a fraction of the
	   furthest one, so all four edges move at the same speed. The script writes them on
	   every transition; they default to 1 for the case where it measured nothing (a card
	   flush against every edge, or a head script that failed), and 1 means one duration for
	   all four insets. The floor of 0.34 stays for the same reason it stayed for the bars:
	   an edge flush against an edge of the screen has a travel of about zero, and an inset
	   that closes in 40ms reads as a glitch rather than as an edge that had nowhere to go.
	   At 560ms the floor is 190ms, and the longest edge always gets the full 560ms, so the
	   whole reveal still ends when the fade does. */
	--gpluo-tx-kt: 1;
	--gpluo-tx-kb: 1;
	--gpluo-tx-kl: 1;
	--gpluo-tx-kr: 1;
	--gpluo-tx-dt: calc(var(--gpluo-tx-dur) * max(var(--gpluo-tx-kt), 0.34));
	--gpluo-tx-db: calc(var(--gpluo-tx-dur) * max(var(--gpluo-tx-kb), 0.34));
	--gpluo-tx-dl: calc(var(--gpluo-tx-dur) * max(var(--gpluo-tx-kl), 0.34));
	--gpluo-tx-dr: calc(var(--gpluo-tx-dur) * max(var(--gpluo-tx-kr), 0.34));

	/* What the page looks like before the window reaches it. The clipped-away area shows the
	   root element, so this has to be the site's own canvas and not the browser's.

	   Light mode is pure white under the pointer everywhere on this site (measured off
	   computed styles in cur_surface_check.py, 2026-10-21). Dark mode is #333333, and the
	   honest note on that value is that this rule does not paint it: gpluo-dark.php's own head
	   script sets document.documentElement.style.backgroundColor = '#333' inline, and an
	   inline style outranks any selector, so on a dark arrival the canvas is already right
	   before this file is consulted. Measured, 2026-10-21, in tx_frame.py: the root element's
	   computed background reads rgb(51,51,51) with this class on and with it off alike. The
	   value here is the fallback for the case where that script stops running — and the reason
	   it is #333333 and not the #2e2e2e of gpluo-dark.css section 0, which is the cards, not
	   the canvas behind them. */
	--gpluo-tx-backdrop: #ffffff;
	background-color: var(--gpluo-tx-backdrop);

	/* style.css:1 puts `transition: background 0.8s` on the root element, which would
	   turn the backdrop into a 800ms fade from the browser's own canvas — the frame
	   outside the window would start white and darken underneath the reveal. Measured,
	   not theorised: tx_frame.py caught html's computed background at rgba(46,46,46,.44)
	   260ms into a 520ms transition. The class only lives for the length of the
	   animation, so the only thing this suppresses is that one fade. */
	transition: none;
}

/* gpluo-dark.php decides the colour mode in its own head script (wp_head 20) and drops
   that class at DOMContentLoaded — which is in the middle of this animation — so the
   head script here copies the answer onto a class that outlives it. */
html.gpluo-tx-in.gpluo-tx-dark {
	--gpluo-tx-backdrop: #333333;
}

/* The window. One clip-path, read from five custom properties that are each animated by
   their own animation, and applied to the element the new content actually arrived in.
   The body is that element for a new document; under pjax it is #page, because the body is
   the one that was already here and clipping it would take the header, the footer and the
   chat window off the screen for no reason — they did not change.

   The radius is one value for all four corners. The click side reads the pressed element's
   four corner radii and takes the largest: on this site every card is uniform (style.css
   gives .post-list-thumb 30px and the GUIDE tiles 15px), and a text link has none, so the
   difference between the corners is never the thing being looked at.

   These rules come after the one above on purpose: they tie with it on specificity, so the
   tie is broken by order, and the order is this file's. Same for the release rule further
   down — it has to beat the paused declaration, and it does by being later. */
html.gpluo-tx-in body {
	clip-path: inset(var(--gpluo-tx-t) var(--gpluo-tx-r) var(--gpluo-tx-b) var(--gpluo-tx-l)
	                 round var(--gpluo-tx-rad));
	animation:
		gpluo-tx-fade var(--gpluo-tx-fade) ease-out both,
		gpluo-tx-t var(--gpluo-tx-dt) var(--gpluo-tx-ease) both,
		gpluo-tx-r var(--gpluo-tx-dr) var(--gpluo-tx-ease) both,
		gpluo-tx-b var(--gpluo-tx-db) var(--gpluo-tx-ease) both,
		gpluo-tx-l var(--gpluo-tx-dl) var(--gpluo-tx-ease) both,
		gpluo-tx-rad var(--gpluo-tx-dur) var(--gpluo-tx-ease) both;
	animation-play-state: paused;
}

/* With the theme's pjax switched on there is no new document, so the sentence above about
   which element arrived is false in a way it has not been before: only the inside of #page
   changed. Fading or clipping the body would flicker three things that did not arrive. */
html.gpluo-tx-pjax body {
	clip-path: none;
	animation: none;
}

html.gpluo-tx-pjax #page {
	clip-path: inset(var(--gpluo-tx-t) var(--gpluo-tx-r) var(--gpluo-tx-b) var(--gpluo-tx-l)
	                 round var(--gpluo-tx-rad));
	animation:
		gpluo-tx-fade var(--gpluo-tx-fade) ease-out both,
		gpluo-tx-t var(--gpluo-tx-dt) var(--gpluo-tx-ease) both,
		gpluo-tx-r var(--gpluo-tx-dr) var(--gpluo-tx-ease) both,
		gpluo-tx-b var(--gpluo-tx-db) var(--gpluo-tx-ease) both,
		gpluo-tx-l var(--gpluo-tx-dl) var(--gpluo-tx-ease) both,
		gpluo-tx-rad var(--gpluo-tx-dur) var(--gpluo-tx-ease) both;
	animation-play-state: paused;
}

/* The held page. gpluo-tx.php keeps the element the library threw away — the swap is a
   replaceWith, so the old page is detached from the document but still alive in a variable —
   and puts it back in a fixed holder behind the reveal, at the viewport coordinates it had at
   the moment of the click. This is the owner's own sentence for what he wants: "点按菜单/文章
   卡片等时，维持当前呈现不动，通过动效过渡到新页面". It is also what a full navigation gave us
   for free and a swap does not: the browser keeps painting the old document until the new one
   has pixels of its own, which is the real reason the pre-pjax version of this feature never
   flashed. A swap destroys the old rendering at the moment the response lands, and what is
   behind it is the body's cover picture, so the reveal opens on a screen of cover. Enlarging
   the window and lifting the fade's floor, which is what the previous batch did, took the
   cover from 98.8% of the first frame to 88.4% of it. It made the flash smaller. He is right
   that it is still a flash.

   z-index -1 is what puts the holder under the arriving page, and where the script parks it is
   what makes that work instead of running backwards. Two things stand between the body's
   background and the arriving page, and both were measured before being written down.

   The first is #main-container, the theme's own wrapper: style.css gives it position relative,
   overflow hidden and z-index 2, and in dark mode an opaque background
   (rgba(51,51,51,var(--theme-dm-background_transparency,1))) while in light mode no background
   at all. #page is its child. A holder parked on the body at z-index -1 therefore sits under
   that whole stacking context — invisible in dark mode, and visible in light mode only because
   there happens to be nothing opaque there. Measured 2026-10-22: a hit test at three points
   outside the reveal window answered section#main-container every time and the held page was
   nowhere in the stack. So the holder goes INSIDE #main-container, where the order inside that
   context is: its own background, the held page, the arriving #page, then the header cover and
   the footer, which were never swapped and should keep painting over all of it.

   The second is the root element's own background: gpluo-tx-in gives it an opaque colour (the
   backdrop rule above), and once the root has one, the body's background — the cover picture —
   stops propagating to the canvas and is painted as an in-flow block background, which inside a
   stacking context happens AFTER the negative-z-index children. The isolation below is the
   guard for the case where #main-container is not in the document and the holder has to go on
   the body: with it, the body is its own stacking context and the order inside it is the cover,
   then the held page, then everything else. Without it the held page would sit under the cover
   and be invisible.

   pointer-events none because the held page is a picture, not a target: it still carries every
   link that was on screen, and a click during the reveal belongs to the page that is arriving. */
#gpluo-tx-ghost {
	position: fixed;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	overflow: hidden;
	z-index: -1;
	pointer-events: none;
}

/* Only matters for the fallback path (no #main-container in the document), where the holder
   has to be a child of the body — see the comment above. It costs a stacking context for the
   length of the reveal and nothing else: everything visible is inside the body anyway, so
   nothing changes hands between one context and the next. */
html.gpluo-tx-in.gpluo-tx-pjax body {
	isolation: isolate;
}

/* The held element keeps its id on purpose: the theme's stylesheet styles it by id (width,
   margins, the banner geometry), and an element that keeps its id renders exactly as it did,
   which is the whole point of a freeze. The price is two elements with that id for the length
   of the reveal, and this is the half of the price that stops the held one from being clipped
   and animated along with the arriving one. The other half is document order: the holder is
   appended at the end of the document, so getElementById still answers with the new page. */
html.gpluo-tx-pjax #page.gpluo-tx-frozen {
	clip-path: none;
	animation: none;
	opacity: 1;
}

#gpluo-tx-ghost #page {
	position: absolute;
	margin: 0;
}

/* Everything above starts paused, and this is why: a CSS animation begins counting from the
   moment its element is first styled, which on this site is well before the moment it is
   first seen. The head script runs as soon as the stylesheet lands; the theme's own app.js
   is deferred and runs after the parse, sizing #centerbg to 100vh and starting lazysizes,
   which on a slow machine is a few hundred milliseconds of script between the two. Count
   from then and the reveal is already half over at the first paint — the worse failure being
   the one where the visitor sees nothing of it. So the head script adds gpluo-tx-run from a
   requestAnimationFrame, and the first rAF callback of a document runs before that document's
   first paint: the animation starts on the frame it is visible on. Paused with fill-mode both
   still paints the from-state, so the window is already the size of the card at that first
   paint.

   The paused declaration is repeated on both containers rather than once on a shared class,
   because `animation:` is a shorthand that resets play-state to running, and the container
   rules out-rank any shared class rule — so a pause declared there loses to the shorthand
   here no matter which file it came from. tx_frame.py caught exactly that under the bars: the
   animation ran, finished, and animationend took the whole apparatus off before the first
   frame was ever drawn. The release rule below carries the same weight for the same reason:
   two classes on the root element, so it out-ranks the paused declaration instead of tying
   with it and losing to whichever rule happens to be last.

   tx_frame.py is the other side of the same door: it never adds this class, which is how it
   gets to seek the animation by hand and shoot the t=0 frame. */
html.gpluo-tx-in.gpluo-tx-run body,
html.gpluo-tx-in.gpluo-tx-run #page {
	animation-play-state: running;
}

/* Five animations, five properties, five durations. Both ends of each come from a property
   the script wrote — the -0 end is the card, the -1 end is the part of the clipped container
   the visitor can see — so one set of keyframes serves every card on the site at every window
   size. The fallbacks are 0px, which is the whole element: a script that wrote nothing
   produces a reveal that is already over rather than one that hides the page.

   The end values are not decoration. The clipped container is the whole document, so without
   them the bottom edge's journey is to the bottom of the page rather than to the bottom of
   the screen, and its share of the duration is set by a distance nobody can see: measured on
   the front page with a card at 320,120,800,300, that trip is 4500px against the sides' 320px,
   which paints as the bottom of the window snapping open in the first 30ms while the sides
   take 143ms. The radius is the one property that always ends at nothing — the last frame is
   a rectangle the size of the screen, and a 30px corner on that is a white notch bitten out of
   each corner of the display. */
@keyframes gpluo-tx-t {
	from { --gpluo-tx-t: var(--gpluo-tx-t0, 0px); }
	to   { --gpluo-tx-t: var(--gpluo-tx-t1, 0px); }
}
@keyframes gpluo-tx-r {
	from { --gpluo-tx-r: var(--gpluo-tx-r0, 0px); }
	to   { --gpluo-tx-r: var(--gpluo-tx-r1, 0px); }
}
@keyframes gpluo-tx-b {
	from { --gpluo-tx-b: var(--gpluo-tx-b0, 0px); }
	to   { --gpluo-tx-b: var(--gpluo-tx-b1, 0px); }
}
@keyframes gpluo-tx-l {
	from { --gpluo-tx-l: var(--gpluo-tx-l0, 0px); }
	to   { --gpluo-tx-l: var(--gpluo-tx-l1, 0px); }
}
@keyframes gpluo-tx-rad {
	from { --gpluo-tx-rad: var(--gpluo-tx-rad0, 0px); }
	to   { --gpluo-tx-rad: 0px; }
}

/* The fade, and the 0.62 is the part worth arguing about.

   It used to start at 0, which is what a fade normally does. Measured on 2026-10-22 with
   tx_flash_probe.py on a menu-tab hop, that made the first painted frame of the reveal a
   frame the new page contributed nothing to: opacity 0 inside a window the size of the
   pressed tab, which is 1.2% of the screen, so the whole rest of the display was the site's
   cover picture. The next frame, 110-230ms later, was already 20-94% revealed. What that
   sequence looks like to the person who pressed the tab is exactly what he reported:
   "会先闪一下背景图片（就是首页大图），才开始过渡到新页面" — one frame of the cover picture,
   then the new page. The cover did not change and nothing was wrong with the animation; the
   animation's first frame was simply empty, and an empty frame after a full one is a flash.

   Starting at 0.62 means the first frame the eye gets already holds the new page, at the
   shape of the thing that was pressed, and the fade then carries it the rest of the way to
   opaque while the window opens. Nothing is ever painted that shows only the cover.

   The value is not a guess at "invisible enough": at 0.62 the content behind the window is
   readable but visibly not-yet-settled, which is the seamlessness that was asked for. A
   value near 1 would remove the fade's whole job, which is to keep the window's edge from
   arriving with content already fully lit. */
@keyframes gpluo-tx-fade {
	from { opacity: 0.62; }
	to   { opacity: 1; }
}


/* -------------------------------------------------------------------------
 * The theme's own load-time entrance animations, quieted. Every selector here
 * is (0,2,1) or better against style.css's (0,1,0), so none of it needs
 * !important, and gpluo-tx.css prints at wp_enqueue_scripts 101 — after the
 * theme (10), after gpluo-dark.css (99) and after gpluo-ui.css (100) — so it
 * also wins anything it ties with.
 *
 * The list is the load-time half of style.css's animation table: the cover and
 * the board and the footer blur in, the content area and every card slide up,
 * the logo and the header menu and the search box slide sideways. Nothing here
 * touches the hover states, the lightbox, the profile card, the music player or
 * anything else that animates on interaction — those are not running at first
 * paint, and clearing them would break the widget.
 *
 * On gpluo-tx-quiet rather than gpluo-tx-in, and it is not removed when the
 * reveal ends: the header of this file has the whole story, and the short
 * version is that taking an animation-name away and giving it back starts that
 * animation, so a quieting class that ended with the reveal re-ran the theme's
 * entrance one beat after the transition finished. That class also covers the
 * theme's scroll reveal (.post-list-show), which is why cards below the fold
 * stop sliding up on an arrival that came through a card.
 * ---------------------------------------------------------------------- */
html.gpluo-tx-quiet .headertop,
html.gpluo-tx-quiet .pattern-center,
html.gpluo-tx-quiet .wrapper,
html.gpluo-tx-quiet #content,
html.gpluo-tx-quiet .site-footer,
html.gpluo-tx-quiet .site-branding,
html.gpluo-tx-quiet .header-user-avatar,
html.gpluo-tx-quiet .site-top .lower,
html.gpluo-tx-quiet .searchbox.js-toggle-search,
html.gpluo-tx-quiet .top-feature,
html.gpluo-tx-quiet .post-list-thumb,
html.gpluo-tx-quiet .post-list-show,
html.gpluo-tx-quiet .post-list,
html.gpluo-tx-quiet .comments,
html.gpluo-tx-quiet .notice {
	animation-name: none;
}

/* body.dark carries the same search-box entrance a second time. */
html.gpluo-tx-quiet body.dark .searchbox.js-toggle-search {
	animation-name: none;
}


@media (prefers-reduced-motion: reduce) {
	html.gpluo-tx-in {
		--gpluo-tx-fade: 200ms;
	}

	html.gpluo-tx-in body,
	html.gpluo-tx-pjax #page {
		clip-path: none;
	}
}
