﻿.site-main.account-main {
  display: grid;
  place-items: center;
  padding-block: 2.5rem;
}

.panel.account-panel {
  width: 100%;
  max-width: 30rem;
  padding: 0;
  overflow: hidden;
}

.account-panel__body {
  padding: 1.75rem;
}

.account-panel h1 {
  font-size: clamp(2rem, 5vw, 2.5rem);
}

.account-panel .field {
  max-width: none;
  gap: 0.25rem;
  margin-bottom: 0.5rem;
}

.account-panel .field > .field__error {
  display: block;
  min-height: 1.4em;
  font-size: 0.875rem;
  line-height: 1.4;
}

.field__label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.account-panel .field:has(.field__hint) > .field__error {
  min-height: 0;
}

.field:has(.field__error:not(:empty)) > .field__hint {
  display: none;
}

.field__hint {
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

.password-toggle {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: underline;
  cursor: pointer;
}

.password-toggle:hover {
  color: var(--ink);
}

.account-remember {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
  font-weight: 600;
  font-size: 0.9375rem;
}

.account-remember input {
  width: 1.125rem;
  height: 1.125rem;
  accent-color: var(--table-edge);
}

/* The label is a flex row, and an author display beats the hidden attribute on its own. */
.account-remember[hidden] {
  display: none;
}

.account-submit {
  width: 100%;
}

/* Turnstile writes its widget in after the page has painted, 65 pixels tall at the flexible size;
   holding that room keeps the button under it from jumping down when it arrives. Its iframe sits
   inline, so without the zero line height the line's descender adds a few pixels more. */
.cf-turnstile {
  min-height: 65px;
  margin-bottom: 0.5rem;
  line-height: 0;
}

.account-switch {
  max-width: none;
  margin: 0;
  padding: 1rem 1.75rem;
  border-top: 0.0625rem solid var(--panel-edge);
  background: rgba(27, 36, 32, 0.04);
  font-size: 0.9375rem;
  color: var(--ink-soft);
  text-align: center;
}

.account-switch a {
  font-weight: 700;
  color: var(--ink);
}

@media (max-width: 40rem) {
  .account-panel__body,
  .account-switch {
    padding-inline: 1.25rem;
  }
}

/* A page that asks for one thing says why above the field, in the panel's quiet ink. */
.account-note {
  margin: 0 0 1.25rem;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--ink-soft);
}

/* Staying signed in and giving up on the password are both answers to the field above them, so
   they share its last row rather than stacking under it. */
.account-options {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

.account-options .account-remember {
  margin-bottom: 0;
}

.account-forgot {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: underline;
}

.account-forgot:hover {
  color: var(--ink);
}

/* A secondary button fills with --panel, which is the panel it sits on, so it needs its own edge
   to read as a control rather than as more panel. */
.account-again {
  width: 100%;
  border: 0.0625rem solid var(--panel-edge);
}

/* The profile is a page, not a form: it takes the shell's width and lays its parts out on the felt
   as panels, rather than borrowing the narrow column a login form wants. */
.profile {
  display: grid;
  gap: 1.5rem;
}

/* Avatar, who it is, and what can be done about it: one row until the panel is too narrow to seat
   them, and the actions drop under the name rather than squeezing it. */
.profile-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
}

/* What a saved change left behind takes the card's first row to itself, above the player it is about. */
.profile-card .account-notice {
  flex-basis: 100%;
  margin: 0;
}

.profile-identity {
  flex: 1 1 14rem;
  min-width: 0;
}

.profile-avatar {
  --avatar-size: 5.5rem;
  --avatar-face: 2.5rem;
}

.profile-name {
  margin: 0;
  font-size: clamp(2rem, 5vw, 2.5rem);
  overflow-wrap: anywhere;
}

/* A note rather than a decoration: the one role worth saying out loud. */
.profile-role {
  display: inline-block;
  margin: 0.5rem 0 0;
  padding: 0 0.5rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.5rem;
}

