/*
 * The bottom-corner stack.
 *
 * One owner for the geometry of every control pinned to a bottom corner of the
 * viewport. Until this file each of them positioned itself: bot.css wrote
 * `inset-block-end: 1rem; inset-inline-end: 1rem`, access.css wrote the same
 * pair mirrored, and anything that had to keep clear of them guessed at what
 * they occupied. Three defects came out of that, and all three are arithmetic
 * rather than carelessness — nothing held the numbers to each other:
 *
 * - `ticker.css` reserved 184px of the announcement row for the chat launcher,
 *   while `bot.css`, in the same file that published that 184px, allowed the
 *   launcher to grow to 240px. Measured pill widths are 151px at "Tanya DIN"
 *   and 193px at "Tanya Mualimin" — one of the three names the department is
 *   choosing between — so the reservation was already 9px short of a name that
 *   is on the table, and 56px short of the bound the launcher was given.
 * - `--jmns-bot-clearance` reserved `44px + 2rem` of footer, describing a 44px
 *   launcher. The launcher measures 48px: the token was written from the
 *   `min-block-size` in the rule rather than from the box the rule produces.
 * - The footer reserved for the chat launcher and nothing at all for the
 *   reading-preferences launcher in the opposite corner, which has been sitting
 *   over the bottom-inline-start end of the legal row since it shipped.
 *
 * So: no control states an inset. A control states which corner it is in — as
 * `data-jmns-stack` in its own markup — and where it sits in that corner, as
 * `--jmns-stack-order` in its own stylesheet. Everything else is derived here,
 * and the numbers other components reserve are derived from the same
 * declarations that place the controls. A reservation can no longer disagree
 * with the thing it is reserving against, because there is only one of each
 * number.
 *
 * The roster, which is the thing to read first and the thing to add to:
 *
 *     corner   order   control          declared in
 *     ------   -----   --------------   ----------------------------------
 *     start        0   .jmns-access     access.css   reading preferences
 *     start        1   .jmns-top        top.css      scroll to top
 *     end          0   .jmns-bot        bot.css      chat launcher
 *
 * The chat launcher is one setting away from being in the start corner instead
 * (`bottom-left`), and until this file that setting dropped it exactly on top of
 * the reading-preferences launcher: same corner, same 1rem, same 48px, measured
 * at 48x48px of overlap — one control completely covering another. bot.css
 * declares order 2 for that case, so the setting now produces a column of three
 * rather than two controls in one square.
 *
 * No JavaScript. A script that measured the controls and wrote the results back
 * as custom properties would be reading numbers this file already knows, one
 * frame late, and would leave every consumer with nothing to reserve until it
 * ran. The stack does not measure the controls; it dictates their geometry, and
 * publishes what it dictated.
 *
 * ADR-001: the plugin owns both launchers already, and a stylesheet that is
 * only correct when a particular theme is active is not something the plugin
 * can rely on. Every custom property carries a fallback at the point of use for
 * the same reason.
 */

/*
 * The vocabulary, in `:where()` per ADR-052 — these are defaults a member is
 * allowed to raise, and a member's own `:root` rule has to be able to win
 * without depending on which stylesheet WordPress happened to print first.
 * That source-order dependence is the trap this codebase keeps hitting.
 */
