/* The login sheet every scene runs in, loaded after app.css on the login page
   only, before the scene's own <name>.css. It owns what no scene restyles
   from scratch: the overscanned backdrop (#wall) and its paper grain, the
   loading cover and the grain reveal, the lift over an on-screen keyboard,
   the native control resets, and a default submit arrow a scene themes
   through custom properties (see #go below). The script half is shell.js. */

:root {
	--paper-opacity: 0.54;
	--reveal: 0.9s;
	/* shell.js re-pins this to 256px / devicePixelRatio; the value here
	   is only the no-script fallback. */
	--grain: 256px;
}

/* The login backdrop every scene paints into (scenes/<name>/<name>.js): full
   bleed, the way a printed page reads. Only the login sheet wears it, so the
   sheet owns the fade - no opacity of its own. */
#wall {
	position: fixed;
	/* Paint well past the sheet, then pull that overscan back with negative
	   margins so it adds no scrollable space. Rubber-band bounce and the
	   keyboard's vacated strip then reveal more marks instead of bare page. */
	top: calc(-1 * var(--overscan));
	right: 0;
	left: 0;
	/* LARGE viewport height: unlike a percentage of the live layout viewport,
	   this does not shrink when a keyboard opens, so the lattice holds still. */
	height: calc(100vh + var(--overscan) * 2);
	height: calc(100lvh + var(--overscan) * 2);
	color: var(--fg);
	pointer-events: none;
	user-select: none;
	z-index: 0;
	/* lifted with the sheet over an on-screen keyboard (shell.js) */
	margin-top: calc(-1 * var(--kb-lift, 0px));
	transition: margin-top var(--kb-glide) ease-out;
}

/* The grain rides on the backdrop rather than on <body> so it inherits the same
   fade: paper and ink arrive together, in one frame, instead of the page
   showing bare grain while the marks are still being built. `opacity` here is
   the strength knob, applied once at composite time - the tile itself is baked
   at full contrast, because scaling grain's contrast down along with its
   brightness is what makes it read as flat grey (see trace-god src/paper.ts). */
#wall::before {
	content: "";
	position: absolute;
	inset: 0;
	background-image: url("/static/paper.png");
	/* --grain pins the tile to DEVICE pixels: a speck stays one physical pixel
	   at any zoom instead of growing into a 2x2 block and turning paper into a
	   screen door. */
	background-size: var(--grain) var(--grain);
	background-repeat: repeat;
	/* Quiet paper tooth: texture behind the ink, not competing with it. */
	opacity: var(--paper-opacity);
}

/* Shortened, not removed. Reduced motion is about movement - parallax, slides,
   spin - and this is a still image coming up from black with nothing moving
   across the screen, so cutting it to zero just gave people a hard flash of a
   full-contrast wall instead. Both opacity fades share this duration. */
@media (prefers-reduced-motion: reduce) {
	:root {
		--reveal: 0.3s;
	}
}

/* The login sheet is exactly one screen, so it never needs to scroll. Holding
   it still is what stops Safari's bounce from dragging the marks off the
   glass. Keyboard panning and pinch zoom still work: those move the VISUAL
   viewport, which the overscanned backdrop below is painted to cover. */
html:has(.scene),
body:has(.scene) {
	height: 100%;
	overflow: hidden;
	/* `hidden` is still a scroll container, just one with no scrollbar. The
	   sheet is `100lvh` while this box is the small viewport, so the body has
	   the url bar's height of scrollable overflow - and ios's focus-reveal
	   scrolls exactly that, dragging the marks down and letting them spring
	   back. `clip` is not a scroll container at all, so scrollIntoView has
	   nothing to move. `hidden` above is only the pre-2022 fallback. */
	overflow: clip;
	overscroll-behavior: none;
}

/* The body used to be taken out of flow (`position: fixed`) to stop Safari
   scrolling the document when it revealed a focused field. `overflow: clip`
   above now removes the scroll range outright, and a fixed body is itself the
   classic trigger for ios shoving fixed subtree around during a keyboard
   transition - so the body stays in flow and the clip does the whole job. */

/* Paint well past the sheet, then pull that overscan back with negative
   margins so it adds no scrollable space. Rubber-band bounce and the
   keyboard's vacated strip then reveal more marks instead of bare page. */
.scene {
	--overscan: 25vh;
	/* how the sheet glides up clear of an on-screen keyboard (shell.js
	   liftOverKeyboard), about as fast as the keyboard itself comes up */
	--kb-glide: 0.25s;
	position: relative;
	top: calc(-1 * var(--kb-lift, 0px));
	/* A stable sheet, not the keyboard's shrinking visual viewport. */
	min-height: 100vh;
	min-height: 100lvh;
	/* Custom controls, so no platform tap overlay on top of the ink. */
	-webkit-tap-highlight-color: transparent;
	transition: top var(--kb-glide) ease-out;
}

