@font-face {
  src: url("../fonts/Faustina-VariableFont_wght.ttf");
  font-family: "Faustina";
}

:root {
  --targetX: 50%;
  --defaultGap: 20px;
  /* How long colors on the Settings sheet take to shift to a new theme, mode or contrast. */
  --themeShiftTime: 700ms;
  /* Outside the sheet colors switch at once: nothing there is seen while a theme is chosen. The sheet redeclares this with --themeShiftTime.
      An element whose own transition list ends with this, like button, eases its colors on the sheet and snaps them everywhere else. */
  --colorTransition: background-color 0s, color 0s, border-color 0s, outline-color 0s;
  /* Every element draws inside this centered column. A full-screen layer fills the page with its background and holds its content in a <column>. */
  --columnWidth: 500px;
  /* As a left, right or padding, a percentage is of the viewport for the fixed layers that use this. */
  --columnInset: max(var(--defaultGap), calc((100% - var(--columnWidth)) / 2));
  /* The column's height. Uncapped on a touch device, and capped with the stage on a PC. */
  --columnHeight: 100%;
  --columnBlockInset: max(0px, calc((100% - var(--columnHeight)) / 2));
  --halfGap: calc(0.5 * var(--defaultGap));
  --doubleGap: calc(2 * var(--defaultGap));
  --connectorRetractTime: 40ms;
  --wallRotateTime: 100ms;
  --connectorExtendTime: 40ms;
  --loaderFadeTime: 700ms;
  /* TEMP: matches TEMP_CAROUSEL_MS. */
  --tempCarouselTime: 220ms;

  /* --boardColor: hsl(0, 0%, 100%);
    --wallColor: hsl(219.4, 27.1%, 25.3%); */

  /* Hues first, so one number retints everything built on it. */
  --hueBoard: 219.4;
  --huePage: 219.4;

  /* How strongly the page shows its hue. 1 is the designed amount, 0 is grey, and it can go above 1. */
  --pageSaturation: 1.2;

  /* How much of any hue dark mode keeps. 1 everywhere, and 0 for a grey theme. */
  --chroma: 1;

  /* Multipliers on lightness: darks by --dm, lights by --lm. They are 1 until the OS asks for more contrast. */
  --dm: 1;
  --lm: 1;

  --inkSaturation: 36.1%;
  --inkHSL: var(--hueBoard), var(--inkSaturation), calc(var(--dm) * 30.3%);
  --controlBorderHSL: var(--inkHSL);
  /* The ink is strong on a light page, so the border shows only part of it. The dark blocks set 1. */
  --controlBorderAlpha: 0.3;
  --boardShadeHSL: calc(var(--hueBoard) + 5), calc(var(--chroma) * 44%), calc(var(--dm) * 14%);
  --sandHSL: var(--huePage), calc(var(--chroma) * 38%), calc(var(--lm) * 89%);
  --paperColor: hsl(0, 0%, 100%);

  --wallColor: var(--paperColor);
  --boardColor: hsl(var(--inkHSL));
  --boardShadeColor: hsl(var(--boardShadeHSL));
  --rotatedColor: hsl(25, 100%, 58%);
  --hoverColor: hsl(326.7, 44.9%, 63.7%);

  /* The light theme: a pale wash of the page's hue. */
  --pageHSL: var(--huePage), calc(var(--pageSaturation) * 38%), calc(var(--lm) * 89%);
  --pageGlowHSL: calc(var(--huePage) + 4), calc(var(--pageSaturation) * 60%), calc(var(--lm) * 96%);
  --pageDeepHSL: calc(var(--huePage) - 12), calc(var(--pageSaturation) * 20%), calc(var(--lm) * 76%);
  --pageStripeHSL: 0, 0%, 100%;
  --appForegroundHSL: var(--inkHSL);
  --buttonColor: hsla(var(--pageHSL), 0.5);
  --buttonTextColor: hsl(var(--inkHSL));
  --tertiaryColor: hsla(var(--inkHSL), 0.12);
  --tertiaryTextColor: hsl(var(--inkHSL));
  --startButtonColor: var(--startColor);
  --startButtonTextColor: var(--paperColor);
  --headerButtonColor: hsla(0, 0%, 0%, 0.1);
  --headerButtonTextColor: hsl(0, 0%, calc(var(--dm) * 18%));
  --sheetHSL: var(--huePage), calc(var(--pageSaturation) * 64%), calc(var(--lm) * 95%);
  --shadowColor: hsla(var(--boardShadeHSL), 0.42);

  --appBackground: hsl(var(--pageHSL));
  --pageGlow: hsl(var(--pageGlowHSL));
  --pageDeep: hsl(var(--pageDeepHSL));
  --appForeground: hsl(var(--appForegroundHSL));
  --sheetColor: hsl(var(--sheetHSL));
  --inkColor: hsl(var(--inkHSL));

  --rotatedColorLight: hsl(42.9, 100%, 97.3%);
  --startColor: hsl(201.6, 77%, 62.5%);
  --finishColor: hsl(26.5, 100%, 41.8%);
  --routeColor: hsl(140, 60.6%, 57.3%);
  --routeDarkColor: hsl(140.4, 54.9%, 40%);
  --finalWallColor: hsl(219.1, 40.4%, 88.8%);

  --primaryColor: hsl(26.5, 100%, 41.8%);
  --secondaryColor: var(--wallColor);
  /* How strongly hover tints a button. */
  --hoverShadeAlpha: 0.2;
  /* What hover mixes in: white brightens a button in light mode, and the dark blocks swap it for black to shade. */
  --hoverTintHSL: 0, 0%, 100%;
  /* Rings each cell of a theme swatch, so a light cell still reads on its light page. The dark blocks drop it. */
  --swatchCellRing: inset 0px 0px 0px 1px hsla(0, 0%, 0%, 0.18);
  /* A button sits a little above the page, and settles closer to it while pressed. */
  --buttonShadow: 0px 1px 2px hsla(0, 0%, 0%, 0.25);
  --buttonPressedShadow: 0px 0px 1px hsla(0, 0%, 0%, 0.2);
  /* A primary button's edge: a translucent shade over its own color, so it reads as a deeper rim of the same color in every theme. */
  --primaryBorderColor: hsla(0, 0%, 0%, 0.18);
}