:where(:root) {
	/* Distance from the two viewport edges the stack is pinned to. */
	--jmns-stack-edge: 1rem;

	/* Between two stacked controls, and between the stack and anything that
	   has reserved room for it. */
	--jmns-stack-gap: 0.5rem;

	/*
	 * One slot: the square a stacked control is placed in.
	 *
	 * 48px is what both launchers already measure, and comfortably past the
	 * 44px touch floor. The `3rem` half is not decoration — the reading
	 * preferences panel scales the root font size up to 137.5%, and the chat
	 * launcher is built out of rem (a 2rem avatar in 0.5rem of padding), so at
	 * the largest text step its pill is 66px tall. A slot fixed at 48px would
	 * put the control above it 18px inside it at exactly the text size chosen
	 * by the visitor least able to tell two overlapping controls apart.
	 */
	--jmns-stack-slot: max( 48px, 3rem );

	/* Slot plus the gap above it: how far up the next control starts. */
	--jmns-stack-step: calc( var(--jmns-stack-slot) + var(--jmns-stack-gap) );

	/*
	 * Above the page and above the sticky header (100), below the search
	 * overlay (9998) and below the first-entry loader (10000) — which is what
	 * keeps the scroll-to-top button from ever being drawn over the intro,
	 * whatever the theme does about fading it. The chat launcher raises this to
	 * 9999 for itself: below 30rem its panel takes the whole screen, and a
	 * fullscreen panel that sits under the search overlay is a panel with
	 * another page drawn across it.
	 */
	--jmns-stack-z: 95;

	/*
	 * How many slots each corner holds. The default is the roster above minus
	 * the chat launcher, which is a setting and is therefore counted by
	 * bot.css — that stylesheet is only enqueued when the bot actually renders,
	 * so a site with the bot switched off reserves nothing for it. That is the
	 * same bargain `--jmns-bot-clearance` used to make, kept.
	 */
	--jmns-stack-count-start: 2;
	--jmns-stack-count-end: 0;

	/*
	 * How wide a corner's widest occupant may become. The start corner holds
	 * square icon buttons and needs no more than the slot; the chat launcher is
	 * a pill and raises this for whichever corner it is in, in the same
	 * declaration that bounds the pill.
	 */
	--jmns-stack-extent-start: var(--jmns-stack-slot);
	--jmns-stack-extent-end: var(--jmns-stack-slot);

	/*
	 * ---- published, for anything that has to keep clear of the stack ----
	 *
	 * Vertical room a full-width element at the foot of the page has to leave.
	 * The taller of the two corners, because the footer spans both.
	 *
	 * `n * step - gap` is n slots with n-1 gaps between them; the `max(0px, …)`
	 * is what makes an empty corner reserve nothing rather than minus one gap.
	 *
	 * This over-reserves by one slot on a page too short to ever summon the
	 * scroll-to-top button. That is the cheap direction to be wrong in: the
	 * alternative is a reservation that grows when the button appears, which is
	 * a footer that changes height while the visitor is scrolling towards it.
	 */
	--jmns-stack-clear-block: calc(
		var(--jmns-stack-edge)
		+ max(
			max( 0px, calc( var(--jmns-stack-count-start) * var(--jmns-stack-step) - var(--jmns-stack-gap) ) ),
			max( 0px, calc( var(--jmns-stack-count-end) * var(--jmns-stack-step) - var(--jmns-stack-gap) ) )
		)
		+ var(--jmns-stack-gap)
	);

	/*
	 * Horizontal room in each bottom corner, for a row whose own controls sit
	 * flush against that edge — the announcement strip's stepper is the case
	 * this exists for.
	 *
	 * `min( 1, count )` rather than the count itself: an occupied corner
	 * reserves once however many controls are stacked in it, and an empty one
	 * reserves nothing at all instead of a bare edge and gap.
	 */
	--jmns-stack-clear-inline-start: calc(
		min( 1, var(--jmns-stack-count-start) )
		* ( var(--jmns-stack-edge) + var(--jmns-stack-extent-start) + var(--jmns-stack-gap) )
	);
	--jmns-stack-clear-inline-end: calc(
		min( 1, var(--jmns-stack-count-end) )
		* ( var(--jmns-stack-edge) + var(--jmns-stack-extent-end) + var(--jmns-stack-gap) )
	);
}

/*
 * Membership. Not in `:where()` — this is the rule, not a default, and nothing
 * is meant to override it. A member that needs a different stacking order
 * raises `--jmns-stack-z` instead of restating `z-index`, so there is no
 * specificity contest to lose.
 */
[data-jmns-stack] {
	position: fixed;
	inset-block-end: calc(
		var(--jmns-stack-edge) + var(--jmns-stack-order, 0) * var(--jmns-stack-step)
	);
	z-index: var(--jmns-stack-z);
}

[data-jmns-stack="start"] {
	inset-inline-start: var(--jmns-stack-edge);
}

[data-jmns-stack="end"] {
	inset-inline-end: var(--jmns-stack-edge);
}

