/*
 * The Wii U skin.
 *
 * `css/sync.css` says in its own header that it is a harness and that this
 * would replace it. It does not replace it — it sits on top and repaints it.
 * Everything in the sync harness and the setup wizard is laid out in --unit and
 * coloured through the tokens, so a light theme is a matter of changing what
 * the tokens mean and adding the chrome the console had. Rewriting those pages
 * to be light would have meant re-verifying every step of a setup guide that
 * currently works, to change the colour of it.
 *
 * WHY LIGHT, AND WHY IT IS A THEME RATHER THAN A REPLACEMENT
 *
 * The console's menu is near-white — that is most of what makes a screenshot of
 * it recognisable. But a dark room at midnight with a 65" panel at full white
 * is genuinely unpleasant, and the calibration screens are looked at while
 * somebody stands in the dark holding a phone. So the tokens carry both, the
 * plaza forces light because it is a picture of a light place, and
 * `[data-theme="dark"]` on <html> puts the old skin back.
 *
 *   <html data-theme="wiiu">  the console. The default.
 *   <html data-theme="dark">  the harness, as it was.
 */

:root,
:root[data-theme="wiiu"] {
	/* Ink first. The console's text is a soft near-black, never #000 — pure
	   black on near-white buzzes on a television's sharpening filter. */
	--ink: #303840;
	--ink-dim: #6f7b86;
	--ink-faint: #a3aeb8;

	--ground: #e9eff3;
	--panel: rgba(255, 255, 255, 0.90);
	--line: rgba(70, 92, 110, 0.16);

	/* The blue everything on a Wii U is outlined in. */
	--accent: #009bdd;
	--accent-deep: #0079ac;
	--good: #4ab558;
	--warn: #e8a317;
	--bad: #e05144;

	/* Chrome. A Wii U panel is a white slab with a soft drop shadow and a
	   generous radius — the radius is most of the brand. */
	/* Control surfaces. A Wii U button is a white capsule with a blue edge,
	   and the primary one is filled. */
	--btn: #ffffff;
	--btn-hover: #f2f7fa;
	--btn-edge-hover: var(--accent);
	--btn-active: #e4edf2;
	--btn-primary: var(--accent);
	--btn-primary-edge: var(--accent-deep);
	--btn-primary-hover: var(--accent-deep);
	--btn-primary-ink: #ffffff;
	--well: #e2eaef;
	--rule: #cbd7de;
	/* Overlays over a light plaza wash out rather than black out. Not a
	   near-white, though: the pairing cards are white and have to stay the
	   brightest thing on the screen, since being read across a room is the
	   entire job of that overlay. */
	--scrim: rgba(226, 236, 242, 0.95);

	--tile-radius: calc(var(--unit) * 1.1);
	--shadow-lo: 0 calc(var(--unit) * 0.15) calc(var(--unit) * 0.5) rgba(40, 62, 80, 0.14);
	--shadow-hi: 0 calc(var(--unit) * 0.5) calc(var(--unit) * 1.6) rgba(40, 62, 80, 0.22);
}

:root[data-theme="dark"] {
	--ink: #e8e9ec;
	--ink-dim: #8b8f98;
	--ink-faint: #5a5e66;
	--ground: #101114;
	--panel: #17181c;
	--line: #24262c;
	--accent: #5ac8fa;
	--good: #63d68b;
	--warn: #e8b84b;
	--bad: #e8695b;
}

/*
 * The sky.
 *
 * The plaza's floor is drawn in WebGL and fades out rather than ending, so
 * whatever is behind the canvas is the horizon. A flat colour there makes the
 * fade visible as a band; a gradient in roughly the floor's far colour makes
 * the floor appear to reach the top of the screen.
 */