/* Darks go darker and lights go lighter, including the board's. */
@media (prefers-contrast: more) {
  :root {
    --dm: 0.25;
    --lm: 1.75;
  }
}

/* The High contrast toggle, for a player whose OS doesn't ask for it. Keep the values matching the block above. */
:root[data-contrast="more"] {
  --dm: 0.25;
  --lm: 1.75;
}

/* The dark theme. The player chooses it in Settings, and it never follows the OS. */
:root[data-theme="dark"] {
  --huePage: var(--hueBoard);
  --pageSaturation: var(--chroma);
  --pageHSL: var(--huePage), calc(var(--pageSaturation) * 58%), calc(var(--dm) * 14%);
  --pageGlowHSL: calc(var(--huePage) + 4), calc(var(--pageSaturation) * 52%), calc(var(--dm) * 23%);
  --pageDeepHSL: calc(var(--huePage) - 12), calc(var(--pageSaturation) * 65%), calc(var(--dm) * 7%);
  --pageStripeHSL: 0, 0%, 0%;
  --appForegroundHSL: var(--sandHSL);
  --buttonColor: hsla(var(--sandHSL), 0.1);
  --buttonTextColor: hsl(var(--sandHSL));
  --tertiaryColor: hsla(0, 0%, 100%, 0.1);
  --tertiaryTextColor: hsl(var(--sandHSL));
  --startButtonColor: var(--finishColor);
  --startButtonTextColor: var(--paperColor);
  --headerButtonColor: hsla(0, 0%, 100%, 0.1);
  --headerButtonTextColor: hsl(0, 0%, calc(var(--lm) * 84%));
  --sheetHSL: var(--hueBoard), calc(var(--chroma) * 46.1%), calc(var(--dm) * 10.3%);
  --controlBorderHSL: var(--hueBoard), var(--inkSaturation), calc(var(--lm) * 30.3%);
  --controlBorderAlpha: 1;
  --primaryBorderColor: hsla(0, 0%, 100%, 0.2);
  --hoverTintHSL: 0, 0%, 0%;
  --swatchCellRing: none;
  --shadowColor: hsla(0, 0%, 0%, 0.55);
}

/* Themes. data-palette on <html> picks one, and each tile in Settings carries its own so its swatch paints from the same block.
    A block sets only hues and the board's accents, and the light and dark blocks above build on them.
    navy repeats :root's values, so its tile still shows them while another theme is on. TEMP: every theme after navy is a stub. */
[data-palette="navy"] {
  --hueBoard: 219.4;
  --huePage: 219.4;
  --startColor: hsl(201.6, 77%, 62.5%);
  --finishColor: hsl(26.5, 100%, 41.8%);
  --routeColor: hsl(140, 60.6%, 57.3%);
  --routeDarkColor: hsl(140.4, 54.9%, 40%);
  --rotatedColor: hsl(25, 100%, 58%);
  --hoverColor: hsl(326.7, 44.9%, 63.7%);
  --primaryColor: hsl(26.5, 100%, 41.8%);
}

[data-palette="moss"] {
  --hueBoard: 150;
  --huePage: 60;
  --startColor: hsl(38, 92%, 50%);
  --finishColor: hsl(355, 75%, 50%);
  --routeColor: hsl(190, 80%, 60%);
  --routeDarkColor: hsl(190, 70%, 42%);
  --rotatedColor: hsl(330, 90%, 66%);
  --hoverColor: hsl(270, 60%, 70%);
  --primaryColor: hsl(355, 75%, 46%);
}

[data-palette="plum"] {
  --hueBoard: 290;
  --huePage: 330;
  --startColor: hsl(175, 65%, 40%);
  --finishColor: hsl(40, 100%, 45%);
  --routeColor: hsl(95, 70%, 58%);
  --routeDarkColor: hsl(95, 60%, 40%);
  --rotatedColor: hsl(15, 100%, 62%);
  --hoverColor: hsl(190, 70%, 65%);
  --primaryColor: hsl(330, 75%, 45%);
}

[data-palette="ember"] {
  --hueBoard: 8;
  --huePage: 25;
  --startColor: hsl(200, 85%, 45%);
  --finishColor: hsl(45, 100%, 45%);
  --routeColor: hsl(160, 65%, 52%);
  --routeDarkColor: hsl(160, 60%, 36%);
  --rotatedColor: hsl(55, 100%, 60%);
  --hoverColor: hsl(280, 60%, 70%);
  --primaryColor: hsl(200, 85%, 40%);
}

/* Color blind friendly. Built from the Okabe-Ito colors, and start and finish always differ in lightness as well as hue. */
[data-palette="clear"] {
  --hueBoard: 220;
  --huePage: 210;
  --startColor: hsl(202, 77%, 63%);
  --finishColor: hsl(26, 100%, 42%);
  --routeColor: hsl(56, 85%, 60%);
  --routeDarkColor: hsl(50, 80%, 40%);
  --rotatedColor: hsl(41, 100%, 55%);
  --hoverColor: hsl(327, 45%, 64%);
  --primaryColor: hsl(202, 100%, 35%);
}

[data-palette="bright"] {
  --hueBoard: 240;
  --huePage: 45;
  --startColor: hsl(41, 100%, 55%);
  --finishColor: hsl(202, 100%, 35%);
  --routeColor: hsl(164, 100%, 40%);
  --routeDarkColor: hsl(164, 100%, 28%);
  --rotatedColor: hsl(56, 85%, 65%);
  --hoverColor: hsl(327, 45%, 64%);
  --primaryColor: hsl(202, 100%, 35%);
}

/* For blue-yellow color blindness: red against teal, with no blue and yellow pairs. */
[data-palette="tritan"] {
  --hueBoard: 200;
  --huePage: 15;
  --startColor: hsl(350, 80%, 62%);
  --finishColor: hsl(180, 70%, 30%);
  --routeColor: hsl(0, 0%, 88%);
  --routeDarkColor: hsl(0, 0%, 60%);
  --rotatedColor: hsl(340, 90%, 75%);
  --hoverColor: hsl(180, 50%, 60%);
  --primaryColor: hsl(350, 70%, 45%);
}

