/* five-in-a-row/style.css
 *
 * Loaded AFTER the site stylesheet (/style.css), and deliberately
 * subordinate to it. Three rules hold everywhere in this file:
 *
 *   1. It never redefines a selector the site stylesheet defines. Not
 *      .btn, not .modal-overlay/.modal-box/.modal-actions, not
 *      .settings-panel/.settings-row/.segmented/.toggle-row, not
 *      .achievement-toast, not .site-header/.site-footer. Those
 *      components are used as-is; where this page needed to differ, the
 *      MARKUP was changed to match the site's conventions rather than the
 *      CSS overridden. That is why index.html opens modals with
 *      data-open="true" and not a `hidden` attribute.
 *   2. No bare element selectors at all — no *, html, body, button,
 *      input, textarea. The extracted stylesheet this replaces had them,
 *      and they leaked onto the site's own components once both files
 *      loaded together.
 *   3. Every selector below is either a .fir- prefixed class or scoped
 *      under #five-in-a-row. The one deliberate exception is documented
 *      where it appears.
 *
 * Colours come from the site's :root custom properties. This file
 * declares no palette of its own; the previous version carried 19
 * hardcoded hex values, including an orange accent (#e4572e) that
 * measured 3.22:1 on the page background and failed the 4.5:1 text
 * threshold outright.
 *
 * Sizing targets an over-65 reader on a phone: 56px minimum control
 * height (the site's .btn floor is 48px, and this page raises its own
 * controls above that), 16px minimum gap between adjacent targets, and
 * 500-600 weight on anything that has to be read at a glance.
 */

/* ============================ page shell ============================ */

#five-in-a-row {
  max-width: 640px;
  margin: 0 auto;
  padding: 8px 8px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Bottom inset so the last control clears a phone's home indicator when
   * the page is scrolled to the end. */
  padding-bottom: calc(24px + env(safe-area-inset-bottom));
}

/* The permanent one-line statement of how you win. Sits directly above
 * the board and never goes away: a tutorial modal is friction, is
 * dismissed once and then forgotten, and this audience is the least
 * likely to go looking for the rules a second time. */
.fir-goal {
  /* Tightened from `4px 0 12px` to claw back the vertical the status
   * line's three-line reserve costs. At 430x844 the last control ended
   * 0.9px past the fold without this. */
  margin: 2px 0 8px;
  font-size: 18px;
  font-weight: 500;
  line-height: 1.45;
  text-align: center;
  color: var(--ink);
}

.fir-goal strong {
  font-weight: 700;
}

/* Whose turn it is / the danger warning / a hint explanation.
 *
 * The reserved height is the whole point of this rule, and 3.2em was not
 * enough. Measured: "Tap the board to choose a spot." renders on one line
 * (57.6px at 390px) while "Tap the same spot again, or press Place stone."
 * takes two (64.6px) — so the line grew by 7px the instant a stone was
 * staged, and every control below it moved down by the same 7px. That is
 * small, but it is movement under a finger that is already on the screen,
 * which is exactly what this element exists to prevent.
 *
 * Now reserves THREE lines (3 x 1.35 line-height, plus the 16px of
 * vertical padding that border-box folds in). Two lines is the longest
 * any current message needs at 390px and above; the third is headroom so
 * a narrower phone, or a slightly longer sentence later, cannot
 * reintroduce the shift. The cost is ~24px of vertical space and the
 * controls still sit well inside the viewport with ads enabled. */
.fir-status {
  margin: 0 0 8px;
  min-height: calc(4.05em + 16px);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 19px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  padding: 8px 12px;
  border-radius: var(--radius-md);
  background: var(--paper-dim);
  width: 100%;
  max-width: 560px;
}

/* The danger state is NOT signalled by colour alone: the text itself says
 * what is happening, the board draws a distinct double-ring-with-
 * exclamation shape, and this adds a left bar plus a heavier weight. Any
 * one of the three is enough on its own. */
.fir-status-danger {
  background: #f6e4e2;
  color: #7a1811;
  border-left: 6px solid var(--danger);
  font-weight: 700;
}

.fir-status-hint {
  background: #e4ece7;
  color: var(--felt-dark);
  border-left: 6px solid var(--felt);
}

/* ============================== board =============================== */

.fir-board-wrap {
  position: relative;
  width: 100%;
  max-width: 560px;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  /* 캔버스가 이 안을 꽉 채우지만, 모서리 반올림 때문에 래퍼가 직접
   * 맞는 픽셀이 남는다. 캔버스와 같은 규칙을 줘 그 자리만 예외가 되지
   * 않게 한다. */
  touch-action: manipulation;
}