/* Touch devices bounce much further, and only they show the keyboard gap. */
@media (pointer: coarse) {
	.scene {
		--overscan: 60vh;
	}
}

/* Hidden while loading under a near-opaque cover, the sheet untouched:
   fully visible, so the browser paints all of it - svg filter tiles, the web
   font, the canvas's first frames - and the shell reveals once that has
   settled (shell.js). Hiding the sheet itself didn't work: at 0 opacity or
   `visibility: hidden` nothing is painted, and at 0.001 ios webkit still
   left it all for the reveal, which then showed bare paper for a second
   and a frozen canvas for two more. The sheet never changes, so nothing
   has to paint when the cover goes. The cover is a hair short of opaque
   (nothing a screen can show) and always its own layer: a fully opaque one
   is culled under, and chromium left the sheet unrastered until the
   reveal, stalling it the same way. It fades in 0.34s under the grain
   (below), or over the whole --reveal as the plain fallback when a scene
   fails or the 8s safety timer fires. */
.scene::after {
	content: "";
	position: fixed;
	z-index: 1;
	inset: 0;
	background-color: var(--bg);
	pointer-events: none;
	opacity: 0;
	visibility: hidden;
	will-change: opacity;
	transition:
		opacity var(--reveal) ease-out,
		visibility 0s linear var(--reveal);
}

html[data-ink-loading="1"] .scene::after {
	opacity: 0.998;
	visibility: visible;
	transition: none;
}

html[data-ink-loading="1"] .scene {
	pointer-events: none;
}

/* Reuse paper.png's irregular alpha as the reveal tooth. Six offset stamps
   make its deliberately quiet paper alpha visible; the combined mask stays
   static and only this layer's opacity changes. Its fill is always var(--bg),
   so the paper's warm rgb channels cannot tint the page. */
.veil {
	position: fixed;
	z-index: 2;
	inset: 0;
	visibility: hidden;
	opacity: 0;
	background-color: var(--bg);
	-webkit-mask-image:
		url("/static/paper.png"), url("/static/paper.png"),
		url("/static/paper.png"), url("/static/paper.png"),
		url("/static/paper.png"), url("/static/paper.png");
	mask-image:
		url("/static/paper.png"), url("/static/paper.png"),
		url("/static/paper.png"), url("/static/paper.png"),
		url("/static/paper.png"), url("/static/paper.png");
	-webkit-mask-size: var(--grain) var(--grain);
	mask-size: var(--grain) var(--grain);
	-webkit-mask-position:
		0 0,
		74px 41px,
		151px 103px,
		23px 179px,
		199px 17px,
		113px 211px;
	mask-position:
		0 0,
		74px 41px,
		151px 103px,
		23px 179px,
		199px 17px,
		113px 211px;
	-webkit-mask-repeat: repeat;
	mask-repeat: repeat;
	pointer-events: none;
}

html[data-ink-ready="1"] .scene::after {
	transition-duration: 0.34s, 0s;
	transition-delay: 0s, 0.34s;
}

html[data-ink-ready="1"] .veil {
	animation: reveal-grain var(--reveal) ease-out forwards;
}

@keyframes reveal-grain {
	from {
		visibility: visible;
		opacity: 1;
	}
	20% {
		visibility: visible;
		opacity: 1;
	}
	to {
		visibility: hidden;
		opacity: 0;
	}
}

@media (prefers-reduced-motion: reduce) {
	html[data-ink-ready="1"] .scene::after {
		transition-duration: 0.12s, 0s;
		transition-delay: 0s, 0.12s;
	}

	/* the lift is a jump, not a glide */
	.scene {
		--kb-glide: 0s;
	}
}

/* Just two inked fields over the printed sheet. */
.scene main {
	position: relative;
	z-index: 1;
	display: grid;
	place-items: center;
	/* LARGE viewport height, like the backdrop: the fields must not re-centre
	   into a keyboard-shrunk viewport and drift off their lattice rows. */
	min-height: max(360px, 100vh);
	min-height: max(360px, 100lvh);
	padding: 3rem 2rem;
	overflow: visible;
}

.scene form {
	position: relative;
	isolation: isolate;
	display: flex;
	flex-direction: column;
	gap: 1.6rem;
	width: min(100%, 320px);
	margin: 0;
}

.scene .field {
	position: relative;
}

/* Every custom control starts from nothing: no platform chrome, box or fill. */
.scene .field input,
#go {
	appearance: none;
	-webkit-appearance: none;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
}

