/* Chess dice page (plugin Widgets.ChessStore.ChessPuzzles).

   No Figma frame draws this page; it composes the theme's primitives (CLAUDE.md,
   "Figma source-of-truth deviations"): the `.chip` filters; the die and the
   solution as one row, the amber `.btn` pill with the quiet text action beside
   it; the shared confirm dialog; the hero's round step arrows for the playable
   board; and the store's book card laid out as a row.
   This sheet only places them and draws the board, whose dark squares are the
   category tile's cream and whose light squares are the page itself.

   The puzzle is laid out the way a problem book prints a diagram, at every
   width: the composer and source above the board, the stipulation under its
   left corner and the piece count (5+9) under its right, and under those the
   line of the visitor's own moves. On a desktop the board stands alone on the
   left, sized from the viewport height as well as its width, and the right
   column opens with the heading, explains the game, holds the filters, the
   die and the solution, and ends with the book. Below the desktop breakpoint
   it is one column that puts the board straight under the filters (the
   heading, the filters, the board, the book, then the introduction), and the
   die and the solution stand on the shared bottom action bar
   (`.ui-action-bar`), held at the foot of the screen. */

.puzzle-stage {
  --puzzle-diagram-width: min(100%, var(--puzzle-board-max), max(var(--puzzle-board-min), calc(100svh - var(--puzzle-page-chrome))));

  display: grid;
  justify-items: center;
  gap: var(--space-md);
}

/* One column, as wide as the board, until the desktop grid places them. */
.puzzle-stage__title,
.puzzle-intro,
.puzzle-filters,
.puzzle-empty,
.puzzle-diagram,
.puzzle-actions,
.puzzle-book {
  width: var(--puzzle-diagram-width);
}

/* ---- Introduction and filters ---------------------------------------------- */

.puzzle-stage__title {
  margin: 0;
  font-size: var(--font-size-3xl);
  line-height: var(--line-height-tight);
}

/* The heading takes focus only after Back or Forward, when the replaced stage
   took the focused control with it (chess-puzzles.js). It is not a control, so
   it draws no ring. */
.puzzle-stage__title:focus-visible {
  outline: none;
}

.puzzle-intro {
  margin: 0;
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
}

.puzzle-filters {
  display: grid;
  gap: var(--space-2xs);
}

/* The title with the notation key's info toggle beside it. The key's bubble
   hangs from this row, not from the toggle at its end, so it starts at the
   title's edge and stays within the gutters on a phone. */
.puzzle-filters__heading {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
}

/* The right column's section labels share one size under the page's heading:
   what the die can land on, and the book the puzzle comes from. */
.puzzle-filters__title,
.puzzle-book__title {
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-snug);
}

.puzzle-filters__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}


/* The notation key opens in the shared info bubble (components.css), floating
   under the heading so opening it moves neither the chips nor the die. */
.puzzle-key {
  color: var(--color-muted);
}

.puzzle-key__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  list-style: none;
  cursor: pointer;
}

/* Safari draws the disclosure triangle from its own pseudo-element, which
   `list-style` does not reach. */
.puzzle-key__toggle::-webkit-details-marker {
  display: none;
}

.puzzle-key__note {
  position: absolute;
  z-index: var(--toast-z);
  top: 100%;
  left: 0;
  width: max-content;
  max-inline-size: min(var(--info-note-max-width), calc(100vw - var(--gutter) - var(--gutter)));
  margin: 0;
}

/* ---- Die glyph -------------------------------------------------------------
   Drawn in `currentColor` through a mask, so it follows the text it stands
   beside: the header link's hover, the die button's outline and pill states. */

.die-icon {
  flex-shrink: 0;
  width: var(--die-icon-size);
  height: var(--die-icon-size);
  background-color: currentColor;
  -webkit-mask: url("../../../images/icon-die.svg") no-repeat center / contain;
  mask: url("../../../images/icon-die.svg") no-repeat center / contain;
}

/* The header link's die: the glyph in an amber circle, as the card's add to
   cart is the cart in one, so the feature stands out among text links in the
   theme's own accent. */
.die-mark {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: var(--die-mark-size);
  height: var(--die-mark-size);
  border-radius: var(--radius-pill);
  background-color: var(--color-action);
  color: var(--color-ink);
}

.die-mark .die-icon {
  width: 1em;
  height: 1em;
}

/* The once-a-week turn (die-hint.js); --motion-dice is 0 under reduced
   motion, and the script does not add the class then either. */
.die-mark--hint {
  animation: puzzle-die-roll var(--motion-dice) var(--ease-out) var(--die-hint-delay) var(--die-hint-turns);
}