/* Works with no color at all: every piece sits at its own lightness. */
[data-palette="contrast"] {
  --hueBoard: 220;
  --huePage: 220;
  --inkSaturation: 10%;
  --pageSaturation: 1;
  --startColor: hsl(50, 100%, 75%);
  --finishColor: hsl(270, 50%, 50%);
  --routeColor: hsl(190, 90%, 60%);
  --routeDarkColor: hsl(190, 90%, 40%);
  --rotatedColor: hsl(30, 100%, 60%);
  --hoverColor: hsl(0, 0%, 75%);
  --primaryColor: hsl(270, 50%, 40%);
}

[data-palette="cocoa"] {
  --hueBoard: 28;
  --huePage: 40;
  --inkSaturation: 12%;
  --pageSaturation: 0.5;
  --startColor: hsl(185, 24%, 45%);
  --finishColor: hsl(345, 28%, 55%);
  --routeColor: hsl(90, 22%, 55%);
  --routeDarkColor: hsl(90, 20%, 38%);
  --rotatedColor: hsl(45, 40%, 58%);
  --hoverColor: hsl(200, 24%, 70%);
  --primaryColor: hsl(345, 28%, 48%);
}

[data-palette="fern"] {
  --hueBoard: 100;
  --huePage: 90;
  --inkSaturation: 12%;
  --pageSaturation: 0.5;
  --startColor: hsl(290, 22%, 58%);
  --finishColor: hsl(30, 38%, 52%);
  --routeColor: hsl(55, 36%, 58%);
  --routeDarkColor: hsl(55, 32%, 40%);
  --rotatedColor: hsl(345, 34%, 62%);
  --hoverColor: hsl(195, 28%, 68%);
  --primaryColor: hsl(290, 20%, 45%);
}

[data-palette="lagoon"] {
  --hueBoard: 185;
  --huePage: 170;
  --inkSaturation: 12%;
  --pageSaturation: 0.5;
  --startColor: hsl(50, 38%, 50%);
  --finishColor: hsl(350, 32%, 58%);
  --routeColor: hsl(130, 24%, 55%);
  --routeDarkColor: hsl(130, 22%, 38%);
  --rotatedColor: hsl(25, 40%, 60%);
  --hoverColor: hsl(270, 24%, 72%);
  --primaryColor: hsl(350, 30%, 48%);
}

/* Greyscale: no hue anywhere, and every piece at its own lightness. */
[data-palette="charcoal"] {
  --hueBoard: 0;
  --huePage: 0;
  --chroma: 0;
  --inkSaturation: 0%;
  --pageSaturation: 0;
  --startColor: hsl(0, 0%, 88%);
  --finishColor: hsl(0, 0%, 55%);
  --routeColor: hsl(0, 0%, 70%);
  --routeDarkColor: hsl(0, 0%, 48%);
  --rotatedColor: hsl(0, 0%, 62%);
  --hoverColor: hsl(0, 0%, 78%);
  /* Near black, under the dark page's lightness, so it reads apart from the tertiary tint in both modes. */
  --primaryColor: hsl(0, 0%, 8%);
}

[data-palette="indigo"] {
  --hueBoard: 245;
  --huePage: 245;
  --startColor: hsl(245, 75%, 62%);
  --finishColor: hsl(245, 90%, 45%);
  --routeColor: hsl(65, 70%, 78%);
  --routeDarkColor: hsl(65, 55%, 58%);
  --rotatedColor: hsl(245, 100%, 86%);
  --hoverColor: hsl(245, 60%, 70%);
  --primaryColor: hsl(245, 80%, 40%);
}

[data-palette="violet"] {
  --hueBoard: 268;
  --huePage: 268;
  --startColor: hsl(268, 75%, 62%);
  --finishColor: hsl(268, 90%, 45%);
  --routeColor: hsl(88, 70%, 78%);
  --routeDarkColor: hsl(88, 55%, 58%);
  --rotatedColor: hsl(268, 100%, 86%);
  --hoverColor: hsl(268, 60%, 70%);
  --primaryColor: hsl(268, 80%, 40%);
}

[data-palette="berry"] {
  --hueBoard: 335;
  --huePage: 335;
  --startColor: hsl(335, 75%, 62%);
  --finishColor: hsl(335, 90%, 45%);
  --routeColor: hsl(155, 70%, 78%);
  --routeDarkColor: hsl(155, 55%, 58%);
  --rotatedColor: hsl(335, 100%, 86%);
  --hoverColor: hsl(335, 60%, 70%);
  --primaryColor: hsl(335, 80%, 40%);
}

[data-palette="slate"] {
  --hueBoard: 205;
  --huePage: 205;
  --startColor: hsl(205, 75%, 62%);
  --finishColor: hsl(205, 90%, 45%);
  --routeColor: hsl(25, 70%, 78%);
  --routeDarkColor: hsl(25, 55%, 58%);
  --rotatedColor: hsl(205, 100%, 86%);
  --hoverColor: hsl(205, 60%, 70%);
  --primaryColor: hsl(205, 80%, 40%);
}

/* Themes are changed from the Settings sheet, which covers everything else, so only the sheet and what is on it ease their colors.
    :where keeps this at zero specificity, so any rule with its own transition wins. Such a rule adds var(--colorTransition) to its list
    if it shows on the sheet, as button, sheet, checkbox and swatch do. Everything else switches at once, unseen behind the sheet. */
:where(sheet, sheet *),
:where(sheet *)::before,
:where(sheet *)::after {
  transition: var(--colorTransition);
}

html,
body {
  margin: 0px;
  padding: 0px;
  width: 100%;
  height: 100%;
  overflow: hidden;
  color: var(--appForeground);
  font-family: "Faustina", serif;
  user-select: none;
  -webkit-user-select: none;
}