/* For the person the badge is about it is also the way in to the editors. It stays a badge rather
   than becoming a button — it is already saying something — and says it goes somewhere the way
   "Forgot your password?" does on these same pages: an underline, not an arrow after the label. */
.profile-role--link {
  text-decoration: underline;
  text-decoration-thickness: 0.0625rem;
  text-underline-offset: 0.15em;
}

.profile-role--link:hover {
  background: color-mix(in srgb, var(--accent) 44%, var(--panel));
  text-decoration-thickness: 0.125rem;
}

/* The streak beside the name, in the shape the record's readings already have. It was a tile in the
   felt's own colours first: a dark block on a light card is the activity strip's reading, which is
   dark because it has to lift off a panel, and this one sits on it. The card carries the player, so
   what it says about them is said in the card's own voice. */
.profile-streak {
  display: grid;
  gap: 0.5rem;
}

.profile-streak__readings {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 0;
}

.profile-streak dt {
  color: var(--ink-soft);
  font-size: 0.875rem;
}

.profile-streak dd {
  margin: 0.25rem 0 0;
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 600;
}

/* The readings are about the player and the buttons are about the account, so the card puts more
   than its own gap between them. */
.profile-card .profile-actions {
  margin-left: 1rem;
}

/* A run that is still to be kept today is the one thing on the card asking for something back, and
   it asks in words: there is no colour here doing it instead. */
.profile-streak__note {
  margin: 0;
  max-width: none;
  color: var(--ink-soft);
  font-size: 0.875rem;
}

.profile-streak[data-state="at-risk"] .profile-streak__note {
  color: var(--ink);
  font-weight: 600;
}

.profile-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

/* Each section stands beside the others while there is room for it, and under them when there is not.
   Sections sharing a row share its height, so the row reads as one band rather than a ragged edge. */
.profile-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: 1.5rem;
  align-items: stretch;
}

.profile-sections > .profile-section {
  display: flex;
  flex-direction: column;
}

/* Once all three stand across, they are not the same width: the record is four short readings in two
   columns and fills 17rem of any panel it is given, so the rest of an equal third of the table was
   air beside it. What it gives up goes to the day's boards, whose rows carry a name, where it stands
   and a button, and to the list beside them. */
@media (min-width: 60rem) {
  .profile-sections--daily {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 17rem) minmax(0, 1fr);
  }
}

.profile-section h2 {
  margin: 0 0 0.75rem;
  font-size: 1.1875rem;
}

.profile-empty {
  margin: 0;
  color: var(--ink-soft);
}

.profile-empty__play {
  align-self: flex-start;
  margin-top: 1rem;
  border: 0.0625rem solid var(--panel-edge);
}

/* Four readings across a section, each falling to its own row on a narrow one. */
.profile-record {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
  gap: 1rem;
  margin: 0;
}

.profile-record dt {
  color: var(--ink-soft);
  font-size: 0.875rem;
}

.profile-record dd {
  margin: 0.25rem 0 0;
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 600;
}

/* The activity strip: a month of boards as one bar a day, across the profile's full width above
   the sections. It is a list with two numbers on each item and CSS works the heights out of them,
   so no geometry is computed anywhere and the labels are ordinary text at the reader's own size.

   The two fills are the amber ramp's dark step and the panel's own soft ink, both at or above 3:1
   on the panel and far enough apart to tell one from the other with any colour vision. The bright
   --accent reads at 1.8:1 here, which is fine under a button label and not enough for a mark. */
.profile-activity {
  --mark-won: var(--accent-edge);
  --mark-unfinished: var(--ink-soft);
}

/* The chart's labels are chrome rather than body copy, so the 60ch cap that keeps a paragraph
   readable would pull the axis in to two thirds of the plot it is labelling. */
.activity__legend,
.activity__scale,
.activity__span {
  max-width: none;
}

.activity__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.875rem;
}

/* The swatch carries the identity; the word beside it stays in the panel's ink. */
.activity__key::before {
  content: "";
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  margin-right: 0.375rem;
  border-radius: 0.125rem;
  vertical-align: baseline;
}