/* ---- Home page die ---------------------------------------------------------
   The same amber die, at the size of the card's icon actions, floating in the
   home page's corner (the plugin's "show the die on the home page" setting).
   Its note opens beside it in the shared info bubble on hover and keyboard
   focus; on touch a tap simply opens the page. */

.die-fab {
  position: fixed;
  z-index: var(--die-fab-z);
  right: var(--gutter);
  bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-card);
  font-size: var(--font-size-xl);
  text-decoration: none;
}

.die-fab__mark {
  width: var(--action-height);
  height: var(--action-height);
}

.die-fab__note {
  position: absolute;
  top: 50%;
  right: calc(100% + var(--space-2xs));
  width: max-content;
  max-inline-size: min(var(--info-note-max-width), calc(100vw - var(--gutter) - var(--gutter) - var(--action-height)));
  opacity: 0;
  pointer-events: none;
  transform: translate(var(--space-3xs), -50%);
  transition:
    opacity var(--motion-card) var(--ease-out),
    transform var(--motion-card) var(--ease-out);
}

.die-fab:focus-visible .die-fab__note {
  opacity: 1;
  transform: translate(0, -50%);
}

@media (hover: hover) and (pointer: fine) {
  .die-fab:hover .die-fab__note {
    opacity: 1;
    transform: translate(0, -50%);
  }
}

/* ---- Diagram -------------------------------------------------------------- */

.puzzle-diagram {
  position: relative;
  display: grid;
  grid-template-areas:
    "source"
    "board"
    "caption";
  gap: var(--space-xs);
}

.puzzle-source {
  grid-area: source;
  text-align: center;
}

.puzzle-diagram > .puzzle-board {
  grid-area: board;
}

.puzzle-source p,
.puzzle-caption p {
  margin: 0;
}

.puzzle-source__composer {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-snug);
  overflow-wrap: anywhere;
}

.puzzle-source__description {
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-snug);
  white-space: pre-line;
}

/* Read from both corners: the stipulation over its words on the left, the
   piece count over the side to move on the right. */
.puzzle-caption {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  grid-area: caption;
}

.puzzle-caption__side {
  display: grid;
  justify-items: start;
  gap: var(--space-3xs);
}

.puzzle-caption__side--end {
  justify-items: end;
  text-align: end;
}

.puzzle-caption__mark {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
}

.puzzle-caption__name,
.puzzle-caption__turn {
  color: var(--color-muted);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-snug);
}

/* ---- Actions: the die and the solution -------------------------------------
   One row at every width: the pill at its own width floats to the row's start
   and the solution's quiet text action sits beside it at the pill's height;
   the opened solution clears the float and runs the row's full width under
   both. Not a flex row, because the opened text lives inside the <details>
   with its summary: the details is inline, so its summary shares the pill's
   line (and the row's intrinsic width is the pill plus the summary), while
   the opened text drops to a block of its own under both. */

.puzzle-actions__pair {
  display: flow-root;
}

.puzzle-die {
  float: inline-start;
  margin-inline-end: var(--space-md);
}

.puzzle-solution {
  display: inline;
}

.puzzle-solution__summary {
  list-style: none;
}

/* Scoped to the row: the quiet primitive's own `min-height: 0` in
   controls.css would otherwise win, since the shared sheets load last. */
.puzzle-actions .puzzle-solution__summary {
  min-height: var(--action-height);
}

/* "Show solution" while it is closed, "Hide solution" while it is open. Both
   labels share one grid cell and the inactive one is only made invisible, so
   the action is as wide as its longer label either way and opening the
   solution never shifts the pill beside it. The summary is scoped here: the
   quiet primitive's own `display` in controls.css would otherwise win, since
   the shared sheets load last. */
.puzzle-solution .puzzle-solution__summary {
  display: inline-grid;
}

.puzzle-solution__show,
.puzzle-solution__hide {
  grid-area: 1 / 1;
}

.puzzle-solution:not([open]) .puzzle-solution__hide,
.puzzle-solution[open] .puzzle-solution__show {
  visibility: hidden;
}

/* The disclosure triangle the browser adds; the summary is a button. */
.puzzle-solution__summary::-webkit-details-marker {
  display: none;
}

.puzzle-solution__text {
  clear: both;
  margin: var(--space-2xs) 0 0;
  /* Its own length never widens the row: it wraps within the pill and the
     text action above it. */
  contain: inline-size;
  padding: var(--space-sm);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-normal);
  text-align: start;
  white-space: pre-line;
}

.puzzle-die.is-rolling .puzzle-die__icon {
  animation: puzzle-die-roll var(--motion-dice) var(--ease-out);
}

