/*
  Quiet Solitaire.

  Sized from a --card-w custom property that is set once from the viewport width, so
  the whole board scales as one thing. Seven columns have to fit across the narrowest
  phone in portrait; everything else follows from that.
*/

:root {
  --felt: #1b5e37;
  --felt-dark: #14472a;
  --bar: #10361f;
  --ink: #f4f6f4;
  --slot: rgba(0, 0, 0, 0.22);
  --card-face: #fdfdfb;
  --card-red: #c1272d;
  --card-black: #1f2733;
  --card-blue: #1f5fa9;
  --card-green: #14804a;

  /* The portfolio palette. Felt green is the studio colour across all seven titles —
     icon ground, store frames, chrome — not just this table. See docs/art-and-feel.md.
     These two were literals until that was decided, which is how a palette drifts:
     nobody sets out to have four greens, they arrive one hardcoded hex at a time. */
  --accent: #2f9e5f;
  --surface-raised: #17251c;

  /* Interaction colours, and the only two that are allowed to be non-green: both have
     to read against the felt and against a card face, which a green cannot do. */
  --select: #ffd34d;
  --hint: #7fd4ff;
  --gap: 6px;
  --card-w: 46px;
  --card-h: calc(var(--card-w) * 1.42);
  --fan: calc(var(--card-h) * 0.28);
  --fan-down: calc(var(--card-h) * 0.14);
  --radius: 5px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--felt);
  color: var(--ink);
  font: 15px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

body {
  display: flex;
  flex-direction: column;
  background: radial-gradient(ellipse at 50% 0%, var(--felt) 0%, var(--felt-dark) 100%);
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* ---- chrome ------------------------------------------------------------- */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  background: var(--bar);
  flex: 0 0 auto;
}

.bar-left, .bar-right { display: flex; gap: 6px; }

.bar-stats {
  display: flex;
  gap: 10px;
  font-size: 12px;
  opacity: 0.85;
  white-space: nowrap;
}

.bar-stats b { font-variant-numeric: tabular-nums; }

.btn {
  appearance: none;
  border: 0;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.14);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  padding: 7px 11px;
  cursor: pointer;
  min-width: 38px;
  min-height: 38px;
}

.btn:active { background: rgba(255, 255, 255, 0.26); }
.btn:disabled { opacity: 0.35; cursor: default; }
.btn-primary { background: var(--accent); font-weight: 600; }

/* ---- the table ---------------------------------------------------------- */

.table {
  flex: 1 1 auto;
  padding: var(--gap);
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
}

.row { display: flex; gap: var(--gap); }
.row-top { flex: 0 0 auto; }
.spacer { flex: 1 1 auto; min-width: 0; }

.row-tableau {
  flex: 1 1 auto;
  align-items: flex-start;
  min-height: 0;
}

.column {
  position: relative;
  width: var(--card-w);
  flex: 0 0 auto;
}

.slot, .column-base {
  width: var(--card-w);
  height: var(--card-h);
  border-radius: var(--radius);
  background: var(--slot);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.12);
  position: relative;
  flex: 0 0 auto;
}

.slot-waste { width: calc(var(--card-w) * 1.6); }

/* ---- cards -------------------------------------------------------------- */

.card {
  position: absolute;
  left: 0;
  width: var(--card-w);
  height: var(--card-h);
  border-radius: var(--radius);
  background: var(--card-face);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  color: var(--card-black);
  line-height: 1;
  cursor: pointer;
  overflow: hidden;
  transition: transform 90ms ease-out;
}

/* The corner index. Everything a covered card needs to say fits in here. */
.card .corner {
  position: absolute;
  top: 5%;
  left: 8%;
  display: flex;
  align-items: baseline;
  gap: 2px;
}

.card .corner b {
  font-size: calc(var(--card-w) * 0.40);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.04em;
}

.card .corner i {
  font-size: calc(var(--card-w) * 0.28);
  font-style: normal;
}

.card .pip {
  position: absolute;
  right: 8%;
  bottom: 4%;
  font-size: calc(var(--card-w) * 0.52);
  opacity: 0.9;
}

.card.red { color: var(--card-red); }

/* Four-colour deck: the single highest-value accessibility option in the genre,
   and it costs nothing but two more colours. */
body.fourcolour .card.suit-d { color: var(--card-blue); }
body.fourcolour .card.suit-c { color: var(--card-green); }

.card.down {
  overflow: hidden;
  background:
    repeating-linear-gradient(45deg, #2b4b7d 0 4px, #24406c 4px 8px);
  background-size: cover;
  background-position: center;
  color: transparent;
  cursor: default;
}

.card.down.custom-back {
  background-image: var(--custom-back);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

.card.selected {
  transform: translateY(-8px);
  box-shadow: 0 0 0 2.5px var(--select), 0 3px 8px rgba(0, 0, 0, 0.5);
}

.card.hinted {
  animation: pulse 620ms ease-in-out 3;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); }
  50% { box-shadow: 0 0 0 3px var(--hint), 0 3px 10px rgba(0, 0, 0, 0.5); }
}