:root[data-theme="wiiu"] body {
	background:
		radial-gradient(120% 78% at 50% 86%, #ffffff 0%, rgba(255, 255, 255, 0) 60%),
		linear-gradient(#cfdde6 0%, #dde8ee 38%, #eaf1f4 70%, #f3f7f8 100%);
	background-attachment: fixed;
}

/*
 * The pairing cards, on a light overlay.
 *
 * The scrim is pale, so the cards' own off-white leaves them barely
 * distinguishable from the screen behind them — and these four glyphs are the
 * one thing on this television that has to survive being read across a room by
 * somebody without their glasses. Pure white plus a real shadow puts them back
 * in front.
 */
:root[data-theme="wiiu"] #code .card {
	background: #ffffff;
	box-shadow: 0 calc(var(--unit) * 0.5) calc(var(--unit) * 1.4) rgba(40, 62, 80, 0.30);
}

/* ---------------------------------------------------------------------
 * The plaza
 *
 * Three stacked layers, all pinned to the viewport: the canvas at the back,
 * the things anchored into the scene above it, and the cursors above those.
 * Anything in the scene layer is positioned in *projected* pixels every frame
 * and must therefore never be laid out — hence the absolute positioning and the
 * translate rather than left/top, which keeps it off the layout thread.
 */

/*
 * The plaza owns the screen while it is up.
 *
 * `position: fixed` alone is not enough, and it looked like it was. The sync
 * harness underneath has `position: relative` on its panels and its meter, so
 * those are *positioned* elements too — and painting among positioned siblings
 * goes by document order, which puts everything after #plaza on top of it. The
 * television showed a plaza with the harness's headings, panels and a green
 * sync needle floating over the crowd.
 *
 * A z-index fixes the paint order, and `body.plaza-open` hides the harness
 * outright: two screens stacked with one of them invisible is a stack waiting
 * to come apart again, and a hidden harness also stops its own clock and dust
 * doing work behind a screen nobody is looking at.
 *
 * Below the setup overlays (z-index 7 to 10) on purpose — pairing, the setup
 * stage and the Mii stage all have to appear over the plaza.
 */
#plaza { position: fixed; inset: 0; overflow: hidden; z-index: 2; }
body.plaza-open > header,
body.plaza-open > .grid { display: none; }
body.plaza-open { padding: 0; }
#plaza[hidden] { display: none; }
#plaza-gl { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#plaza-scene, #plaza-cursors {
	position: absolute; inset: 0; pointer-events: none;
}

/*
 * A floating app block.
 *
 * The console's tiles are a rounded white square with the artwork inset and a
 * hairline of the app's own colour along the bottom. The bob is CSS rather than
 * part of the projection so it costs nothing per frame, and each block gets its
 * own delay from the script — a grid of tiles rising and falling in unison
 * looks like a rendering fault rather than like they are floating.
 */
.block {
	position: absolute;
	/* The projected point is the block's centre, and translate(-50%,-50%) is
	   applied here so the script only ever writes one transform. */
	left: 0; top: 0;
	pointer-events: auto;
	cursor: none;
	transform-origin: 50% 50%;
	will-change: transform;
}
.block .face {
	position: absolute;
	width: calc(var(--unit) * 7.4); height: calc(var(--unit) * 7.4);
	left: calc(var(--unit) * -3.7); top: calc(var(--unit) * -3.7);
	border-radius: calc(var(--unit) * 1.1);
	background: #ffffff;
	box-shadow: var(--shadow-hi), inset 0 0 0 1px rgba(255, 255, 255, 0.9);
	display: flex; align-items: center; justify-content: center;
	overflow: hidden;
	animation: block-bob 4.4s ease-in-out infinite;
	transition: box-shadow 140ms ease;
}
.block .face img, .block .face svg { width: 74%; height: 74%; object-fit: contain; }
.block .face .glyph { font-size: calc(var(--unit) * 3.6); line-height: 1; }

/*
 * The app's colour along the bottom edge.
 *
 * Inside the face, not beside it. As a sibling it positioned itself against
 * the nearest positioned ancestor — which is the whole scene layer, because
 * .block is transformed rather than offset — and every stripe on the screen
 * ended up in a heap across the middle of the plaza, looking for all the world
 * like a renderer fault in the crowd.
 */
.block .face .stripe {
	position: absolute; left: 0; right: 0; bottom: 0;
	height: calc(var(--unit) * 0.6);
	background: var(--accent);
	pointer-events: none;
}
@keyframes block-bob {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(calc(var(--unit) * -0.55)); }
}

/* Hover — the console lifts the tile and rings it in white. A cursor that
   cannot show what it is on is a cursor that cannot be used from a sofa. */