@keyframes puzzle-die-roll {
  50% {
    transform: rotate(0.6turn) scale(1.2);
  }

  to {
    transform: rotate(1turn);
  }
}

/* ---- Board ----------------------------------------------------------------
   White at the bottom, as problem diagrams are printed; rank numbers on the
   left edge and file letters under the board. */

.puzzle-board {
  margin: 0;
}

.puzzle-board__frame {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "ranks grid"
    ". files";
  gap: var(--space-3xs);
}

.puzzle-board__ranks,
.puzzle-board__files {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--color-muted);
  font-size: var(--font-size-3xs);
  line-height: var(--line-height-solid);
}

.puzzle-board__ranks {
  grid-area: ranks;
  grid-template-rows: repeat(8, minmax(0, 1fr));
  align-items: center;
}

.puzzle-board__files {
  grid-area: files;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  text-align: center;
}

/* The rows are sized like the columns, and nothing inside may impose a
   minimum: the piece images are 45px drawings, and WebKit otherwise grows a
   smaller square's row to the image's own height, stretching the board into
   tall strips. */
.puzzle-board__grid {
  grid-area: grid;
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  grid-template-rows: repeat(8, minmax(0, 1fr));
  aspect-ratio: 1;
  min-height: 0;
  border: var(--stroke-hairline) solid var(--color-border);
}

/* Positioned, so whatever a square draws over itself (the playable board's
   marks) is held to the square. */
.puzzle-board__square {
  position: relative;
  min-width: 0;
  min-height: 0;
}

.puzzle-board__square--dark {
  background-color: var(--color-brand-soft);
}

.puzzle-board__square--light {
  background-color: var(--color-canvas);
}

.puzzle-board__piece {
  display: block;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: contain;
}

/* ---- Playable board ---------------------------------------------------------
   board/puzzle-board.js adds `is-playable` once the game has started. Every
   mark is drawn over the square's own colour (tokens.css, "The playable
   board"), so a dark square stays dark under it. Only a piece that can move
   takes touches for itself, so a drag moves it rather than the page; a swipe
   that starts anywhere else on the board scrolls the page as it would off it
   (a phone's board fills the screen's width). */

.puzzle-board.is-playable .puzzle-board__grid {
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
}

.puzzle-board.is-playable .puzzle-board__square.is-movable {
  touch-action: none;
}

.puzzle-board.is-playable .puzzle-board__square.is-movable,
.puzzle-board.is-playable .puzzle-board__square.is-target,
.puzzle-board.is-playable .puzzle-board__square.is-capture-target {
  cursor: pointer;
}

.puzzle-board.is-dragging .puzzle-board__grid,
.puzzle-board.is-dragging .puzzle-board__square {
  cursor: grabbing;
}

/* A focused square draws its ring over its neighbours, which come later in
   the grid and would otherwise paint over it. */
.puzzle-board.is-playable .puzzle-board__square:focus-visible {
  z-index: 1;
}

/* The last move is framed rather than tinted: an amber tint over a light
   square is the dark square's own colour, so it read as a misdrawn board. */
.puzzle-board__square.is-last-move::before {
  position: absolute;
  inset: 0;
  content: "";
  pointer-events: none;
  box-shadow: inset 0 0 0 var(--board-last-move-frame) var(--color-board-last-move);
}

.puzzle-board__square.is-selected {
  background-image: linear-gradient(var(--color-board-selected), var(--color-board-selected));
}

.puzzle-board__square.is-check {
  background-image: radial-gradient(circle, var(--color-board-check), transparent 70%);
}

.puzzle-board__square.is-target::after,
.puzzle-board__square.is-capture-target::after {
  position: absolute;
  inset: 0;
  content: "";
  pointer-events: none;
}

.puzzle-board__square.is-target::after {
  background-image: radial-gradient(circle, var(--color-board-target) var(--board-target-dot), transparent calc(var(--board-target-dot) + 1%));
}

.puzzle-board__square.is-capture-target::after {
  background-image: radial-gradient(circle, transparent var(--board-capture-ring), var(--color-board-target) calc(var(--board-capture-ring) + 1%));
}

/* The dragged piece follows the pointer by the offsets the script measures,
   above the squares it passes over. */
.puzzle-board__piece.is-dragged {
  position: relative;
  z-index: 2;
  pointer-events: none;
  transform: translate(var(--drag-x), var(--drag-y));
}

/* ---- Moves on the board ------------------------------------------------------
   One line under the caption, from its left edge so the arrows never shift as
   moves are added: the round step arrows, the visitor's moves as options they
   can jump to, and check or mate after the last. A long game wraps onto
   further lines under it. */

