@import url('tokens/fonts.css');
@import url('tokens/colors.css');
@import url('tokens/typography.css');
@import url('tokens/spacing.css');
@import url('tokens/brand.css');

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--shr-blue-navy); }
body { font-family: var(--font-sans); color: var(--text-body); }

/* Phone-width column; full-bleed on real phones, centred card on desktop */
#app { height: 100dvh; display: flex; justify-content: center; }
.app-viewport {
	width: 100%; max-width: 480px; height: 100dvh; display: flex; flex-direction: column;
	background: var(--surface-app); position: relative; overflow: hidden;
	padding-top: env(safe-area-inset-top);
}
.app-viewport--brand-top { background-image: linear-gradient(var(--shr-blue) 0 env(safe-area-inset-top), transparent env(safe-area-inset-top)); }

.screen-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }

/* App.razor's <FocusOnNavigate Selector="h1"/> parks focus on the new screen's heading after every
   navigation, so a screen reader reads the screen out and the keyboard tab order restarts at the
   top. That focus is a routing aid, not something anyone reached for: nobody clicked or tabbed to
   the heading, and the heading is not interactive. Painting the browser's focus ring on it would
   report an affordance that is not there, on every screen, at load. Interactive elements keep
   their rings — this exempts the heading only. */
h1:focus, h1:focus-visible { outline: none; }

/* Text for assistive technology only — the sentence under a skeleton's role="status" wrapper,
   which a sighted candidate reads off the shapes instead. The usual clip recipe: not display:none,
   which would hide it from readers too. */
.shr-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Blazor's unhandled-error banner. The framework flips `display` to block inline when an
   exception escapes, so everything here describes the *shown* state and `display: none` is the
   only thing the framework overrides. Styled as a danger-tinted bar pinned to the bottom of the
   phone column so it reads as part of this app rather than a browser artefact. */
#blazor-error-ui {
	display: none;
	position: fixed; left: 0; right: 0; bottom: 0; z-index: 1100;
	margin: 0 auto; width: 100%; max-width: 480px;
	padding: 12px 40px 12px 16px;
	background: var(--shr-danger-tint); color: var(--shr-danger);
	font-size: 14px; line-height: 1.4;
	border-top: 1px solid var(--shr-danger);
	box-shadow: 0 -2px 12px rgba(0, 43, 63, 0.18);
	padding-bottom: calc(12px + env(safe-area-inset-bottom));
}
#blazor-error-ui .reload { color: var(--shr-danger); font-weight: 500; text-decoration: underline; }
#blazor-error-ui .dismiss {
	position: absolute; top: 8px; right: 12px;
	cursor: pointer; color: var(--shr-danger); font-size: 16px; line-height: 1;
	padding: 4px;
}

/* Every skeleton wrapper (role="status") — Skeletons.jsx's "show after 300ms": a read that
   lands fast never flashes a skeleton. A zero-length animation with a 300ms delay and a
   backwards fill keeps the wrapper invisible until the delay is up and then leaves it shown;
   the reader hears the sentence either way. */
.shr-loading {
	animation: shr-loading-in 0s 300ms both;
}

@keyframes shr-loading-in {
	from { visibility: hidden; }
	to { visibility: visible; }
}