.slot.droppable, .column.droppable > .column-base {
  box-shadow: inset 0 0 0 2.5px var(--select);
}

.stock-count {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  opacity: 0.6;
  pointer-events: none;
}

/* ---- banner ------------------------------------------------------------- */

.banner {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.82);
  padding: 10px 16px;
  border-radius: 999px;
  font-size: 14px;
  z-index: 40;
  max-width: 88vw;
  text-align: center;
}

/* ---- boot --------------------------------------------------------------- */

.boot {
  position: fixed;
  inset: 0;
  background: var(--felt-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.boot[hidden] { display: none; }
.boot-card { text-align: center; opacity: 0.8; }

.boot-spinner {
  width: 34px;
  height: 34px;
  margin: 0 auto 12px;
  border: 3px solid rgba(255, 255, 255, 0.2);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: spin 800ms linear infinite;
}

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

/* ---- settings ----------------------------------------------------------- */

.sheet {
  border: 0;
  border-radius: 14px;
  padding: 0;
  max-width: 380px;
  width: calc(100vw - 32px);
  background: var(--surface-raised);
  color: var(--ink);

  /*
    The sheet is taller than a small phone held sideways, and it was before the About
    row was added — measured on a 568x320 viewport, the Done button sat 131px below
    the fold with no way to reach it. The dialog was already clamping its own height;
    what it was missing is somewhere for the overflow to go.
  */
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sheet::backdrop { background: rgba(0, 0, 0, 0.55); }
.sheet form { padding: 18px; }
.sheet h2 { margin: 0 0 14px; font-size: 18px; }
.sheet h3 { margin: 18px 0 8px; font-size: 14px; opacity: 0.7; }

.field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
}

.field-check { justify-content: flex-start; }
.field-check input { width: 20px; height: 20px; flex: 0 0 auto; }
.field-stack { display: block; }

.field select {
  background: rgba(255, 255, 255, 0.12);
  color: var(--ink);
  border: 0;
  border-radius: 7px;
  padding: 7px 9px;
  font: inherit;
}

.backs { display: flex; gap: 8px; margin: 10px 0; flex-wrap: wrap; }

.back-swatch {
  width: 40px;
  height: 56px;
  border-radius: 5px;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  background-size: cover;
  background-position: center;
}

.back-swatch[aria-pressed="true"] { border-color: var(--select); }

.upload {
  display: inline-block;
  background: rgba(255, 255, 255, 0.12);
  padding: 8px 12px;
  border-radius: 7px;
  cursor: pointer;
  font-size: 14px;
}

.upload input { display: none; }

.note {
  font-size: 12px;
  opacity: 0.62;
  margin: 8px 0 0;
  line-height: 1.45;
}

.record {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 5px 12px;
  margin: 0;
  font-size: 14px;
}

.about {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 8px;
  margin-top: 2px;
  opacity: 1;
}

/*
  Legible, not decorative: this is the link a reviewer is told to go and find, and
  the one a player goes looking for. The vertical padding is the point — the text
  is 12px, and a 12px line is not a tap target. 12px of padding either side puts it
  over 40 CSS px, and the negative margin keeps that from opening a gap in the sheet.
*/
.about a {
  color: var(--ink);
  opacity: 0.8;
  padding: 14px 0;
  margin: -14px 0;
}

.about a:hover, .about a:focus-visible { opacity: 1; }
.about span { opacity: 0.4; }

.record dt { opacity: 0.7; }
.record dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

.sheet menu {
  display: flex;
  justify-content: flex-end;
  padding: 16px 0 0;
  margin: 0;
}

/* ---- the collection ------------------------------------------------------ */

/*
  One app, two boards. Both exist in the document at once and visibility is driven by
  a single attribute on <body>, rather than by building and tearing down a board on
  every switch — switching games is something a player does often enough that it
  should be instant, and a rebuilt board loses its scroll position and its selection.

  The cost is that a hidden board measures zero, which is why each view re-measures
  when it becomes visible rather than only on resize.
*/
body[data-game='klondike'] #fc-table,
body[data-game='klondike'] #fc-banner,
body[data-game='klondike'] [data-for='freecell'],
body[data-game='freecell'] #table,
body[data-game='freecell'] #banner,
body[data-game='freecell'] [data-for='klondike'] {
  display: none;
}

.picker {
  display: inline-flex;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  overflow: hidden;
}

.pick {
  appearance: none;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  padding: 0.3rem 0.75rem;
  cursor: pointer;

  /* Comfortably past the 44px tap target on a phone once the bar's own padding is
     counted; a segmented control that is hard to hit is worse than two plain buttons. */
  min-height: 32px;
}

.pick.is-active {
  background: rgba(255, 255, 255, 0.9);
  color: #14351f;
  font-weight: 600;
}

.group {
  display: inline-flex;
  gap: 0.35rem;
}

/* FreeCell's top row: four cells on the left, four foundations on the right, with the
   gap between them carrying the meaning — one side is storage, the other is the goal. */
.cells {
  display: flex;
  gap: 5px;
}

/* FreeCell sizes from its own width, because eight columns do not fit at seven
   columns' width. Everything the two boards share is re-pointed here rather than
   given a second set of class names. */
#fc-table .slot,
#fc-table .column,
#fc-table .column-base,
#fc-table .card {
  width: var(--fc-card-w, 44px);
}

#fc-table .slot,
#fc-table .column-base,
#fc-table .card {
  height: var(--fc-card-h, 62px);
}