html {
  /* The glow sits where the board's light does, up and to the left, so the page and the board share one room. */

  background-image: linear-gradient(var(--sheetColor), transparent 200px), repeating-linear-gradient(90deg, hsla(var(--pageStripeHSL), 0.1) 0px, hsla(var(--pageStripeHSL), 0.1) 40px, hsla(var(--pageStripeHSL), 0.05) 40px, hsla(var(--pageStripeHSL), 0.05) 80px), radial-gradient(120% 75% at 22% 0%, var(--pageGlow), transparent 70%), linear-gradient(165deg, var(--appBackground) 35%, var(--pageDeep));

  /* Never seen, because the last gradient is opaque, but iOS tints the status bar from it, so it is the top of the page's color. */
  background-color: var(--sheetColor);
}

/* iOS only hands its text size setting to this font keyword. A desktop Mac would get its small system size, hence the coarse pointer. */
@media (pointer: coarse) {
  @supports (font: -apple-system-body) {
    html {
      font: -apple-system-body;
      font-family: "Faustina", serif;
    }

    body {
      font-size: 103.5%;
    }
  }
}

body {
  margin: 0px auto;
  width: calc(100% - 40px);
  max-width: var(--columnWidth);
  height: 100%;
  overflow: visible;
  font-size: 110%;
  line-height: 1.3em;
}

[v-cloak] {
  opacity: 0;
  transition: 250ms ease-out opacity;
}

.hidden {
  visibility: hidden;
}

.disabled,
:disabled {
  opacity: 0.1;
  pointer-events: none;
}

h1,
h2,
h3,
h4,
h5 {
  margin: 80px 0px 0px 0px;
  font-weight: normal;
  line-height: 100%;
}

.startTextColor {
  color: var(--startColor);
  font-weight: bold;
}

.finishTextColor {
  color: var(--finishColor);
  font-weight: bold;
}

/* Vue's mount point, so that app itself can carry bindings. It adds nothing to the layout. */
root {
  display: contents;
}

app {
  display: flex;
  align-content: center;
  justify-content: center;
  /* align-items: center;
    flex-direction: column; */
  height: 100%;
}

stage {
  display: flex;
  flex-direction: column;
  align-content: center;
  justify-content: center;
  align-items: center;
  gap: var(--defaultGap);
  margin: 0px auto;
  width: 100%;
}

/* On a PC the stage stops at 1024px and sits in the middle of a taller window. */
@media (hover: hover) and (pointer: fine) {
  :root {
    --columnHeight: 963px;
  }

  stage {
    align-self: center;
    max-height: var(--columnHeight);
  }
}

home,
howto {
  display: block;
  margin: 0px auto;
  background: hsl(0, 0%, 100%);
  width: 100%;
  height: 100%;
}

quit {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  z-index: 1;
  cursor: pointer;
  padding-right: 20px;
  width: 110px;
  height: 50px;
  font-size: 200%;
}

home,
howto {
  display: none;
  position: absolute;
  flex-direction: column;
  align-content: center;
  justify-content: flex-start;
  align-items: center;
  z-index: 1000;
  box-shadow: 0px 0px 20px hsla(0, 0%, 0%, 0.4);
  background: hsl(0, 0%, 95.3%);
}

home.show,
howto.show {
  display: flex;
}

button {
  /* all doesn't reach a custom property, so the hover shade is named on its own.
      background-image is held at 0ms: the gradient changes every frame as the alpha moves, and a browser that cross-fades
      images, as Safari does, would restart that fade each frame and flicker. */
  transition:
    150ms ease-in-out all,
    0ms background-image,
    200ms ease-in-out --buttonHoverAlpha,
    var(--colorTransition);
  cursor: pointer;
  box-shadow: var(--buttonShadow);
  border: 0px solid transparent;
  border-radius: 3px;
  /* The hover shade, painted over the background color and under the text. Clear until hovered. */
  background-image: linear-gradient(hsla(var(--hoverTintHSL), var(--buttonHoverAlpha)), hsla(var(--hoverTintHSL), var(--buttonHoverAlpha)));
  background-color: var(--buttonColor);
  padding: var(--halfGap) var(--defaultGap) calc(1px + var(--halfGap));
  min-width: 5.3rem;
  color: var(--buttonTextColor);
  font-weight: bold;
  font-size: 110%;
  line-height: 100%;
  font-family: "Faustina";
}

button icon {
  background-color: hsl(0, 0%, 100%);
}

button.primary {
  /* margin-bottom: var(--defaultGap); */
  border-color: var(--primaryBorderColor);
  background-color: var(--primaryColor);
  /* padding-bottom: calc(var(--defaultGap)); */
  color: var(--paperColor);
  /* font-size: 1.2em; */
}

button.tertiary {
  box-shadow: none;
  background-color: var(--tertiaryColor);
  color: var(--tertiaryTextColor);
}

button.quaternairy {
  box-shadow: none;
  background-color: transparent;
}

home nav button.primary {
  margin-bottom: 0;
}