.puzzle-play {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3xs) var(--space-2xs);
}

.puzzle-play__controls {
  display: flex;
}

/* The arrow primitive draws its circle from ::before, placed against the
   button; the hero places its own arrows absolutely instead. */
.puzzle-play__step {
  position: relative;
  cursor: pointer;
}

.puzzle-play__step:disabled {
  cursor: default;
  opacity: var(--disabled-opacity);
}

/* Nothing played yet, or nothing to say, takes no room in the line. */
.puzzle-play__moves:empty,
.puzzle-play__status:empty {
  display: none;
}

.puzzle-play__status {
  margin: 0;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
}

.puzzle-play__moves {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.puzzle-play__move {
  border: 0;
  background-color: transparent;
  font-family: inherit;
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* Moves stepped back over stay in the line, quieter, until a new move
   replaces them. */
.puzzle-play__move.is-ahead:not([aria-current]) {
  color: var(--color-muted);
}

/* ---- First-visit note -------------------------------------------------------
   board/board-coach.js, once per browser: the shared backdrop dims the page,
   header and action bar included; the board stands lit above it on a ring of
   page colour, and the note, in the shared info bubble, floats beside it
   without moving anything. */

.puzzle-coach__backdrop {
  z-index: var(--puzzle-coach-z);
}

.puzzle-board.is-coached {
  position: relative;
  z-index: calc(var(--puzzle-coach-z) + 1);
  background-color: var(--color-canvas);
  box-shadow: 0 0 0 var(--space-xs) var(--color-canvas);
}

.puzzle-coach {
  position: absolute;
  z-index: calc(var(--puzzle-coach-z) + 1);
  inset-inline: 0;
  display: grid;
  justify-items: center;
  gap: var(--space-xs);
  width: fit-content;
  max-inline-size: min(var(--info-note-max-width), 100%);
  margin-inline: auto;
  text-align: center;
}

/* The script picks the first placement that fits on screen, since the page
   is locked while the note shows: under the board (over the caption), above
   it (over the source), or over the board's own lower edge. Each clears the
   board's lit ring. */
.puzzle-coach[data-placement="below"] {
  grid-area: caption;
  top: var(--space-md);
}

.puzzle-coach[data-placement="above"] {
  grid-area: board;
  bottom: calc(100% + var(--space-md));
}

.puzzle-coach[data-placement="over"] {
  grid-area: board;
  bottom: var(--space-md);
}

.puzzle-coach__text {
  margin: 0;
  font-size: var(--font-size-sm);
  text-wrap: balance;
}

/* ---- Promotion chooser ------------------------------------------------------
   The shared dialog surface with no icon: the question, then the four pieces
   in the pawn's colour, then the way back. */

.puzzle-promotion .confirm-dialog__content {
  grid-template-columns: minmax(0, 1fr);
}

.puzzle-promotion .confirm-dialog__title,
.puzzle-promotion .confirm-dialog__actions {
  grid-column: 1;
}

.puzzle-promotion__choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

/* Scoped to the dialog: the outline primitive's own padding in controls.css
   would otherwise win, since the shared sheets load last. */
.puzzle-promotion .puzzle-promotion__choice {
  width: var(--puzzle-promotion-choice-size);
  height: var(--puzzle-promotion-choice-size);
  padding: var(--space-3xs);
}

.puzzle-promotion__choice img {
  width: 100%;
  height: 100%;
}

/* ---- Empty state ---------------------------------------------------------- */

.puzzle-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-sm);
}

/* ---- Book -----------------------------------------------------------------
   The puzzle's source, so the shared book card is laid out as a row: the
   cart and favourites line item's cover size on the left, title, author,
   price and actions on the right. The card's markup, price, controls and
   states remain its own; only its layout changes here. */

.puzzle-book {
  display: grid;
  align-self: start;
  align-content: start;
  gap: var(--space-2xs);
}

.puzzle-book .book-card {
  display: grid;
  grid-template-columns: var(--cart-cover-width) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-sm);
  height: auto;
  padding: var(--space-xs);
  background-image: none;
  background-color: var(--color-surface);
}

.puzzle-book .book-card__cover-frame {
  width: var(--cart-cover-width);
  aspect-ratio: var(--cart-cover-aspect);
  margin: 0;
}

.puzzle-book .book-card__badges {
  top: var(--space-xs);
}

.puzzle-book .book-card__badge {
  min-width: 0;
  min-height: var(--space-lg);
  padding-inline: var(--space-2xs);
  font-size: var(--font-size-3xs);
}

.puzzle-book .book-card__body {
  gap: var(--space-3xs);
  padding: 0;
}