#fc-tableau {
  gap: 5px;
}

/*
  FreeCell's type sizes from FreeCell's card width.

  Everything under .card sized itself from --card-w, which is Klondike's measurement.
  Eight columns are narrower than seven, so every rank and pip on this board was drawn
  for a card about 12% wider than the one it sat on — the glyphs overflowed the sliver
  a fanned card actually shows, and a queen read as an O.
*/
#fc-table .card .corner b { font-size: calc(var(--fc-card-w, 44px) * 0.40); }
#fc-table .card .corner i { font-size: calc(var(--fc-card-w, 44px) * 0.28); }
#fc-table .card .pip { font-size: calc(var(--fc-card-w, 44px) * 0.52); }

/* A card that cannot start a run is dimmed rather than hidden. The player still needs
   to read it — knowing what is buried is most of the game — but it should not look
   like something a tap will pick up.

   Kept deliberately slight. The first attempt used 0.82 and, because most cards in a
   FreeCell column are buried most of the time, it turned the whole board grey: the
   signal was accurate and the board looked broken. This is enough to notice when
   comparing two cards and not enough to read as damage. */
#fc-table .card[data-playable='no'] {
  filter: brightness(0.94);
}

/*
  Empty foundations carry their suit, faintly.

  Without it the cells and the foundations are eight identical boxes in a row, and
  nothing on screen says which four are storage and which four are the goal. A player
  who knows FreeCell can infer it from position; a player meeting the game here cannot,
  and the first thing they do is drop a card in the wrong place.

  It is on the slot rather than in the markup because it is decoration — a screen
  reader announcing "spade" for an empty pile would be describing the furniture.
*/
#fc-foundations .slot::after {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: calc(var(--fc-card-w, 44px) * 0.5);
  color: rgba(255, 255, 255, 0.16);
  pointer-events: none;
}

#fc-foundations .slot:nth-child(1)::after { content: '♣'; }
#fc-foundations .slot:nth-child(2)::after { content: '♦'; }
#fc-foundations .slot:nth-child(3)::after { content: '♥'; }
#fc-foundations .slot:nth-child(4)::after { content: '♠'; }

/* And the cells read as somewhere to put things down rather than somewhere to build:
   a lighter, hollower box than the foundation beside it. */
#fc-cells .slot {
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.05);
  background: rgba(0, 0, 0, 0.12);
}

/*
  The bar has to be allowed to wrap.

  It holds a game picker, three stats, and five buttons, and that does not fit on one
  line until about 600px — so on every phone in portrait, something was running off
  the right-hand edge. It was invisible for two reasons: the page never scrolled
  horizontally, because the overflow was inside a flex row rather than the document,
  and the thing that fell off the end was "Daily", which nobody misses until they go
  looking for it.

  Wrapping rather than a breakpoint, deliberately. The first attempt guessed 419px and
  was worse than useless — it fixed 360 while 430 still clipped by 95px, which is the
  trouble with picking a number instead of asking the layout. Flexbox already knows
  when the row is full.

  The stats are what wrap, because moves and score are the least urgent thing up there
  and a bar one line taller is a much better answer than a control that cannot be
  reached.
*/
.bar { flex-wrap: wrap; }

.bar-stats { flex: 0 1 auto; justify-content: center; }

@media (max-width: 419px) {
  .bar {
    padding: 8px 6px;
    gap: 6px;
  }

  /* order matters as much as the basis. The stats sit between the picker and the
     buttons in the markup, so a full-width row there splits the bar into three
     instead of two — the picker above it and the buttons below. */
  .bar-stats {
    order: 3;
    flex: 1 1 100%;
  }

  /*
    Sized from a measurement, not a guess. At 360px the picker and the buttons came to
    349px of content in 348px of space — one pixel over, which cost a whole third row
    of bar and a chunk of board with it. These few pixels of padding buy that row back
    at 360 and up; at 320 it still wraps to three rows, which is the right trade for a
    screen that narrow.
  */
  .btn {
    font-size: 13px;
    padding: 7px 6px;
    min-width: 32px;
  }

  .pick {
    font-size: 0.78rem;
    padding: 0.3rem 0.45rem;
  }
}


/*
  Centre the board, and stop it sprawling on a tablet.

  Each row is capped to exactly the width its own columns occupy, so the top row's
  spacer pushes the foundations to the end of the tableau rather than to the edge of
  the screen. On a phone the cap equals the available width and nothing changes; on a
  13-inch iPad it is the difference between a board and a scattering.
*/
#table .row { max-width: var(--board-w, 100%); margin-inline: auto; width: 100%; }

#fc-table .row { max-width: var(--fc-board-w, 100%); margin-inline: auto; width: 100%; }