/* The gradient's alpha, from fully clear to the hover shade. Registered as a number so the button's transition can animate it. An unregistered variable would snap. */
@property --buttonHoverAlpha {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@media (hover: hover) {
  button:hover {
    --buttonHoverAlpha: var(--hoverShadeAlpha);
  }
}

/* Pressed: the button drops a pixel and its shadow tightens, as if pushed into the page. It goes down fast and comes back up at the normal pace.
    translate, not transform, so it adds to a button that is already positioned with transform, like Start. */
button:active {
  translate: 0px 1px;
  transition-duration: 50ms;
  box-shadow: var(--buttonPressedShadow);
}

/* A tertiary button has no shadow to tighten, so it only drops. */
button.tertiary:active {
  box-shadow: none;
}

/* home #donate:focus,
  home #donate:active,
  button:focus,
  button:active {
    transform: translateY(1px);
    box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0.3);
  } */

home img {
  display: block;
  margin: 0px auto;
  width: 50%;
}

chrome {
  display: flex;
  position: absolute;
  top: 0px;
  flex-direction: row;
  justify-content: start;
  align-items: center;

  margin: 0px auto;
  background: var(--sheetColor);
  padding: var(--halfGap) var(--defaultGap);
  width: calc(100% - var(--defaultGap));
  max-width: var(--columnWidth);
}

header {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  gap: var(--halfGap);
  width: 100%;
}

stage header {
  flex-direction: row;
  gap: 0;
}

/* At large text the buttons give way, so the move count never gets crushed. */
header button {
  flex: 0 1 auto;
  padding-right: var(--halfGap);
  padding-left: var(--halfGap);
  width: 5.3rem;
  min-width: 0px;
}

header button.tertiary {
  background-color: var(--headerButtonColor);
  color: var(--headerButtonTextColor);
}

header sullied {
  flex: none;
}

howto chrome {
  justify-content: flex-end;
}

howto h1 {
  margin-top: 30px;
}

/* The one thing outside the column: fixed to the page's own bottom left corner, clear of the safe areas. */
version {
  display: block;
  position: fixed;
  bottom: calc(10px + env(safe-area-inset-bottom));
  left: calc(10px + env(safe-area-inset-left));
  padding: 0px;
  font-size: 0.8em;
}

logo {
  display: inline-block;
  mask-image: url(/images/big_tent_logo.svg);
  -webkit-mask-image: url(/images/big_tent_logo.svg);
  mask-position: center;
  mask-size: 120px 99.188px;
  mask-repeat: no-repeat;
  cursor: pointer;
  margin-top: 40px;
  background: hsl(0, 100%, 34%);
  width: 120px;
  height: 99.188px;
  -webkit-mask-size: 120px 99.188px;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  opacity: 0.7;
  transition: 100ms ease-out opacity;
}

logo:hover {
  opacity: 1;
}

sullied {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  /* border: 3px solid var(--finishColor); */
  /* border-radius: 4px; */
  /* background-color: var(--boardColor); */
  padding-bottom: 4px;
  overflow: hidden;
  color: var(--appForeground);

  font-weight: bold;
  font-size: 3em;
  line-height: 100%;
}

sullied small {
  display: flex;
  justify-content: center;
  padding: 0px 10px 3px;
  min-width: 100%;
  font-weight: normal;
  font-size: 0.3em;
  line-height: 100%;
  letter-spacing: 1px;
  text-transform: uppercase;
}

date {
  display: flex;
  justify-content: end;
  font-weight: bold;
}

/* TEMP: the board's date between the arrows that step through the dailies. */
dateline {
  display: flex;
  align-items: center;
  gap: var(--halfGap);
  /* A sideways swipe on the date steps dailies, so the browser keeps only vertical panning here. */
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

dateline datewrap {
  justify-content: center;
  align-items: center;
  /* border: 1px solid var(--boardColor); */
  border-radius: 8px;
  width: 7.5em;
  height: 100%;
  overflow: hidden;
}

/* Fixed and centered, so the arrows stay put as the date's length changes. In em, so it follows the text size. */
dateline date {
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
}

dateline button.tertiary {
  background-color: transparent;
  padding: var(--halfGap);
  min-width: 0px;
}

dateline button icon {
  background-color: var(--headerButtonTextColor);
}

/* TEMP: a daily slides away from the arrow that was tapped, and the next waits off the other side with no transition, then slides in. The date slides with it. */
stage playarea,
stage dateline date {
  transition:
    var(--tempCarouselTime) ease-in translate,
    var(--tempCarouselTime) ease-in opacity;
}

stage.outleft playarea,
stage.inleft playarea,
stage.outleft dateline date,
stage.inleft dateline date {
  translate: -30% 0px;
  opacity: 0;
  pointer-events: none;
}

stage.outright playarea,
stage.inright playarea,
stage.outright dateline date,
stage.inright dateline date {
  translate: 30% 0px;
  opacity: 0;
  pointer-events: none;
}

stage.inleft playarea,
stage.inright playarea,
stage.inleft dateline date,
stage.inright dateline date {
  transition: none;
}

time {
  display: block;
  cursor: pointer;
  padding: 0px 20px;
  text-align: center;
  /* margin-top: 30px; */
  /* font-family: 'Courier New', Courier, monospace; */
}

addtime {
  display: block;
  position: absolute;
  transform: translateY(-20px);
  opacity: 0;
  transition: 3000ms ease-out all;
  color: var(--appForeground);
  font-size: 1.5em;
  font-family: "Bangers";
  user-select: none;
  -webkit-user-select: none;
  margin-left: -0.6em;
}

addtime.flyaway {
  transform: translateY(20px);
  opacity: 1;
  transition: 0ms linear none;
}

puzzle {
  display: flex;
  justify-content: center;
}

/* TEMP: the bare daily list on the Puzzles page, until its real design. */
puzzles {
  display: flex;
  flex-direction: column;
  gap: var(--halfGap);
  margin-bottom: var(--defaultGap);
  width: 100%;
  max-width: 28rem;
}

/* The date and the modifiers stack on the left, with the status beside both. */
puzzles puzzle {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--halfGap);
  align-items: center;
  cursor: pointer;
  box-sizing: border-box;
  border: 1px solid hsla(var(--controlBorderHSL), var(--controlBorderAlpha));
  border-radius: 3px;
  padding: var(--halfGap) var(--defaultGap);
  color: hsl(var(--appForegroundHSL));
}

puzzles puzzle status {
  grid-row: 1 / span 2;
  grid-column: 2;
}

/* The board's date sits at the end of its row, and this one starts its own. */
puzzles puzzle date {
  justify-content: start;
}

puzzles puzzle modifiers {
  display: block;
  opacity: 0.7;
  font-size: 0.85em;
}

puzzles puzzle.solved {
  opacity: 0.5;
}

status {
  display: inline;
}

playarea {
  display: block;
  cursor: pointer;
  box-sizing: border-box;
  aspect-ratio: 1;
  width: 100%;
  overflow: visible;
}

board {
  display: block;
  width: 100%;
  height: 100%;
}

/* visible so the start and finish circles can straddle the corners past the viewBox */
board svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

playarea {
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

/* the drawing is in board units: one unit is the gap between two posts */
rect.wall {
  transition:
    var(--wallRotateTime) ease-in-out var(--connectorRetractTime) transform,
    var(--wallRotateTime) ease-in-out var(--connectorRetractTime) fill;
  fill: var(--wallColor);
}

path.connector {
  fill: var(--wallColor);
}

/* a changed join is a new element: the old one retracts into its post, the wall rotates, then the new one extends */
path.connector.connector-leave-active {
  animation: var(--connectorRetractTime) ease-in connector-retract forwards;
}

path.connector.connector-enter-active {
  animation: var(--connectorExtendTime) ease-out calc(var(--connectorRetractTime) + var(--wallRotateTime)) connector-extend backwards;
}

@keyframes connector-retract {
  to {
    transform: scale(0);
  }
}

@keyframes connector-extend {
  from {
    transform: scale(0);
  }
}

rect.wall.sullied {
  fill: var(--rotatedColor);
}

rect.wall.aimed {
  transition: none;
  fill: var(--hoverColor);
}

circle.start,
rect.start {
  transition: 250ms ease-out fill;
  fill: var(--startColor);
}

circle.start.complete,
rect.start.complete {
  fill: var(--routeColor);
}

circle.finish,
rect.finish {
  transition: 250ms ease-out fill;
  fill: var(--finishColor);
}

circle.finish.complete,
rect.finish.complete {
  fill: var(--routeColor);
}

path.path {
  transition: 250ms ease-out stroke-width;
  fill: none;
  stroke-linecap: round;
}

rect.frame {
  fill: var(--boardColor);
}

path.border {
  fill: none;
  stroke: var(--wallColor);
  stroke-linecap: round;
}

rect.gridsquare {
  fill: hsla(0, 0%, 100%, 0.05);
}

p,
instructions {
  margin: var(--defaultGap);
  text-align: center;
}

sheet p {
  margin: 0 var(--defaultGap);
}

instructions {
  display: flex;
  flex-direction: column;
  max-width: 19rem;
  /* font-style: italic; */
}

instructions p {
  display: inline-flex;
  margin: 0;
  padding: 0;
  /* line-height: 100%; */
}

instructions p icon {
  display: inline;
  background-color: var(--startColor);
}

splash {
  display: flex;
  position: fixed;
  top: 0px;
  right: 0px;
  bottom: 0px;
  left: 0px;
  justify-content: center;
  align-items: center;
  z-index: 3000;
  transition: 400ms ease-out opacity;
  background: var(--sheetColor);
}

splash.gone {
  opacity: 0;
  pointer-events: none;
}

/* Waits for the loader's sheet to finish fading in beneath it, so the splash never fades over a half-drawn sheet. */
splash.loading {
  transition-delay: 300ms;
}

splash h1,
home h1 {
  display: flex;
  align-items: center;
  gap: 0.25em;
  margin: 0px;
  font-size: 4em;
}

home h1 {
  color: var(--appForeground);
}

home h2 {
  margin: 30px auto;
  color: var(--appForeground);
}

home {
  position: fixed;
  top: var(--columnBlockInset);
  right: var(--columnInset);
  bottom: var(--columnBlockInset);
  left: var(--columnInset);
  justify-content: center;
  z-index: 1;
  box-shadow: none;
  background-color: transparent;
  width: auto;
  height: auto;
}

nav,
subnav {
  display: flex;
  flex-direction: column;
  gap: var(--halfGap);
}

subnav {
  flex-direction: row;
}

home button,
sheet nav button,
sheet subnav button {
  width: 11.75rem;
}

subtitle {
  color: var(--startColor);
  letter-spacing: 1px;
  text-transform: uppercase;
}

home h2 {
  padding: var(--defaultGap);
}

update {
  cursor: pointer;
  text-decoration: underline;
}

/* a board reached through the modal is revealed by the sheet; a shared link has no sheet, so it fades in */
stage.fadein {
  animation: 400ms ease-out fadein;
}

@keyframes fadein {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

modal {
  display: flex;
  position: fixed;
  top: 0px;
  right: 0px;
  bottom: 0px;
  left: 0px;
  visibility: hidden;
  z-index: 2000;
  backdrop-filter: blur(0px);
  transition: 300ms ease-out all;
  pointer-events: none;
}

modal.show {
  visibility: visible;
  pointer-events: auto;
}

/* Only a modal that fades blurs what is under it. A sliding sheet is opaque and covers it. */
modal.show:has(sheet.fades) {
  backdrop-filter: blur(8px);
}

sheet {
  display: flex;
  position: relative;
  justify-content: center;
  align-items: center;
  transform: translateY(100%);
  /* Declared here, not at :root, so it resolves with the shift time for the sheet and everything on it. */
  --colorTransition: background-color var(--themeShiftTime) ease-in-out, color var(--themeShiftTime) ease-in-out, border-color var(--themeShiftTime) ease-in-out, outline-color var(--themeShiftTime) ease-in-out;
  transition:
    300ms ease-out transform,
    var(--colorTransition);
  box-sizing: border-box;
  background: hsla(var(--sheetHSL), 1);
  width: 100%;
  height: 100%;
}

/* A full-screen layer's content, centered in it. The layer's background fills the page, and everything it holds stays inside this. */
column {
  display: flex;
  position: relative;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--defaultGap);
  box-sizing: border-box;
  padding: 20px 0px;
  width: calc(100% - 2 * var(--defaultGap));
  max-width: var(--columnWidth);
  height: 100%;
  max-height: var(--columnHeight);
}

modal.show sheet {
  transform: translateY(0px);
}

/* A sliding modal's Close stays at the sheet's top right, so a list that scrolls has to scroll inside the sheet. */
sheet chrome {
  right: 0px;
  justify-content: flex-end;
  gap: var(--halfGap);
  box-sizing: border-box;
  margin: 0px;
  background: none;
  padding: calc(env(safe-area-inset-top) + var(--defaultGap)) 0px 0px;
  width: auto;
  max-width: none;
}

/* Every chrome button is at least as tall as one carrying an icon, so Cancel and Save don't move the layout when they replace the close. */
sheet chrome button {
  flex: 0 1 auto;
  padding-right: var(--halfGap);
  padding-left: var(--halfGap);
  width: auto;
  min-width: unset;
  min-height: calc(var(--smallIconSize) + 2 * var(--halfGap) + 1px);
}

/* A modal showing Close is pinned to the top: Close, then its title, both fixed, and the rest scrolls in <scroll> under them.
    Without Close, the content stays centered. */
/* No top padding, so Close sits where it did when it was positioned over the column. */
column:has(> chrome) {
  justify-content: flex-start;
  padding-top: 0px;
}

column:has(> chrome) > chrome {
  position: static;
  flex: none;
  align-self: flex-end;
}

column:has(> chrome) > h2 {
  flex: none;
}

column:has(> chrome) > scroll {
  flex: 1;
  min-height: 0px;
}

/* The mask fades content out at both edges as it scrolls, and the padding keeps content at rest clear of the fade. */
scroll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--defaultGap);
  mask-image: linear-gradient(180deg, transparent, hsl(0, 0%, 0%) var(--defaultGap), hsl(0, 0%, 0%) calc(100% - var(--defaultGap)), transparent);
  box-sizing: border-box;
  padding: var(--defaultGap) 0px;
  width: 100%;
  overflow-y: auto;
}