.activity__key--won::before {
  background: var(--mark-won);
}

.activity__key--unfinished::before {
  background: var(--mark-unfinished);
}

/* The busiest day, sitting on the rule it names. */
.activity__scale {
  margin: 0 0 0.25rem;
  color: var(--ink-soft);
  font-size: 0.875rem;
  line-height: 1;
}

/* Thirty equal columns standing on one baseline. The two rules around them are the whole axis: the
   top one is the busiest day and the bottom one is nothing. */
.activity {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: end;
  gap: 0.25rem;
  height: 7.5rem;
  margin: 0;
  padding: 0;
  border-top: 0.0625rem solid var(--panel-edge);
  border-bottom: 0.0625rem solid var(--panel-edge);
  list-style: none;
}

/* The whole column is the target, so a quiet day is as easy to point at as a busy one. */
.activity__day {
  position: relative;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 100%;
}

/* The day's two numbers against the busiest day's, which is the only scaling anything does. */
.activity__bar {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  /* A bar is as wide as its column up to a point, and then the column's leftover is simply air. */
  width: min(100%, 1.5rem);
  height: max(0.1875rem, calc(var(--played) / var(--busiest) * 100%));
  border-radius: 0.25rem 0.25rem 0 0;
  overflow: hidden;
}

/* The gap between the two parts is panel showing through, not a stroke around either of them, and
   flex only puts one there when both parts exist. */
.activity__rest {
  flex: 1;
  background: var(--mark-unfinished);
}

.activity__won {
  flex: none;
  height: calc(var(--won) / var(--played) * 100%);
  background: var(--mark-won);
}

/* A day with nothing on it is still a day. It keeps a tick on the baseline so the month reads as
   thirty of them rather than as however many were played, and so there is something to point at. */
.activity__day--empty .activity__bar {
  height: 0.125rem;
  border-radius: 0;
  background: var(--panel-edge);
}

/* Hover answers what a person did, which is the one thing motion is for here. */
.activity__day:hover .activity__bar {
  filter: brightness(1.12);
}

.activity__day:hover .activity__day--empty .activity__bar {
  background: var(--ink-soft);
}

/* The reading lifts off the panel on the felt's own dark band. It is never hidden from a screen
   reader, only from the eye, so the readings are the table behind the chart. */
.activity__reading {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  z-index: 1;
  width: max-content;
  max-width: 11rem;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-control);
  background: var(--rail);
  color: var(--cream);
  font-size: 0.875rem;
  line-height: 1.4;
  text-align: center;
  opacity: 0;
  transform: translateX(-50%);
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .activity__reading {
    transition: opacity var(--duration-hover) ease;
  }
}

.activity__reading b {
  display: block;
  font-family: var(--display);
}

.activity__day:hover .activity__reading,
.activity__day:focus-within .activity__reading {
  opacity: 1;
}

/* A reading centred on the first or last few days would hang off the panel, so those sit against
   the end they belong to instead. */
.activity__day:nth-child(-n + 3) .activity__reading {
  left: 0;
  transform: none;
}

.activity__day:nth-last-child(-n + 3) .activity__reading {
  right: 0;
  left: auto;
  transform: none;
}

.activity__span {
  display: flex;
  justify-content: space-between;
  margin: 0.25rem 0 0;
  color: var(--ink-soft);
  font-size: 0.875rem;
}

/* A section heading with a way onwards at the far end of its row: the heading keeps the space under
   it that a bare one has, so the section below reads the same either way. */
.profile-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.profile-section__head h1,
.profile-section__head h2 {
  margin: 0;
}

.profile-section__more {
  font-size: 0.9375rem;
  font-weight: 600;
  white-space: nowrap;
}