.puzzle-book .book-card__title {
  font-size: var(--font-size-md);
}

.puzzle-book .book-card__title--long,
.puzzle-book .book-card__author {
  font-size: var(--font-size-sm);
}

.puzzle-book .book-card__description {
  display: none;
}

.puzzle-book .book-card__footer {
  flex-wrap: nowrap;
  padding-top: var(--space-2xs);
}

.puzzle-book .price__current {
  font-size: var(--font-size-md);
}

/* The cover stays put on hover: in a row it would lift out of the strip. */
.puzzle-book .book-card:hover .book-card__cover-frame {
  transform: none;
}

/* ---- Below the desktop breakpoint: one column, actions at the foot ------- */

@media (max-width: 61.9375rem) {
  /* The board comes straight after the heading and the filters; what explains
     the game follows the book, where it no longer pushes the puzzle out of
     the first screen. */
  .puzzle-intro {
    order: 1;
  }

  .puzzle-intro {
    margin-top: var(--space-md);
  }

  /* The bar comes last and sticks to the foot of the screen while the page
     above it scrolls, so the die is always one tap away. Its band is
     full-bleed; its row may take the whole width inside the gutters, since
     the pill and the text action need more than the board, which shrinks
     with a short screen, and one row keeps the bar as low as it can be. */
  .puzzle-actions {
    position: sticky;
    bottom: 0;
    z-index: var(--action-bar-z);
    order: 2;
    justify-self: stretch;
    width: auto;
    margin-inline: calc(-1 * var(--gutter));
    container-type: inline-size;
  }

  /* The row is as wide as the pill and the text action, centred in the bar;
     an opened solution wraps within that width, so opening it never moves
     the pill, and scrolls inside the bar instead of pushing the board out of
     sight above it. */
  .puzzle-actions__pair {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
  }

  .puzzle-solution__text {
    max-height: var(--puzzle-solution-max-height);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* A bar too narrow for the pill and "Показати розв'язок" side by side
     stacks them instead: the pill at the board's width, the text action
     centred under it. The row measures at most
     340px in Ukrainian (282px in English) below the desktop breakpoint, in
     Chromium and WebKit alike, and the threshold is that width: a 375px
     phone (343px inside its gutters) keeps one row, a 360px one stacks. The
     width is the bar's own, so it holds however wide the window. Container
     conditions cannot read custom properties, hence the literal. */
  @container (max-width: 21.25rem) {
    .puzzle-actions__pair {
      width: min(100%, var(--puzzle-diagram-width));
      text-align: center;
    }

    .puzzle-die {
      float: none;
      width: 100%;
      margin-inline-end: 0;
    }

    .puzzle-solution {
      display: block;
      margin-top: var(--space-2xs);
    }

    .puzzle-solution__text {
      text-align: start;
    }
  }
}

/* Phones: the board is sized from the width alone, not the screen's height,
   and kept a little short of it so the caption and the book stay in sight
   above the bar at the foot. */
@media (max-width: 36.25rem) {
  .puzzle-stage {
    --puzzle-diagram-width: min(100%, var(--puzzle-board-phone));
  }
}

/* ---- Desktop: the puzzle on the left, the game beside it ------------------
   The board spans every row of the right column: the heading, the
   introduction, the filters, the die and the solution, then the book, whose
   row takes what height is left. All of it reaches the first screen: the book
   is where a puzzle turns into a sale. */

@media (min-width: 62rem) {
  .puzzle-stage:not(.puzzle-stage--empty) {
    grid-template-columns: var(--puzzle-diagram-width) minmax(0, var(--puzzle-side-width));
    grid-template-rows: repeat(4, auto) 1fr;
    grid-template-areas:
      "diagram title"
      "diagram intro"
      "diagram filters"
      "diagram actions"
      "diagram book";
    align-items: start;
    justify-content: center;
    justify-items: stretch;
    gap: var(--space-sm) var(--space-4xl);
  }

  .puzzle-stage:not(.puzzle-stage--empty) > * {
    width: auto;
  }

  .puzzle-stage:not(.puzzle-stage--empty) > .puzzle-stage__title {
    grid-area: title;
  }

  .puzzle-stage:not(.puzzle-stage--empty) > .puzzle-intro {
    grid-area: intro;
  }

  .puzzle-stage:not(.puzzle-stage--empty) > .puzzle-filters {
    grid-area: filters;
  }

  .puzzle-diagram {
    grid-area: diagram;
  }

  .puzzle-actions {
    grid-area: actions;
  }

  .puzzle-book {
    grid-area: book;
    margin-top: var(--space-sm);
  }
}