/* A modal that fades in place instead of sliding. It is see-through, and its modal blurs what is under it. */
sheet.fades {
  transform: none;
  opacity: 0;
  transition: 300ms ease-out opacity;
  background: hsla(var(--sheetHSL), 0.95);
}

/* An animation, because the class and show arrive together and a transition would have nothing to start from. */
modal.show sheet.fades {
  opacity: 1;
  animation: 300ms ease-out fadein;
}

/* The loader never slides. It fades in place, stays opaque, and drops out of view only once it has faded, so the next modal still slides up from below. */
sheet.still {
  opacity: 0;
  transition:
    var(--loaderFadeTime) ease-out opacity,
    0ms transform var(--loaderFadeTime);
}

/* The modal hides itself when its transition ends, so it has to last as long as the loader's fade or it cuts it short. The loader is opaque, so the board fades in from under it with no blur. */
modal:has(sheet.still) {
  backdrop-filter: none;
  transition-duration: var(--loaderFadeTime);
}

modal.show sheet.still {
  opacity: 1;
  transition: 300ms ease-out opacity;
}

sheet h2 {
  margin: 0px 0 var(--defaultGap);
  text-align: center;
}

sheet subnav button {
  max-width: 5.3rem;
}

/* A section's title inside a modal's scrolling content. The first sits at the top, and the rest leave a gap above. */
scroll h3 {
  opacity: 0.7;
  margin: var(--defaultGap) 0px 0px;
  font-size: 0.85em;
  letter-spacing: 1px;
  text-transform: uppercase;
}