.block.hot .face {
	box-shadow:
		0 0 0 calc(var(--unit) * 0.45) #ffffff,
		0 0 0 calc(var(--unit) * 0.75) var(--accent),
		var(--shadow-hi);
}
.block.hot { z-index: 3; }

.block.dim .face { opacity: 0.46; filter: saturate(0.25); }
.block.dim .face .stripe { background: var(--ink-faint); }

.block .caption {
	position: absolute; left: 50%; top: calc(var(--unit) * 4.3);
	transform: translateX(-50%);
	white-space: nowrap;
	font-size: calc(var(--unit) * 1.25 * var(--text-scale));
	font-weight: 600;
	color: var(--ink);
	background: rgba(255, 255, 255, 0.82);
	padding: calc(var(--unit) * 0.25) calc(var(--unit) * 0.9);
	border-radius: 999px;
	opacity: 0; transition: opacity 120ms ease;
}
.block.hot .caption { opacity: 1; }

/*
 * A speech bubble.
 *
 * Miiverse's, near enough: a white rounded rectangle with a tail, the author's
 * face in a circle on the left. The tail is a rotated square rather than a
 * border triangle so it can carry the same shadow as the body — a triangle made
 * of borders cannot, and the join shows.
 */
/*
 * A bubble is two elements, and that is not an accident.
 *
 * The outer one is a zero-sized point that gets the projected position and the
 * distance scale; the bubble hangs off it upwards, with its tail on that exact
 * point. Doing it in one element means composing "put your bottom-left corner,
 * offset by the width of the tail, here" with a scale — and a transform list
 * that mixes a percentage translate with a scale is resolved against the
 * element's own box in an order that is very easy to get subtly wrong. The
 * first attempt hung every bubble *below* the head that said it.
 */
.bubble-at {
	position: absolute; left: 0; top: 0; width: 0; height: 0;
	transform-origin: 0 0;
	will-change: transform;
}
.bubble {
	position: absolute; left: calc(var(--unit) * -1.8); bottom: 0;
	display: flex; align-items: center; gap: calc(var(--unit) * 0.7);
	/* Its containing block is the zero-width anchor, so shrink-to-fit gives
	   it the narrowest box the text can be crammed into — about four
	   characters a line. `max-content` is what makes it as wide as the
	   sentence, and the max-width then brings a long one back. */
	width: max-content;
	max-width: calc(var(--unit) * 22);
	padding: calc(var(--unit) * 0.55) calc(var(--unit) * 1.1) calc(var(--unit) * 0.55) calc(var(--unit) * 0.55);
	background: #ffffff;
	border-radius: calc(var(--unit) * 1.4);
	box-shadow: var(--shadow-lo);
	font-size: calc(var(--unit) * 1.15 * var(--text-scale));
	color: var(--ink);
	transform-origin: calc(var(--unit) * 2.35) 100%;
	pointer-events: none;
}
.bubble::after {
	content: ''; position: absolute;
	left: calc(var(--unit) * 1.8); bottom: calc(var(--unit) * -0.45);
	width: calc(var(--unit) * 1.1); height: calc(var(--unit) * 1.1);
	background: #ffffff; transform: rotate(45deg);
	border-radius: 2px;
}
.bubble .who {
	width: calc(var(--unit) * 2.4); height: calc(var(--unit) * 2.4);
	border-radius: 50%; flex: none; overflow: hidden;
	background: #eef3f6;
}
.bubble .who img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bubble .said {
	/* Two lines at most. A plaza is read at a glance and a bubble that grows
	   to fit its text stops being a bubble and starts being a paragraph. */
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
	overflow: hidden; line-height: 1.3;
}
.bubble.in  { animation: bubble-in 260ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
.bubble.out { animation: bubble-out 200ms ease both; }
@keyframes bubble-in  { from { opacity: 0; transform: scale(0.4) translateY(6px); } }
@keyframes bubble-out { to   { opacity: 0; transform: scale(0.85); } }

/* ---------------------------------------------------------------------
 * The top bar
 */

#plaza-top {
	position: absolute; left: 0; right: 0; top: 0;
	display: flex; align-items: center; gap: calc(var(--unit) * 1.4);
	padding: calc(var(--unit) * 1.6) calc(var(--unit) * 2.6);
	pointer-events: none;
	/* The console keeps the bar readable over the plaza with a wash rather
	   than a solid bar, so the crowd still shows through it. */
	background: linear-gradient(rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0));
}
#plaza-top .me {
	display: flex; align-items: center; gap: calc(var(--unit) * 0.9);
	background: rgba(255, 255, 255, 0.92);
	border-radius: 999px;
	padding: calc(var(--unit) * 0.35) calc(var(--unit) * 1.3) calc(var(--unit) * 0.35) calc(var(--unit) * 0.35);
	box-shadow: var(--shadow-lo);
}
#plaza-top .me img {
	width: calc(var(--unit) * 3.1); height: calc(var(--unit) * 3.1);
	border-radius: 50%; background: #eef3f6; display: block;
}
#plaza-top .me span { font-weight: 600; font-size: calc(var(--unit) * 1.45 * var(--text-scale)); }
#plaza-top .spacer { flex: 1; }
#plaza-clock {
	font-size: calc(var(--unit) * 2.4 * var(--text-scale));
	font-weight: 600; letter-spacing: -0.01em;
	color: var(--ink);
	text-shadow: 0 1px 0 rgba(255, 255, 255, 0.85);
}
#plaza-clock .date {
	display: block; font-size: calc(var(--unit) * 1.05 * var(--text-scale));
	font-weight: 500; color: var(--ink-dim); text-align: right; letter-spacing: 0.02em;
}
/* The only control in the top bar, and it is there because the sound is off —
   so it has to be pressable, which means it opts back into pointer events the
   bar gives up. */