#fir-board {
  display: block;
  width: 100%;
  height: 100%;
  /* `manipulation`, not `none`.
   *
   * This was `none`, to stop a touch-drag from scrolling the page instead
   * of reaching the board. It did that, and it also switched off
   * pinch-zoom for anything starting on the canvas — which on a phone is
   * most of the screen. For a player who needs to magnify the board to
   * see it, that is the one gesture they cannot afford to lose, and the
   * board was the one place it did not work.
   *
   * `manipulation` keeps pinch and pan and drops only double-tap zoom,
   * which is what actually broke placing a stone: confirming means
   * tapping the same spot twice, and Safari reads that as "zoom here".
   *
   * The drag that `none` was protecting is not a real loss. Touch input
   * here is taps only — updateHoverPreview() ignores anything that isn't
   * a mouse — so a drag on the board was never doing anything except
   * withholding the scroll. And when the browser now claims a drag as a
   * scroll it fires pointercancel, which core/input.js already handles by
   * dropping the active pointer, so the finger lifting somewhere else
   * cannot land a stone. */
  touch-action: manipulation;
  /* A long press on a canvas raises the "Save Image" callout on mobile,
   * directly on top of the board. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* The "you have the dark stones" note shown for a beat before play. */
.fir-coinflip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(15, 36, 25, 0.72);
}

.fir-coinflip[hidden] {
  display: none;
}

.fir-coinflip-text {
  margin: 0;
  text-align: center;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.4;
  color: #fdfbf4;
  max-width: 18em;
}

/* ====================== confirm-to-place bar ======================== */
/* One button row, always in the layout. It is never hidden, because
 * appearing and disappearing is what used to shove the four controls
 * below it 148-160px down and off a 390x844 phone entirely. Reserving
 * the row costs one button's height and buys immovable controls. The
 * instruction text lives in .fir-status above, which already reserves
 * its height. */

.fir-confirm {
  width: 100%;
  max-width: 560px;
  margin-top: 12px;
}

.fir-confirm-actions {
  display: flex;
  gap: 16px;
}

/* Scoped to this container rather than restyling .btn itself — the site's
 * button component keeps every one of its own visual rules, and this only
 * states how the buttons share the row. */
.fir-confirm-actions .btn {
  flex: 1;
  min-height: 56px;
  font-size: 18px;
}

/* The disabled state of "Place stone", scoped so the site's own
 * .btn:disabled is untouched everywhere else.
 *
 * The site rule is `opacity: 0.5`, which on this palette computes to
 * 2.70:1 against the page — measured, not estimated. That is below the
 * 3:1 floor for a UI element and, for the reader this page is built for,
 * effectively invisible. Since this particular button is disabled for a
 * large share of the time a player is looking at the page, it has to stay
 * readable while still being unmistakably not-yet-usable.
 *
 * So opacity goes back to 1 and "disabled" is carried by two channels
 * that are not contrast:
 *   - a DASHED border instead of a solid one (shape, survives any colour
 *     vision difference)
 *   - a dimmer surface, --paper-dim instead of --btn-bg
 * and a third channel outside CSS entirely: .fir-status says "Tap the
 * board to choose a spot", which is the reason it is disabled.
 *
 * Measured on this pairing: label and border both 8.34:1 against the
 * button's own surface, versus 9.92:1 when enabled — both far above the
 * 4.5:1 text floor, so the state reads as "waiting", never as "faded". */
.fir-confirm-actions .btn:disabled {
  opacity: 1;
  background: var(--paper-dim);
  border-style: dashed;
  cursor: default;
}

.fir-confirm-actions .btn:disabled:hover {
  background: var(--paper-dim);
}

/* `hidden` does not work on a .btn without this.
 *
 * The UA stylesheet's `[hidden] { display: none }` is a plain type-less
 * rule, so ANY class rule that sets `display` outranks it — and the
 * site's .btn sets `display: inline-flex`. The Cancel button therefore
 * stayed visible with the attribute applied, which the layout test
 * caught. Scoped to this row so the site's own .btn is untouched. */
.fir-confirm-actions .btn[hidden] {
  display: none;
}

/* ============================= controls ============================= */
/* Always on screen, never behind a menu, always with a text label. */

.fir-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px; /* the required minimum separation between adjacent targets */
  width: 100%;
  max-width: 560px;
  margin-top: 16px;
}

.fir-controls .btn {
  flex: 1 1 calc(50% - 16px);
  min-height: 56px; /* above the site's own 48px floor, for this audience */
  min-width: 0;
  font-size: 18px;
  font-weight: 600;
}

@media (min-width: 560px) {
  .fir-controls .btn {
    flex: 1 1 0;
  }
}