/* The boards behind the record: a row each, newest first, each one a way back to that game. */
.profile-games {
  display: grid;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.profile-game {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0.125rem 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 0.0625rem solid var(--panel-edge);
}

.profile-game:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.profile-game__name {
  grid-column: 1 / -1;
  font-weight: 600;
}

/* A row is a link to the board's own page, but it is read as a record rather than as a control, so
   it keeps the text's colour and underlines only when it is being pointed at. */
a.profile-game__name {
  color: inherit;
  text-decoration: none;
}

a.profile-game__name:hover,
a.profile-game__name:focus-visible {
  text-decoration: underline;
}

.profile-game__variant {
  color: var(--ink-soft);
  font-weight: 400;
}

.profile-game__meta {
  grid-column: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.875rem;
}

/* Playing a board again goes to its own page, so it is the quiet underlined link these panels
   already use for a link that goes somewhere, and not a control on a record. It shares the row with
   the board's reading and sits at the far end of it, so a row is two lines rather than three and the
   links line up down the list. */
.profile-game__replay {
  grid-row: 2;
  grid-column: 2;
  justify-self: end;
  color: var(--ink-soft);
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: underline;
}

.profile-game__replay:hover,
.profile-game__replay:focus-visible {
  color: var(--ink);
}

/* A phone's row is not wide enough for both: the reading wraps around the link and the date ends up
   on a line of its own beside it, so there the link goes back under the reading. */
@media (max-width: 40rem) {
  .profile-game__replay {
    grid-row: auto;
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: 0.25rem;
  }
}

.profile-game__outcome {
  padding: 0 0.375rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-weight: 600;
}

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

.pager__count {
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

/* The same pill the filters are, so the two rows around the list read as one set of controls. */
.pager__step {
  padding: 0.25rem 0.75rem;
  border: 0.0625rem solid var(--panel-edge);
  border-radius: var(--radius-control);
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
}

.pager__step:hover {
  border-color: var(--accent-edge);
}

/* On the edit form the actions are a row under the fields, where a way out takes only the room it needs. */
.account-panel .profile-actions {
  margin-top: 1.75rem;
}

.account-panel .profile-actions .button {
  flex: 1 1 auto;
}

.account-panel .profile-actions .button--quiet {
  flex: 0 0 auto;
}

/* The heading that opens the new-password group on the edit form. */
.account-panel .profile-heading {
  margin: 1.75rem 0 0.25rem;
  font-size: 1.1875rem;
}

/* On a phone the card's buttons have the row to themselves, so they take it rather than sitting
   ragged against the left edge at two different widths. */
@media (max-width: 40rem) {
  .profile-card .profile-streak {
    flex-basis: 100%;
  }

  .profile-card .profile-actions {
    width: 100%;
    margin-left: 0;
  }

  .profile-card .profile-actions .button {
    flex: 1 1 auto;
  }
}

/* Account settings: a list of sections down the left and the one being read on the right, both on
   the shell's width. Below the sidebar's own width the two stack, the list first, which is the
   order they are read in anyway. */
.settings {
  display: grid;
  grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}

.settings-nav {
  padding: 1rem;
}

.settings-nav ul {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-nav a {
  display: block;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-control);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}

.settings-nav a:hover {
  background: color-mix(in srgb, var(--panel-edge) 45%, var(--panel));
}

/* The section being read is marked rather than offered, in a wash of the accent rather than the
   accent itself: a note of where you are, not a button. */
.settings-nav a[aria-current="page"] {
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
}

.settings-nav__back {
  display: inline-block;
  margin-top: 1rem;
  padding: 0 0.75rem;
  color: var(--ink-soft);
  font-size: 0.9375rem;
}

.settings-nav__back:hover {
  color: var(--ink);
}

.settings-body {
  display: grid;
  gap: 1.5rem;
  min-width: 0;
}

.settings-section h1 {
  margin: 0 0 0.75rem;
  font-size: 1.5rem;
}

.settings-section h2 {
  margin: 0 0 0.75rem;
  font-size: 1.1875rem;
}

.settings-heading {
  margin: 1.75rem 0 0.25rem;
  font-size: 1.1875rem;
}

.settings-note {
  margin: 0 0 1.25rem;
  color: var(--ink-soft);
}

.settings-warn {
  margin: -0.75rem 0 1.25rem;
  padding: 0.75rem 1rem;
  border-left: 0.1875rem solid var(--danger);
  border-radius: var(--radius-control);
  background: rgba(200, 16, 46, 0.1);
  font-weight: 600;
  color: var(--danger);
}

.settings-done {
  margin: 1rem 0 0;
  color: var(--ink-soft);
  font-weight: 600;
}

/* Deleting is the one thing here that cannot be taken back, and it says so in the game red the
   destructive button already wears. */
.settings-section--danger {
  border-left: 0.1875rem solid var(--danger);
}

.settings-section--danger h2 {
  color: var(--danger);
}

.settings-section .field {
  max-width: 24rem;
  gap: 0.25rem;
  margin-bottom: 1rem;
}

/* Six digits, so the box is the size of six digits: a field the width of the panel invites
   anything. The wrapper keeps its width, which is what the error line under it needs. */
.field--code input {
  width: 8rem;
}

.settings-section .field > .field__error {
  display: block;
  min-height: 1.4em;
  font-size: 0.875rem;
  line-height: 1.4;
}

@media (max-width: 48rem) {
  .settings {
    grid-template-columns: minmax(0, 1fr);
  }

  .settings-nav ul {
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
  }

  .settings-nav a {
    text-align: center;
  }
}

.settings-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

/* A setting that is on or off, so the label is the sentence and the box beside it is the answer. */
.settings-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.settings-check input {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--accent-edge);
}

.settings-check label {
  font-weight: 600;
}

/* Turning this on is two things in an order, so they are numbered rather than run together as
   notes. The marker wears the sidebar's accent wash: a place in a sequence, not a control. */
.setup-steps {
  display: grid;
  gap: 1.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: setup-step;
}

.setup-step {
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  gap: 0.25rem 0.75rem;
  counter-increment: setup-step;
}

.setup-step::before {
  content: counter(setup-step);
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-family: var(--display);
  font-weight: 600;
}

.setup-step > * {
  grid-column: 2;
}

.setup-step__title {
  margin: 0;
  padding: 0.25rem 0 0;
  font-size: 1.1875rem;
}

.setup-scan {
  display: flex;
  flex-wrap: wrap;
  /* The card is the size of the code, not of whatever stands beside it: a stretched flex item
     grew it downwards and left white under the code. */
  align-items: flex-start;
  gap: 1.5rem;
  margin: 0.75rem 0 0;
}

/* The code paints its own black and white, the way a browse card lets its photograph paint its
   own ground: it is a picture a camera has to read, not a surface to tint. The card only holds
   its shape, so no second white sits under the curve. */
.setup-qr {
  flex: 0 0 auto;
  width: 12rem;
  overflow: hidden;
  border: 0.0625rem solid var(--panel-edge);
  border-radius: var(--radius-control);
  line-height: 0;
}

.setup-qr svg {
  display: block;
  width: 100%;
  height: auto;
  /* The code is square and the card is the code. Stated rather than inferred from the viewBox,
     which is the one thing here an engine is free to read its own way. */
  aspect-ratio: 1;
}

.setup-manual {
  /* The key's eight groups want 30rem. Asked for beside the code, the column takes its own row
     rather than squeezing them onto a second line, and is capped so it never runs the whole
     panel. A button on the key's line pushed them over too, so the button sits under it. */
  flex: 1 1 30rem;
  min-width: 0;
  max-width: 34rem;
}

/* A secondary button fills with --panel, which is the panel it sits on, so here as on a game
   tile it needs its own edge to read as a control rather than as more panel. */
.setup-manual .button--secondary,
.settings-section .button--secondary {
  border: 0.0625rem solid var(--panel-edge);
}

.setup-manual__title {
  margin: 0 0 0.25rem;
  font-weight: 600;
}

.setup-manual > :last-child {
  margin-bottom: 0;
}

.setup-step .settings-note {
  margin-bottom: 0.75rem;
}

/* A secret read off the screen and typed into a phone: spaced, monospaced, and selectable as one. */
.settings-key {
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--panel-edge) 45%, var(--panel));
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.0625rem;
  letter-spacing: 0.05em;
  overflow-wrap: anywhere;
  user-select: all;
}