#plaza-sound {
	pointer-events: auto;
	font: inherit;
	font-size: calc(var(--unit) * 1.4 * var(--text-scale));
	font-weight: 600;
	color: #fff;
	background: var(--warn);
	border: 0;
	border-radius: 999px;
	padding: calc(var(--unit) * 0.5) calc(var(--unit) * 1.6);
	box-shadow: var(--shadow-lo);
	cursor: pointer;
}
#plaza-sound[hidden] { display: none; }

/* The faces are not arriving. One line, in the bar, gone the moment they do. */
#plaza-trouble {
	font-size: calc(var(--unit) * 1.25 * var(--text-scale));
	font-weight: 600;
	color: #fff;
	background: var(--bad);
	border-radius: 999px;
	padding: calc(var(--unit) * 0.35) calc(var(--unit) * 1.3);
	box-shadow: var(--shadow-lo);
}
#plaza-trouble[hidden] { display: none; }

#plaza-weather {
	display: flex; align-items: center; gap: calc(var(--unit) * 0.6);
	font-size: calc(var(--unit) * 1.5 * var(--text-scale)); color: var(--ink-dim);
}

/* Who is in the room, as faces. The console shows connected controllers here
   and this is the same fact: a phone that is paired and awake. */
#plaza-room { display: flex; gap: calc(var(--unit) * 0.5); }
#plaza-room img {
	width: calc(var(--unit) * 2.5); height: calc(var(--unit) * 2.5);
	border-radius: 50%; background: rgba(255, 255, 255, 0.9);
	box-shadow: var(--shadow-lo);
	display: block;
}

/* ---------------------------------------------------------------------
 * Cursors
 *
 * One per pointing phone. The Wii U's is a hand; ours is the ring the Wii
 * Remote drew, which is easier to see over a busy plaza and does not need the
 * artwork. Every cursor is tinted from its member id so two people pointing at
 * the same tile can tell which is theirs.
 */
.cursor {
	position: absolute; left: 0; top: 0;
	width: calc(var(--unit) * 3.4); height: calc(var(--unit) * 3.4);
	margin-left: calc(var(--unit) * -1.7); margin-top: calc(var(--unit) * -1.7);
	border-radius: 50%;
	border: calc(var(--unit) * 0.34) solid currentColor;
	box-shadow: 0 0 0 calc(var(--unit) * 0.12) rgba(255, 255, 255, 0.95),
		inset 0 0 0 calc(var(--unit) * 0.12) rgba(255, 255, 255, 0.95),
		var(--shadow-lo);
	/* No transition. A cursor arrives at ~50 Hz and a transition on top of
	   that is a second, slower cursor lagging the first. */
	will-change: transform;
}
.cursor::after {
	content: ''; position: absolute; inset: calc(var(--unit) * 1.05);
	border-radius: 50%; background: currentColor; opacity: 0.55;
}
.cursor.press { transform-origin: 50% 50%; }
.cursor.press::after { opacity: 1; inset: calc(var(--unit) * 0.7); }
/*
 * Whose cursor this is.
 *
 * A white pill with the cursor's own colour as the text — and not the other
 * way round. `background: currentColor` with `color: #fff` on the same element
 * resolves the background against the colour that element was just given,
 * so the first version of this was white text on a white pill: present in the
 * DOM, invisible on the television, and perfectly healthy in every test that
 * only asked whether the label existed.
 */