scroll h3:first-child {
  margin-top: 0px;
}

/* The toggle from Facets, in this game's colors: a labelled row with a switch that slides on. */
checkbox {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  transition:
    150ms ease-in-out opacity,
    var(--colorTransition);
  cursor: pointer;
  box-sizing: border-box;
  border: 1px solid hsla(var(--controlBorderHSL), var(--controlBorderAlpha));
  border-radius: 3px;
  background-color: var(--buttonColor);
  padding: var(--halfGap) var(--defaultGap);
  width: 100%;
  max-width: 20rem;
  color: var(--buttonTextColor);
  font-weight: bold;
  line-height: 140%;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

checkbox.disabled {
  opacity: 0.5;
  pointer-events: none;
}

checkbox toggle {
  display: flex;
  flex-shrink: 0;
  transition:
    150ms ease-in-out background-color,
    var(--colorTransition);
  box-shadow: inset 0px 1px 4px hsla(0, 0%, 0%, 0.25);
  border-radius: 13px;
  background-color: var(--tertiaryColor);
  width: 46px;
  height: 26px;
}

checkbox toggle:before {
  display: block;
  transition:
    150ms ease-in-out transform,
    var(--colorTransition);
  margin: 2px;
  box-shadow: 0px 1px 2px hsla(0, 0%, 0%, 0.4);
  border-radius: 50%;
  background-color: var(--paperColor);
  width: 22px;
  height: 22px;
  content: " ";
}

checkbox.on toggle {
  background-color: var(--primaryColor);
}

checkbox.on toggle:before {
  transform: translateX(20px);
}

/* The theme picker, after Kindred's. */
/* Four across, one row per kind: the originals, color blind friendly, desaturated, then single-hue. */
themes {
  display: grid;
  grid-template-columns: repeat(4, auto);
  row-gap: var(--halfGap);
  justify-content: center;
  width: 100%;
}

theme {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--halfGap);
  cursor: pointer;
  padding: var(--halfGap) 0px;
  width: 80px;
  font-size: 0.9em;
  -webkit-tap-highlight-color: transparent;
  /* Repeats :root's ink and page, because a variable resolves where it is declared and the tile's own hue would not reach them. */
  /* The saturations repeat :root's too, so a tile whose theme doesn't set them shows the default, not the theme that is on. */
  --inkSaturation: 36.1%;
  --pageSaturation: 1.2;
  --chroma: 1;
  --inkHSL: var(--hueBoard), var(--inkSaturation), calc(var(--dm) * 30.3%);
  --controlBorderHSL: var(--inkHSL);
  --pageHSL: var(--huePage), calc(var(--pageSaturation) * 38%), calc(var(--lm) * 89%);
}

/* The tile's page in dark. Keep it matching the dark --pageHSL. */
:root[data-theme="dark"] theme {
  --pageSaturation: var(--chroma);
  --pageHSL: var(--hueBoard), calc(var(--pageSaturation) * 58%), calc(var(--dm) * 14%);
  --controlBorderHSL: var(--hueBoard), var(--inkSaturation), calc(var(--lm) * 30.3%);
}

/* A tiny board: start in its top left, a route that turns down, and finish in its bottom right. Cells are wide, like the cylinder's. */
swatch {
  display: flex;
  flex-direction: column;
  gap: var(--halfGap);
  transition:
    150ms ease-in-out box-shadow,
    var(--colorTransition);
  box-sizing: border-box;
  border: 1px solid hsla(var(--controlBorderHSL), var(--controlBorderAlpha));
  border-radius: 6px;
  /* The tile's own page, since --appBackground resolves at :root to the theme that is on. */
  background-color: hsl(var(--pageHSL));
  padding: var(--halfGap);
  aspect-ratio: 4 / 5;
  width: 70%;
}