.settings-codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.settings-codes li {
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--panel-edge) 45%, var(--panel));
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  user-select: all;
}

.settings-codes-actions .button--secondary {
  border: 0.0625rem solid var(--panel-edge);
}

/* What a copy came to, said once beside the buttons rather than by swapping a button's label. It
   stays on the page, empty, so the announcement has somewhere to land. */
.settings-status {
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
}

.settings-status:empty {
  margin: 0;
}

.settings-done {
  margin-top: 1.75rem;
}

.settings-done .settings-check {
  margin-bottom: 0;
}

.settings-done .settings-check input {
  padding: 0;
}

/* Printed, the codes are the page: everything that neither holds them nor is them goes, and what
   holds them lets go of its frame, so the sheet is the list and nothing else. */
@media print {
  html:has([data-recovery-codes]) {
    background: none;
  }

  body:has([data-recovery-codes])::before {
    content: none;
  }

  body:has([data-recovery-codes]) :not(:has([data-recovery-codes]), [data-recovery-codes], [data-recovery-codes] *) {
    display: none;
  }

  body:has([data-recovery-codes]),
  body:has([data-recovery-codes]) :has([data-recovery-codes]) {
    display: block;
    place-items: normal;
    max-width: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  [data-recovery-codes] {
    grid-template-columns: repeat(2, max-content);
    gap: 0.75rem 2.5rem;
    margin: 0;
  }

  [data-recovery-codes] li {
    padding: 0.5rem 0.75rem;
    border: 0.0625rem solid var(--panel-edge);
    background: none;
    color: var(--ink);
    font-size: 1.125rem;
  }
}

/* A daily is one board for everyone, so the row says so, in the quiet type of the variant name. */
.profile-game__daily {
  margin-left: 0.25rem;
  padding: 0 0.375rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
  color: var(--accent-ink);
  font-size: 0.8125rem;
  font-weight: 600;
}

.profile-dailies-frame {
  flex: 1;
  min-height: 0;
}

/* The day's boards scroll inside the panel rather than stretching it. The bar is drawn quietly: it
   is a hint that there is more below, not a control on the panel. */
.profile-dailies {
  display: grid;
  align-content: start;
  gap: 0.5rem;
  margin: 0;
  padding: 0 0.375rem 0 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--ink-soft) 30%, transparent) transparent;
}

