/* Battleship. Phone first: the sea being fired at fills the width, and the
   other sea sits small beside the controls below it. Each sea is a
   container, so squares, pegs and labels scale with it (cqw). */
.bs {
  --bs-water: #3f7fa6;
  --bs-water-dark: #2c5f80;
  --bs-hull: #8a949c;
  --bs-hull-edge: #5d666d;
  --bs-sunk: #3a3532;
  --bs-peg-hit: #e0412f;
  --bs-peg-miss: #f4f1ea;
  width: min(46rem, 100%);
  display: flex; flex-direction: column; gap: 0.35rem;
}
.bs .status { margin: 0; }

/* What the last volley did, first on the page: one big word and a line
   about who fired where. Colour says it too (red hit, blue miss, dark
   sunk), but the word carries it. It stays until the next volley. */
.bs-result {
  display: flex; align-items: center; gap: 0.8rem; padding: 0.4rem 0.8rem;
  color: #fff; border-radius: var(--radius);
  background: var(--bs-water-dark);
}
.bs-result.hit { background: #b8321f; }
.bs-result.sunk { background: var(--bs-sunk); box-shadow: inset 0 0 0 3px #e0412f; }
.bs-result-word {
  flex: none; font-size: 2.1rem; line-height: 1.1; font-weight: 900; letter-spacing: 0.02em;
  text-shadow: 0 2px 0 rgb(0 0 0 / 0.25);
}
.bs-result.sunk .bs-result-word { color: #ffd479; }
.bs-result-detail { min-width: 0; font-size: 0.85rem; line-height: 1.25; }

.bs-seas { display: grid; gap: 0.5rem; }
/* On a phone the small sea takes a little under half the width, so it
   and the panel beside it fit under the big sea on one screen. */
.bs-side { display: grid; grid-template-columns: 9fr 11fr; gap: 0.6rem; align-items: start; }
.bs-waiting { max-width: 16rem; }
@media (min-width: 48rem) {
  .bs-seas { grid-template-columns: 3fr 2fr; grid-template-areas: "big side" "dock side"; align-items: start; }
  .bs-seas > .bs-big { grid-area: big; }
  .bs-seas > .bs-dock { grid-area: dock; }
  .bs-seas > .bs-side { grid-area: side; }
  .bs-side { grid-template-columns: 1fr; }
}

.bs-sea { container-type: inline-size; min-width: 0; }
.bs-title { margin: 0 0 0.2rem; font-size: 0.95rem; font-weight: 700; }
/* In play the status line names the big sea ("Tap Bob's sea to aim"), so
   its own title only costs a line of a phone's height (the same goes for
   "Your sea" while placing). Screen readers and
   `see` still have it, from the grid's label. */
.bs-playing .bs-big .bs-title, .bs-placing .bs-big .bs-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
/* Placing is slower than firing and a ship is long, so the sea can give up a
   tenth of its width (a tenth of its height too) to keep the ship list and
   the buttons on a phone's screen under a status line that may take two lines. */
.bs-placing .bs-big { width: 90%; margin-inline: auto; }
.bs-small .bs-title { font-size: 0.85rem; color: var(--ink-soft); }

.bs-grid {
  display: grid; grid-template-columns: 0.6fr repeat(10, 1fr);
  user-select: none; -webkit-user-select: none;
}
.bs-label {
  display: flex; align-items: center; justify-content: center;
  font-size: 3.2cqw; font-weight: 700; color: var(--ink-soft); line-height: 1.4;
}

.bs-sq {
  position: relative; aspect-ratio: 1; min-width: 0; box-sizing: border-box;
  margin: 0; padding: 0; border: 0; font: inherit; color: inherit;
  background: var(--bs-water);
  box-shadow: inset 0 0 0 0.5px var(--bs-water-dark);
  touch-action: manipulation;
}
button.bs-sq { cursor: pointer; }
button.bs-sq:not(.aim):hover { background: #4f90b8; }
button.bs-sq:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; z-index: 1; }

/* A ship: a grey hull across its squares, rounded at bow and stern. */
.bs-sq.ship::before {
  content: ""; position: absolute; background: var(--bs-hull);
  border: 0.5cqw solid var(--bs-hull-edge);
}
.bs-sq.ship.h::before { inset: 14% 0; border-left: 0; border-right: 0; }
.bs-sq.ship.v::before { inset: 0 14%; border-top: 0; border-bottom: 0; }
.bs-sq.ship.h.bow::before { left: 10%; border-left: 0.5cqw solid var(--bs-hull-edge); border-radius: 50% 0 0 50%; }
.bs-sq.ship.h.stern::before { right: 10%; border-right: 0.5cqw solid var(--bs-hull-edge); border-radius: 0 50% 50% 0; }
.bs-sq.ship.h.bow.stern::before { border-radius: 50%; }
.bs-sq.ship.v.bow::before { top: 10%; border-top: 0.5cqw solid var(--bs-hull-edge); border-radius: 50% 50% 0 0; }
.bs-sq.ship.v.stern::before { bottom: 10%; border-bottom: 0.5cqw solid var(--bs-hull-edge); border-radius: 0 0 50% 50%; }
/* A sunk ship is dark and sits on darker water, so it can't be taken for
   hits on something still afloat. */
.bs-sq.ship.sunk { background: var(--bs-water-dark); }
.bs-sq.ship.sunk::before { background: var(--bs-sunk); border-color: #1d1a18; }
/* The ship picked up while placing. */
.bs-sq.ship.picked::before { background: #c9a227; border-color: #8a6d12; }

/* Pegs. A miss is a small white dot. A hit is much bigger: a red disc with
   a white ring and a cross, so the two can't be mistaken for each other at
   a glance (the family read red dots as misses). A hit on a ship still out
   there also turns its water dark red; a hit on a sunk ship goes small and
   dark on the hull. */
.bs-sq.hit::after, .bs-sq.miss::after {
  content: ""; position: absolute; border-radius: 50%;
  box-shadow: 0 0.3cqw 0.6cqw rgb(0 0 0 / 0.4);
}
.bs-sq.miss::after { background: var(--bs-peg-miss); inset: 38%; opacity: 0.85; }
.bs-sq.hit::after {
  inset: 10%; border: 0.7cqw solid #fff; background-color: var(--bs-peg-hit);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cpath d='M2.6 2.6l4.8 4.8M7.4 2.6l-4.8 4.8' stroke='white' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  background-position: center; background-repeat: no-repeat; background-size: 100% 100%;
}
.bs-sq.ship.sunk.hit::after { background: #9b3a2f; border: 0; box-shadow: none; inset: 38%; }
.bs-sq.open { background: #5c2a26; }

/* The last turn's shots on this sea: a light ring. */
.bs-sq.last { box-shadow: inset 0 0 0 0.5px var(--bs-water-dark), inset 0 0 0 0.8cqw #f5d76e; }

/* The crosshair: a ring and a cross in the accent colour. */
.bs-sq.aim {
  background:
    linear-gradient(var(--accent), var(--accent)) center / 0.8cqw 100% no-repeat,
    linear-gradient(var(--accent), var(--accent)) center / 100% 0.8cqw no-repeat,
    var(--bs-water);
  box-shadow: inset 0 0 0 1cqw var(--accent);
}
.bs-sq.aim::after { content: ""; position: absolute; inset: 22%; border-radius: 50%; border: 1cqw solid var(--accent); }

/* A new volley drops its pegs and pops the banner once; see `PlayOnce`,
   which marks `.bs` and says how long ago the volley was first seen.

   A volley this page saw arrive (`gn-news`, not one it opened on) also
   lights up, like the toy: everything waits --bs-lag for the launch sound
   (`volley`), then a hit flashes its square red once, and a sinking flashes
   the ship and the banner's edge for two seconds, twice a second (nothing
   here flashes more than three times a second), before the ship goes down
   to the dark hull it stays as. None of it with reduced motion, where the
   banner and the drawing say it all. */
.bs { --bs-lag: 0s; }
.bs.gn-news { --bs-lag: 0.4s; }
@media (prefers-reduced-motion: no-preference) {
  .bs.gn-fresh .bs-sq.landed::after { animation: bs-drop 0.5s ease-out calc(var(--bs-lag) - var(--played, 0s)) both; }
  .bs.gn-fresh .bs-result { animation: bs-pop 0.7s cubic-bezier(.3, 1.5, .5, 1) calc(var(--bs-lag) - var(--played, 0s)) both; }
  .bs.gn-fresh .bs-result.hit .bs-result-word, .bs.gn-fresh .bs-result.sunk .bs-result-word {
    animation: bs-shake 0.5s ease-in-out calc(var(--bs-lag) + 0.1s - var(--played, 0s)) both;
  }
  .bs.gn-news .bs-sq.landed.hit { animation: bs-boom 0.4s steps(1) calc(var(--bs-lag) - var(--played, 0s)); }
  .bs.gn-news .bs-sq.sinking::before {
    animation:
      bs-go-down 1s ease-in calc(2.4s - var(--played, 0s)) both,
      bs-flash 0.5s steps(1) calc(var(--bs-lag) - var(--played, 0s)) 4;
  }
  .bs.gn-news .bs-result.sunk {
    animation:
      bs-pop 0.7s cubic-bezier(.3, 1.5, .5, 1) calc(var(--bs-lag) - var(--played, 0s)) both,
      bs-lights 0.5s steps(1) calc(var(--bs-lag) - var(--played, 0s)) 4;
  }
}
@keyframes bs-boom {
  0% { background: #ffb02e; }
  50% { background: #e0412f; }
}
@keyframes bs-flash {
  0% { background: #ff5a2e; border-color: #ffd479; }
  50% { background: var(--bs-hull); border-color: var(--bs-hull-edge); }
}
@keyframes bs-go-down {
  0% { background: var(--bs-hull); border-color: var(--bs-hull-edge); transform: none; opacity: 1; }
  60% { transform: translateY(22%) scale(0.85); opacity: 0.45; }
  100% { transform: none; opacity: 1; }
}
@keyframes bs-lights {
  0% { box-shadow: inset 0 0 0 5px #ffd479; }
  50% { box-shadow: inset 0 0 0 3px #e0412f; }
}
@keyframes bs-pop {
  0% { transform: scale(0.6); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes bs-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px) rotate(-3deg); }
  40% { transform: translateX(6px) rotate(3deg); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
@keyframes bs-drop {
  0% { transform: scale(2.4); opacity: 0; }
  70% { transform: scale(0.9); opacity: 1; }
  100% { transform: scale(1); }
}

/* The aim and Fire sit right under the sea being fired at, and stay on
   screen above the Rules/Chat bar when the table's header pushes them below
   it, so a phone never has to scroll down to fire (and back up to read the
   banner). Like Shuffle and Ready, they clear whichever
   sheet is open. Past the chat's handle on a wide screen the bar is gone
   and they simply sit under the seas. */
.bs-dock, .bs-place-actions {
  position: sticky; bottom: calc(var(--gn-bar, 3.25rem) + 0.35rem); z-index: 5; padding: 0.3rem 0; background: var(--paper);
}
body.gn-chat-open .bs-dock, body.gn-chat-open .bs-place-actions { bottom: calc(var(--gn-sheet) + 0.3rem); }
body.gn-rulebook-open .bs-dock, body.gn-rulebook-open .bs-place-actions { bottom: calc(var(--gn-rulebook-sheet) + 0.3rem); }
@media (min-width: 60rem) {
  .bs-dock, .bs-place-actions { bottom: 0.5rem; }
  body.gn-chat-open .bs-dock, body.gn-chat-open .bs-place-actions,
  body.gn-rulebook-open .bs-dock, body.gn-rulebook-open .bs-place-actions { bottom: 0.5rem; }
}
.bs-dock { max-width: 28rem; }
.bs-aim {
  margin: 0; min-height: 3rem; display: flex; align-items: center; justify-content: center; gap: 0.3rem;
  font-size: 1.15rem; color: var(--ink-soft);
  border: 2px dashed var(--line); border-radius: var(--radius);
}
.bs-aim strong { font-family: var(--mono); color: var(--ink); }
.bs-fire-button { width: 100%; min-height: 3rem; font-size: 1.25rem; font-weight: 800; letter-spacing: 0.04em; }

/* Beside the small sea: both fleets. */
.bs-panel { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; }
.bs-tally { display: flex; flex-direction: column; gap: 0.15rem; font-size: 0.8rem; color: var(--ink-soft); }
.bs-tally-ships { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.bs-mini { display: inline-flex; gap: 1px; }
.bs-mini span { width: 0.42rem; height: 0.55rem; background: var(--bs-hull); border-radius: 2px; }
.bs-mini.sunk { position: relative; }
.bs-mini.sunk span { background: var(--line); }
.bs-mini.sunk::after {
  content: ""; position: absolute; left: -2px; right: -2px; top: 50%; height: 2px;
  background: var(--bad); transform: translateY(-50%);
}

/* Placement: the five ships to pick from in one row, then Shuffle and
   Ready, so the whole of it fits under the sea on a phone. A ship not yet
   on the sea has a dashed edge; one that is gets a tick by its hull. */
.bs-picker { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.2rem; }
.bs-pick {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.25rem;
  min-height: 3rem; min-width: 0; padding: 0.3rem 0; font: inherit; font-size: 0.7rem; letter-spacing: -0.01em; cursor: pointer;
  background: var(--card); color: var(--ink); border: 2px dashed var(--line); border-radius: var(--radius);
  touch-action: manipulation;
}
.bs-pick-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bs-pick .bs-mini { align-items: center; }
.bs-pick .bs-mini span { width: 0.55rem; height: 0.55rem; }
.bs-pick.placed { background: var(--paper-2); border-style: solid; }
.bs-pick.placed .bs-mini::after { content: "✓"; margin-left: 0.2rem; color: var(--good); font-weight: 800; line-height: 0.6rem; }
.bs-pick.picked { border: 2px solid #c9a227; box-shadow: 0 0 0 2px #c9a227; }
.bs-pick.picked .bs-mini span { background: #c9a227; }
/* Shuffle and Ready stick above the Rules/Chat bar (see `.bs-dock`) even
   when the table's header (watchers wrap onto more lines) pushes them past
   the bottom. `.bs-place` doesn't box them, so they stick within the whole
   game. */
.bs-place { display: contents; }
.bs-place-actions { display: flex; gap: 0.6rem; }
.bs-place-actions .gn-button { flex: 1; min-height: 3rem; font-size: 1.1rem; }