/* The chosen theme's board gets a wall around its edge. */
theme.selected swatch {
  outline: 2px solid var(--wallColor);
  outline-offset: 0px;
}

swatch start {
  display: block;
  flex: 1;
  box-shadow: var(--swatchCellRing);
  border-radius: 3px;
  background-color: var(--startColor);
}

swatch route {
  display: block;
  border-radius: 3px;

  background-color: var(--primaryColor);
  height: 2px;
}

swatch finish {
  display: block;
  flex: 1;
  box-shadow: var(--swatchCellRing);
  border-radius: 3px;
  background-color: var(--routeColor);
}

name {
  display: block;
  opacity: 0.7;
  text-align: center;
}

theme.selected name {
  opacity: 1;
  font-weight: bold;
}

/* always takes its space, so the confirmation does not shift when it shows */
loader {
  display: block;
  visibility: hidden;
  animation: 800ms linear infinite spin;
  box-sizing: border-box;
  margin-bottom: var(--defaultGap);
  border: 4px solid hsla(0, 0%, 100%, 0.25);
  border-top-color: var(--paperColor);
  border-radius: 50%;
  width: 44px;
  height: 44px;
}

sheet.loading loader {
  visibility: visible;
}

sheet.loading nav {
  pointer-events: none;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

toast {
  display: block;
  position: fixed;
  right: 20px;
  bottom: calc(20px + env(safe-area-inset-bottom));
  left: 20px;
  z-index: 4000;
  cursor: pointer;
  margin: 0px auto;
  box-shadow: 0px 5px 15px hsla(0, 0%, 0%, 0.3);
  border-radius: 8px;
  background: var(--paperColor);
  padding: 14px 18px;
  max-width: 460px;
  color: var(--inkColor);
}

lens {
  display: block;
  position: fixed;
  z-index: 200;
  border: 2px solid var(--wallColor);
  border-radius: 50%;
  background: var(--boardColor);
  width: 150px;
  height: 150px;
  overflow: hidden;
  pointer-events: none;
}

lens svg {
  display: block;
  width: 100%;
  height: 100%;
}

lens target {
  display: block;
  position: absolute;
  top: calc(50% - 4px);
  left: calc(50% - 4px);
  border: 1px solid var(--wallColor);
  border-radius: 50%;
  width: 6px;
  height: 6px;
}

/* TEMP: cylinder trial. The play area is the room left in the stage, and the cylinder scales to fit inside it.
   The flat board's stage has the same frame, so the header, date and instructions sit in the same places on every shape. */
stage {
  position: relative;
  box-sizing: border-box;
  padding: calc(env(safe-area-inset-top) + var(--defaultGap)) 0px calc(env(safe-area-inset-bottom) + var(--halfGap));
  height: 100%;
}

/* The flat board stays square for its hit test, so it centers in the room the cylinder's play area would fill. */
stage:not(.cylinder) playarea {
  margin: auto 0px;
}

/* This sits in the corner the curve leaves free, and only the buttons take touches, so the board stays playable beside them. */
boardtools {
  display: flex;
  position: absolute;
  right: 0px;
  bottom: calc(env(safe-area-inset-bottom) + var(--halfGap));
  left: 0px;
  justify-content: flex-end;
  z-index: 1;
  pointer-events: none;
}

boardtools button {
  padding: var(--halfGap);
  min-width: 0px;
  pointer-events: auto;
}

boardtools button.tertiary {
  background-color: var(--headerButtonColor);
}

boardtools button icon {
  background-color: var(--headerButtonTextColor);
}

playarea.cylinder {
  position: relative;
  flex: 1 1 0px;
  aspect-ratio: auto;
  width: calc(100% - 2 * var(--doubleGap));
  min-height: 0px;
}

/* Once Start is pressed the date and instructions are gone, and the cylinder takes the room they leave. */
stage.grown playarea.cylinder {
  margin-bottom: var(--doubleGap);
  width: calc(100% - 4 * var(--halfGap));
}

stage.opening playarea.cylinder {
  pointer-events: none;
}

/* The locked board sits back at a slight lean, and comes up straight and at full strength over the opening. The time matches CYLINDER_OPEN_MS. */
/* rotate, not transform, because the opening's growth animates transform on this same element. Nothing can be tapped until the lean is gone, so the hit test never sees it. */
stage.locked cylinder {
  rotate: 3deg;
}

stage.opening cylinder {
  transition:
    1300ms ease-out opacity,
    1300ms ease-out rotate;
}

playarea.cylinder button {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  background-color: var(--startButtonColor);
  padding: var(--defaultGap) var(--doubleGap) calc(2px + var(--defaultGap));
  pointer-events: auto;
  color: var(--startButtonTextColor);
  font-size: 1.6em;
  /* A gentler hover shade, since the same shade over a button this size reads as heavy. */
  --hoverShadeAlpha: 0.08;
  /* Its shadow is cast onto the cylinder in WebGL, so it has no CSS one. */
  box-shadow: none;
}

/* Start is bigger, so it drops further. */
playarea.cylinder button:active {
  translate: 0px 2px;
}

/* opacity, not visibility: a hidden clipPath clips the route away to nothing */
playarea.cylinder board {
  position: absolute;
  opacity: 0;
  width: 0px;
  height: 0px;
}

playarea.cylinder cylinder,
playarea.cylinder canvas {
  display: block;
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The light is up and to the left, so the cylinder's shadow lands down and to the right. */
/* The shape's outline is worked out in script and drawn on a layer inside, so the blur reaches past its clip. */
playarea.cylinder shadow {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(calc(-50% + 14px), calc(-50% + 16px));
  filter: blur(14px);
}

playarea.cylinder shadow::before {
  position: absolute;
  clip-path: var(--shadowOutline);
  inset: 0;
  background: var(--shadowColor);
  content: "";
}