.profile-dailies::-webkit-scrollbar {
  width: 0.375rem;
}

.profile-dailies::-webkit-scrollbar-track {
  background: transparent;
}

.profile-dailies::-webkit-scrollbar-thumb {
  border-radius: 0.1875rem;
  background: color-mix(in srgb, var(--ink-soft) 30%, transparent);
}

.profile-dailies:hover::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--ink-soft) 50%, transparent);
}

/* From two sections across, the day's boards take their height from the sections beside them: the
   list is lifted out of what the row is measured from, and scrolls inside the height the row settles
   on, but never under five rows: shorter, most of the day's boards sat below a scroll nobody saw.
   Stacked, there is nothing to match, so the list simply runs to its own length. */
@media (min-width: 41rem) {
  .profile-sections .profile-dailies-frame {
    position: relative;
    min-height: 15rem;
  }

  .profile-sections .profile-dailies {
    position: absolute;
    inset: 0;
  }
}

.profile-daily {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.75rem;
}

.profile-daily__variant {
  color: var(--ink-soft);
  font-weight: 400;
}

/* A button on a panel, so it takes the hairline the lobby's browse button takes; sized to the row. */
.profile-daily__play {
  height: 2.25rem;
  padding-inline: 1rem;
  border: 0.0625rem solid var(--panel-edge);
  font-size: 0.9375rem;
}