/* Same treatment, and the same reason, as the disabled Place stone
 * button above. This is not a second special case — it is the identical
 * defect on the identical component: the site's `.btn:disabled` opacity
 * of 0.5 computes to 2.70:1 here, under the 3:1 floor and effectively
 * invisible for this reader.
 *
 * It matters more here than it looks, because Undo is disabled at the
 * start of every single round (nothing to take back yet) and Hint is
 * disabled for the whole of the computer's turn. Those are the first
 * things a new player sees, and a control they cannot read is a control
 * they will not learn exists. Full contrast, dashed border, dimmer
 * surface — a button that is plainly present and plainly not ready. */
.fir-controls .btn:disabled {
  opacity: 1;
  background: var(--paper-dim);
  border-style: dashed;
  cursor: default;
}

.fir-controls .btn:disabled:hover {
  background: var(--paper-dim);
}

/* =========================== settings rows ========================== */
/* .settings-row / .segmented / .toggle-row all come from the site
 * stylesheet untouched. These are the two things this page adds: an
 * explanatory note under a row, and the badge list. */

.fir-row-note {
  margin: 8px 0 0;
  font-size: 15px;
  line-height: 1.45;
  color: var(--felt-dark);
}

/* A note that follows a .toggle-row rather than living inside a
 * fieldset — it has to pull up against the row it explains. */
.fir-row-note-standalone {
  margin: -6px 0 14px;
  padding: 0 2px;
}

.fir-badges {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 10px;
}

.fir-badge {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "state title"
    "state desc";
  gap: 2px 12px;
  align-items: center;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--paper-dim);
  border: 1.5px solid var(--tile-edge);
}

/* Earned state is a WORD first. The colour and border below only
 * reinforce what the text already says. */
.fir-badge-state {
  grid-area: state;
  align-self: center;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--tile-locked-face);
  color: var(--ink);
  white-space: nowrap;
}

.fir-badge-earned {
  border-color: var(--felt-dark);
  background: var(--tile-face);
}

.fir-badge-earned .fir-badge-state {
  background: var(--felt-dark);
  color: #fdfbf4;
}

.fir-badge-title {
  grid-area: title;
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
}

.fir-badge-desc {
  grid-area: desc;
  font-size: 15px;
  line-height: 1.4;
  color: var(--felt-dark);
}

/* ============================== toasts ============================== */
/* The toast component itself (.achievement-toast) is the site's; this is
 * only the container it is appended to. Stacking several at once is this
 * page's own need — the site only ever shows one. */

#fir-toasts {
  position: fixed;
  /* Clears the site header rather than sitting on top of it. The site's
   * own toast uses top:14px, which is correct on a page whose header
   * scrolls away; this page's header is the first thing a returning
   * player looks at, and the restore toast fires on load, so at 14px the
   * page title was covered at the exact moment it was being read.
   * 88px clears the header at every width measured (40px on phones,
   * 77px on desktop) and still lands well above the board, so a toast
   * never covers the board or the controls — only the goal line, which
   * is permanent and still there once the toast fades. */
  top: max(88px, calc(env(safe-area-inset-top) + 74px));
  left: 0;
  right: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

/* The one deliberate non-.fir- selector in this file.
 *
 * The site's .achievement-toast is `position: fixed` and centres itself
 * with `left: 50%; transform: translate(-50%, …)`. Inside a flex column
 * that already centres its children, that transform pushes each toast a
 * half-width off to the left, and `position: fixed` takes them out of the
 * column so they stack on top of each other instead of below.
 *
 * This is scoped under #fir-toasts, so it changes the component only
 * where this page's container puts it and cannot affect the site's own
 * usage anywhere else. It sets position/left/transform only — every
 * visual property (colour, padding, radius, weight, shadow) still comes
 * from the site's own rule. */
#fir-toasts .achievement-toast {
  position: static;
  transform: none;
}

/* ========================= reduced motion ========================== */
/* main.js already skips the canvas animations when the OS asks for
 * reduced motion; this covers the CSS side. Transitions are 200-300ms
 * elsewhere in this file and in the site stylesheet, and nothing here
 * uses a bounce/overshoot easing. */

@media (prefers-reduced-motion: reduce) {
  #five-in-a-row *,
  #fir-toasts * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================ small screens ========================= */

@media (max-width: 400px) {
  #five-in-a-row {
    padding-left: 8px;
    padding-right: 8px;
  }

  .fir-goal {
    font-size: 17px;
  }

  .fir-status {
    font-size: 18px;
  }

  /* Two per row rather than four across, so no label has to shrink. */
  .fir-controls .btn {
    flex: 1 1 calc(50% - 16px);
  }
}