/*
 * Standing down while the reader is going past.
 *
 * The arrangement above settles where the three controls sit relative to each
 * other. It says nothing about the page they are drawn on, and the page is the
 * thing they were covering: measured across five templates and eleven widths
 * from 320px to 1440px, the corners take a tap meant for 853 interactive
 * elements at some reachable scroll position — the fatwa finder's "Kosongkan
 * semua" reset, the staff directory's division chips, the publications' Cari
 * button, the flipbook's own toolbar. `main` carries `padding-bottom: 128px`,
 * which is `--jmns-stack-clear-block` and protects the end of the document;
 * everything before the end scrolls underneath.
 *
 * `pointer-events: none` is the line that fixes it. The rest is so that a
 * control which cannot be pressed does not sit there looking pressable.
 *
 * Not `visibility` and not `display`. Either would take the controls out of the
 * tab order, and a keyboard visitor scrolls with the arrow keys — so the
 * scroll that hid the control would also be the scroll that made it
 * unreachable. At `opacity: 0` the control keeps its place in the tab order and
 * in the accessibility tree, `:focus-within` below brings it back into view,
 * and stack.js clears the attribute outright on `focusin`.
 *
 * The translate is `calc( 100% + var(--jmns-stack-edge) )` rather than a round
 * number so a control leaves by its own width plus its own inset, whatever the
 * reading-preferences panel has done to the root font size.
 */
:root[data-jmns-stack-away] [data-jmns-stack] {
	pointer-events: none;
	opacity: 0;
}

:root[data-jmns-stack-away] [data-jmns-stack="start"] {
	translate: calc( -1 * ( 100% + var(--jmns-stack-edge) ) ) 0;
}

:root[data-jmns-stack-away] [data-jmns-stack="end"] {
	translate: calc( 100% + var(--jmns-stack-edge) ) 0;
}

/*
 * Whatever the page is doing, the keyboard wins.
 *
 * Beats the three rules above on specificity rather than on source order —
 * they are 0-2-0 and this is 0-3-0 — because this file has been caught by
 * source order before; see the doubled attribute in the print rule below.
 */
:root[data-jmns-stack-away] [data-jmns-stack]:focus-within {
	translate: none;
	pointer-events: auto;
	opacity: 1;
}

/*
 * The movement, and only where movement is wanted.
 *
 * `no-preference` rather than a transition plus a `reduce` override: a visitor
 * who has asked for less movement should not have a declaration written for
 * them and then taken away. The portal's own switch is answered too —
 * access.css collapses every transition under `[data-jmns-motion="on"]`, which
 * reaches this the same way it reaches everything else.
 */
@media (prefers-reduced-motion: no-preference) {
	[data-jmns-stack] {
		transition: translate 160ms ease-out, opacity 160ms ease-out;
	}
}

/*
 * A control that has opened a panel claims its whole corner.
 *
 * Both launchers expand upward into a panel taller than the column they sit in,
 * so a control stacked above one of them is behind it the moment it opens —
 * present, focusable, and invisible under an opaque surface. The bot already
 * hid its own launcher for the same reason (§3 in bot.css); this is that rule
 * generalised to the neighbours, which the bot did not have when it was written
 * and now does.
 *
 * Keyed on `aria-expanded`, which both launchers already carry and keep
 * accurate, rather than on a state class either component happens to set —
 * a third control should not have to learn two components' internals to stand
 * down for them.
 */
body:has( [data-jmns-stack="start"] [aria-expanded="true"] ) [data-jmns-stack="start"]:not(:has( [aria-expanded="true"] )),
body:has( [data-jmns-stack="end"] [aria-expanded="true"] ) [data-jmns-stack="end"]:not(:has( [aria-expanded="true"] )) {
	visibility: hidden;
}

/*
 * And below 30rem the chat panel is not a panel in a corner but the whole
 * screen, so it claims both corners rather than its own. `jmns-bot-open` is set
 * by bot.js on exactly that condition — the same class that stops the page
 * scrolling underneath — so this follows the modality instead of guessing at
 * the breakpoint a second time.
 */
body.jmns-bot-open [data-jmns-stack]:not(:has( [aria-expanded="true"] )) {
	visibility: hidden;
}

/*
 * None of these is content. access.css already withheld its own launcher from
 * print; the chat launcher was printing a green pill onto every page anyone
 * sent to a printer, which is the kind of thing a per-component print rule
 * misses and a stack-wide one cannot.
 */
@media print {
	/*
	 * The attribute twice, so this outranks a member's own `display` rather than
	 * merely following it. At one attribute this is 0-1-0, the same weight as
	 * `.jmns-top { display: inline-flex }` in top.css — and top.css is printed
	 * after this file because it depends on it, so the loser was decided by
	 * enqueue order and the scroll-to-top button printed while the two launchers
	 * did not. Measured: `display: flex` under `emulateMediaType('print')`.
	 */
	[data-jmns-stack][data-jmns-stack] {
		display: none;
	}
}