/* The variant is a word after the game's name and a line under it when the row is too narrow for
   both, so the two are laid out rather than spaced apart: a margin would hold the second line in
   from the edge the name starts at. */
.profile-daily__name {
  display: flex;
  flex-direction: column;
  color: inherit;
  font-weight: 600;
  text-decoration: none;
}

.profile-daily__name:hover,
.profile-daily__name:focus-visible {
  text-decoration: underline;
}

.profile-daily__status {
  color: var(--ink-soft);
  font-size: 0.875rem;
}

.profile-daily__status--won {
  padding: 0 0.375rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-weight: 600;
}

/* Achievements and collections share one card, a tab each. They are readings rather than trophies —
   every achievement counts something the record above already counts — so both are set like the
   record is, and take the full width of the grid so their lists reflow across it. */
.profile-sections > .profile-progress {
  grid-column: 1 / -1;
}

/* The tabs are a segmented pair with the one being read marked by the settings list's own wash of
   the accent: a note of where you are, not a button. The wash is one piece that slides between them. */
.profile-tabs {
  position: relative;
  display: inline-grid;
  align-self: flex-start;
  grid-auto-columns: minmax(0, 1fr);
  grid-auto-flow: column;
  margin-bottom: 1.25rem;
  padding: 0.25rem;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--panel-edge) 45%, var(--panel));
}

.profile-tabs[hidden] {
  display: none;
}

.profile-tabs::before {
  position: absolute;
  top: 0.25rem;
  bottom: 0.25rem;
  left: 0.25rem;
  width: calc((100% - 0.5rem) / 2);
  border-radius: calc(var(--radius-control) - 0.25rem);
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  box-shadow: 0 0.0625rem 0.125rem rgba(0, 0, 0, 0.08);
  transform: translateX(calc(var(--tab-index, 0) * 100%));
  content: "";
}

.profile-tabs__tab {
  position: relative;
  padding: 0.5rem 1.25rem;
  border: 0;
  border-radius: calc(var(--radius-control) - 0.25rem);
  background: none;
  color: var(--ink-soft);
  font: inherit;
  font-family: var(--display);
  font-size: 1.0625rem;
  font-weight: 600;
  cursor: pointer;
}

.profile-tabs__tab:hover {
  color: var(--ink);
}

.profile-tabs__tab[aria-selected="true"] {
  color: var(--accent-ink);
}

.profile-progress__panel + .profile-progress__panel {
  margin-top: 1.75rem;
}

.profile-progress--tabbed .profile-progress__panel + .profile-progress__panel {
  margin-top: 0;
}

/* Tabbed, the lists share one cell and the first one sizes it, so switching never changes the
   card's height. A list not being read keeps its place but is hidden from sight, focus and screen
   readers alike; any later list adds nothing to the height and scrolls inside what the first gives. */
.profile-progress--tabbed .profile-progress__panels {
  display: grid;
}

.profile-progress--tabbed .profile-progress__panel {
  grid-area: 1 / 1;
}

.profile-progress--tabbed .profile-progress__panel[hidden] {
  display: block;
  visibility: hidden;
}

.profile-progress--tabbed .profile-progress__panel:not(:first-child) {
  contain: size;
  overflow-y: auto;
  padding-inline-end: 0.5rem;
  scrollbar-color: var(--panel-edge) transparent;
  scrollbar-width: thin;
}

.profile-progress__panel:focus-visible {
  outline-offset: 0.5rem;
  border-radius: 0.25rem;
}