.cursor .tag {
	position: absolute; left: 50%; top: calc(var(--unit) * 3.4);
	transform: translateX(-50%);
	font-size: calc(var(--unit) * 1 * var(--text-scale));
	font-weight: 700;
	background: #fff;
	padding: 0 calc(var(--unit) * 0.7); border-radius: 999px;
	box-shadow: var(--shadow-lo);
	white-space: nowrap;
}

/* ---------------------------------------------------------------------
 * The phone's own plaza screens
 *
 * Two ways to drive the television, and the phone is one or the other at any
 * moment — see the note in index.html about why this is a mode and not a
 * gesture the page tries to infer.
 */

.mode-switch {
	display: flex; gap: 0; border-radius: 999px; overflow: hidden;
	background: rgba(120, 140, 160, 0.16); padding: 3px;
}
.mode-switch button {
	flex: 1; border: 0; background: transparent; color: var(--ink-dim);
	font: inherit; font-weight: 600; padding: 0.55em 0.4em; border-radius: 999px;
	cursor: pointer;
}
.mode-switch button[aria-pressed="true"] {
	background: #fff; color: var(--accent-deep); box-shadow: var(--shadow-lo);
}

/* Wii Remote mode: almost nothing on screen, because the screen is not where
   anybody is looking. */
#remote-aim {
	flex: 1; min-height: 38vh;
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	gap: 1em; text-align: center; color: var(--ink-dim);
	border-radius: calc(var(--unit) * 1.2);
	background: rgba(255, 255, 255, 0.55);
	border: 1px dashed var(--line);
}
#remote-aim.live { background: rgba(0, 155, 221, 0.07); border-style: solid; border-color: var(--accent); }
#remote-aim .dot {
	width: 1.1em; height: 1.1em; border-radius: 50%;
	background: var(--ink-faint);
}
#remote-aim.live .dot { background: var(--accent); }

.remote-keys { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7em; }
.remote-keys button {
	font-size: 1.3em; font-weight: 700; padding: 0.9em 0;
	border-radius: 999px; border: 0; color: #fff; background: var(--accent);
	box-shadow: var(--shadow-lo);
}
.remote-keys button.b { background: #7d8894; }
.remote-keys button:active { filter: brightness(0.92); transform: translateY(1px); }

/* GamePad mode: the list. This is the screen the console put on the pad, and
   it is a list rather than the television's floating blocks for the reason the
   console had — you are holding it, so it can be read and scrolled. */
#pad-list { display: flex; flex-direction: column; gap: 0.6em; overflow-y: auto; flex: 1; }
.pad-app {
	display: flex; align-items: center; gap: 0.9em;
	padding: 0.7em 0.9em;
	background: #fff; border-radius: calc(var(--unit) * 1.1);
	box-shadow: var(--shadow-lo);
	border: 0; width: 100%; text-align: left; font: inherit; color: var(--ink);
}
.pad-app .icon {
	width: 2.8em; height: 2.8em; flex: none; border-radius: 0.7em;
	background: #f1f5f7; display: flex; align-items: center; justify-content: center;
	font-size: 1em;
}
.pad-app .icon img { width: 74%; height: 74%; object-fit: contain; }
.pad-app .icon .glyph { font-size: 1.5em; }
.pad-app .text { flex: 1; min-width: 0; }
.pad-app .name { font-weight: 600; display: block; }
.pad-app .note { color: var(--ink-dim); font-size: 0.85em; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pad-app[aria-current="true"] { outline: 3px solid var(--accent); }
.pad-app:disabled { opacity: 0.45; }
.pad-app:active { transform: scale(0.99); }