.scene .field input {
	position: relative;
	z-index: 2;
	width: 100%;
	height: 100%;
	min-width: 0;
	/* The field paints its own paper and its own ink frame, so the native control
	   has nothing left to draw. Left on `auto`, ios still paints the platform
	   text field on focus - and under `color-scheme: dark` that is a dark fill
	   and a dark border, which is the black flash across the text and the frame
	   when a field is tapped. The other custom controls here already reset it. */
	color: var(--fg);
	caret-color: var(--fg);
	font: inherit;
	outline: none;
}

.scene .field input::selection {
	color: var(--bg);
	background: var(--fg);
}

/* Engines paint autofill with an !important UA background no author rule can
   beat - but a transition outranks every origin, so park the change behind an
   effectively infinite delay and the field keeps its own paper. The old opaque
   inset box-shadow hid the fill by covering it, which also covered the paper
   and the inner half of the ink frame: the hairline boxes on autofilled
   firefox. */
.scene .field input:autofill {
	transition:
		background-color 0s 99999s,
		color 0s 99999s;
	-webkit-text-fill-color: var(--fg);
	caret-color: var(--fg);
}

/* The submit control: a bare native button each scene places and sizes, with
   an arrow mask inside it. A scene themes it through custom properties rather
   than by out-specifying the rules here:
     --arrow-mask  the arrow's shape, an image; every scene sets one
     --arrow-rest  the arrow's opacity until the form will go (0.91)
     --go-ink      its colour until then (the page's --fg)
     --go-ready    its colour once both fields are filled (the sheet's red)
     --go-hover    and under the pointer then */
#go {
	display: grid;
	place-items: center;
	color: var(--go-ink, var(--fg));
	cursor: pointer;
	transition: color 160ms ease;
}

.scene .arrow {
	background-color: currentColor;
	opacity: var(--arrow-rest, 0.91);
	-webkit-mask: var(--arrow-mask) center / contain no-repeat;
	mask: var(--arrow-mask) center / contain no-repeat;
}

.scene form:valid #go {
	color: var(--go-ready, #c94f42);
}

.scene form:valid #go:hover {
	color: var(--go-hover, #e46c58);
}

.scene form:valid .arrow {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	#go {
		transition: none;
	}
}

/* Nothing filled in, nothing to press. The mark keeps its colour - only the
   cursor changes - so the arrow never looks like a different control when the
   form drains or refills. */
#go:disabled {
	cursor: default;
}

#go:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 3px;
}

.scene .err {
	margin: -0.2rem 0 0;
	padding-left: 0.65rem;
	color: #eab9a5;
	font-size: 0.95rem;
	line-height: 1.4;
}

@media (forced-colors: active) {
	#go {
		border: 1px solid ButtonText;
	}
	#go:focus-visible {
		outline-color: Highlight;
	}
	.scene .arrow {
		forced-color-adjust: none;
		background-color: ButtonText;
	}
	.scene .field input {
		border: 2px solid ButtonText;
		background: Canvas;
		color: CanvasText;
	}
}

/* tune mode (tune.js): every scene's `?tune` panel, one look for all. It
 * sits over everything, fixed to the viewport, and ignores the scene's
 * type and colours. */
.tune {
	position: fixed;
	z-index: 10;
	top: 8px;
	right: 8px;
	width: 280px;
	max-height: calc(100vh - 16px);
	overflow: auto;
	padding: 8px;
	background: #000c;
	color: #eee;
	font: 11px/1.35 ui-monospace, monospace;
	text-align: left;
}
.tune summary {
	cursor: pointer;
	margin: 0 0 2px;
}
.tune-title {
	color: #fc8;
	font-weight: bold;
	text-transform: uppercase;
	letter-spacing: 0.06em;
}
.tune-group + .tune-group {
	margin-top: 8px;
	padding-top: 6px;
	border-top: 1px solid #fff3;
}
.tune-section {
	margin: 0 0 6px 6px;
}
.tune-section > summary {
	color: #8cf;
}
.tune-row {
	display: grid;
	grid-template-columns: 84px minmax(0, 1fr) 44px;
	gap: 4px;
	align-items: center;
}
.tune-name {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.tune-value {
	overflow: hidden;
	white-space: nowrap;
}
.tune input {
	width: 100%;
	min-width: 0;
	height: auto;
	margin: 0;
	padding: 0;
	font: inherit;
}
.tune-bar button {
	margin: 6px 4px 0 0;
	padding: 1px 6px;
	border: 1px solid #fff6;
	background: #222;
	color: #eee;
	font: inherit;
}
/* a section's (or a group's) on/off switch, in its summary */
.tune input.tune-switch {
	width: auto;
	margin: 0 6px 0 0;
	vertical-align: -2px;
	accent-color: #8cf;
	cursor: pointer;
}
/* a section that's off: its keys read their neutral values, its sliders
   keep yours, dimmed */
.tune-off > .tune-row {
	opacity: 0.35;
}
.tune-off > summary {
	text-decoration: line-through;
}