/* The tab names the list once the tabs are there; the heading stays for anyone moving by headings. */
.profile-progress--tabbed .profile-progress__title {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  margin: -0.0625rem;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (prefers-reduced-motion: no-preference) {
  .profile-tabs::before {
    transition: transform var(--duration-panel) var(--ease-panel);
  }

  .profile-tabs__tab {
    transition: color var(--duration-hover) ease;
  }

  .profile-progress__panel[data-enter] {
    animation: profile-panel-enter var(--duration-panel) var(--ease-panel);
  }

  .profile-progress__panel[data-enter="start"] {
    --enter-from: -0.75rem;
  }
}

@keyframes profile-panel-enter {
  from {
    opacity: 0;
    transform: translateX(var(--enter-from, 0.75rem));
  }
}

.achievements {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1.25rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Collections: one family a row, divided by a hairline. Its name and reading share a line over its
   bar, in a column of their own so every bar starts at the same place, and its variants sit beside
   them as ways in, centred on that column while they fit on one line. */
.collections {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.collection {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: 0.75rem 1.5rem;
  align-items: center;
  padding: 0.75rem 0;
  border-top: 0.0625rem solid var(--panel-edge);
}

.collection:first-child {
  padding-top: 0;
  border-top: 0;
}

.collection:last-child {
  padding-bottom: 0;
}

.collection__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.collection__name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin: 0;
  font-size: 1rem;
}

/* The family's own game colour, the one its browse page wears, as a dot before its name. */
.collection__name::before {
  flex: none;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: var(--game-color, var(--ink-soft));
  content: "";
}

.collection__name a {
  color: inherit;
  text-decoration: none;
}

.collection__name a:hover {
  text-decoration: underline;
}

.collection__reading {
  flex: none;
  margin: 0;
  font-family: var(--display);
  font-size: 1rem;
  font-weight: 600;
}

.collection--locked .collection__reading {
  color: var(--ink-soft);
}

.collection__bar {
  display: block;
  margin-top: 0.5rem;
}

.collection__variants {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.collection__variant > :first-child {
  display: block;
  padding: 0.25rem 0.5rem;
  border: 0.0625rem solid var(--panel-edge);
  border-radius: var(--radius-control);
  color: var(--ink-soft);
  font-size: 0.875rem;
  line-height: 1.25;
  text-decoration: none;
}

.collection__variant > a:hover {
  border-color: var(--ink-soft);
  color: var(--ink);
}

.collection__variant--won > :first-child {
  border-color: transparent;
  background: color-mix(in srgb, var(--accent) 28%, var(--panel));
  color: var(--accent-ink);
  font-weight: 600;
}

.collection__variant--won > a:hover {
  border-color: var(--accent-edge);
  color: var(--accent-ink);
}

.collection__variant--won > :first-child::before {
  content: "\2713\00a0";
}

@media (max-width: 40rem) {
  .profile-tabs {
    align-self: stretch;
  }

  .collection {
    grid-template-columns: minmax(0, 1fr);
    gap: 0.5rem;
  }
}

.achievement__name {
  margin: 0;
  font-size: 1rem;
}

.achievement__reading {
  margin: 0.25rem 0 0;
  font-family: var(--display);
  font-size: 1.1875rem;
  font-weight: 600;
}

/* One length a tier, laid side by side: the bar says how many steps an achievement has and how far
   through them the reading is, in one mark. A row of pips beside it said the first half again and
   read as three dots rather than as three tiers. */
.achievement__tiers {
  display: flex;
  gap: 0.125rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}

.achievement__tier,
.collection__bar {
  flex: 1;
  height: 0.375rem;
  border-radius: 0.25rem;
  background: var(--panel-edge);
  overflow: hidden;
}

/* The same fill the activity strip's won bars take, because it is the same kind of mark: a length
   that means a count. */
.achievement__tier::before,
.collection__bar::before {
  display: block;
  width: calc(var(--reached) * 100%);
  height: 100%;
  border-radius: inherit;
  background: var(--accent-edge);
  content: "";
}

.achievement__earned {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  margin: 0.375rem 0 0;
  color: var(--ink-soft);
  font-size: 0.875rem;
}

/* Nothing earned yet is still something to aim at, so a locked row is quietened rather than greyed
   out of the way: the name keeps its ink and the bar still shows how far along it is. */
.achievement--locked .achievement__reading {
  color: var(--ink-soft);
}
