/* Connections was the first game built here, and its UI never moved past the
   original gold-on-black pass: every surface, border and button carried the same
   weight, so nothing receded. This overlay brings it onto the same footing as
   ScribbleScape, Model Match and the homepage — a warm card/raised/chip ladder,
   one gold reserved for edges, and the green primary. Loaded after styles.css so
   the shared sheet stays untouched (editing it would force a ?v= bump on every
   page). Scoped to .connections-page throughout. */
.connections-page {
  --cx-card: #35291f;
  --cx-raised: #2a231b;
  --cx-chip: #2e2720;
  --cx-well: #211b16;
  --cx-secondary: #4a3825;
  --cx-border: #b18a4f;
  --cx-inner: #5b4730;
  --cx-hair: #4a3a28;
  --cx-ink: #ffe9bf;
  --cx-muted: #c5ad8b;
  --cx-title: #f3d49b;
  --cx-value: #ffe1a3;
  --cx-gold: #f0c56d;
  --cx-green: #365f2b;
  --cx-green-edge: #80a552;
  --cx-good: #c3e698;
  --cx-bad: #ff9f7a;
  --cx-focus: #e8bd70;
}

/* --- The ground ----------------------------------------------------------- */
/* The flat #2E2C29 grey read as 'no decision made', and it sat only a shade off
   the tiles, so the board never looked like it was resting on anything. Same
   treatment as Higher/Lower, which is the point — one site, one ground: a warm
   near-black base, a soft pool of light behind the board, and grain over the
   top. Everything the page draws is warm brown, so the base is warm-black
   rather than neutral; a grey base under warm panels is what made the old
   background look like an unpainted wall.
   Going darker than the tiles is what does the real work here: the board and
   its panels become the lit surfaces and the page gains a foreground. */
.connections-page {
  background-color: #14110d;
  /* Centred a little above the middle so the pool sits behind the board rather
     than the footer. Stops short of the edges, which vignettes the corners
     without a second gradient doing it. */
  background-image:
    radial-gradient(ellipse 80% 62% at 50% 30%, rgba(104, 80, 50, .26), transparent 74%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}
/* Grain as its own fixed layer, screened rather than blended: soft-light and
   overlay scale with the backdrop's luminance, so over a near-black ground they
   do nothing at all. A faint light copy screened on top is what actually reads
   as material. Fixed, so it stays put while the page scrolls and never turns
   into a moving pattern. */
.connections-page::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url('assets/textures/grain-soft.png') repeat;
  background-size: 620px 620px;
  mix-blend-mode: screen;
  opacity: .09;
}

/* --- Foundations --------------------------------------------------------- */
.connections-page .panel {
  background: var(--cx-card);
  border: 1px solid var(--cx-border);
  border-radius: 12px 8px 12px 8px;
  box-shadow: inset 0 0 0 3px #211b16, 0 10px 30px #0005;
}
.connections-page .btn {
  min-height: 40px;
  color: var(--cx-ink);
  background: var(--cx-secondary);
  border: 1px solid var(--cx-border);
  border-radius: 5px;
  box-shadow: inset 0 1px 0 #ffffff0a, 0 2px 0 #19140f;
}
.connections-page .btn:hover { background: #5a432a; border-color: #edbd68; }
.connections-page .btn-primary {
  color: #fff0cd;
  background: var(--cx-green);
  border-color: var(--cx-green-edge);
  box-shadow: inset 0 1px 0 #ffffff12, 0 2px 0 #19140f;
}
.connections-page .btn-primary:hover { background: #447737; border-color: #9dc26a; }
.connections-page .btn:disabled { filter: grayscale(.6); opacity: .45; box-shadow: 0 2px 0 #19140f; }
.connections-page .btn-small { min-height: 34px; }
.connections-page :is(button, a, input, select, textarea):focus-visible {
  outline: 2px solid var(--cx-focus); outline-offset: 2px;
}
.connections-page :is(.input, .textarea) {
  min-height: 42px;
  padding: 8px 11px;
  color: var(--cx-ink);
  background: #161b18;
  border: 1px solid var(--cx-border);
  border-radius: 5px;
}
.connections-page .textarea { min-height: 110px; }
.connections-page ::placeholder { color: #9c9482; opacity: 1; }
.connections-page .toggle {
  min-height: 36px;
  padding: 5px 10px;
  color: var(--cx-muted);
  background: var(--cx-chip);
  border: 1px solid var(--cx-hair);
  border-radius: 5px;
}
.connections-page .toggle:hover { color: var(--cx-ink); border-color: var(--cx-border); }
.connections-page .toggle:has(input:checked) {
  color: var(--cx-ink); background: var(--cx-secondary); border-color: var(--cx-border);
}
.connections-page .toggle input { accent-color: var(--cx-focus); }

/* --- Header rail ---------------------------------------------------------- */
/* Two loud gold buttons of different styles competed with the board; Browse is
   the everyday action and keeps the weight, Create sits a step below it. */
.connections-page .header-rail { margin-bottom: 14px; }
/* Create and the sound control (sfx.js) share the right-hand end of the rail. */
.connections-page .header-rail-end { display: inline-flex; align-items: center; gap: 10px; }
.connections-page .btn-browse,
.connections-page .btn-create {
  min-height: 42px;
  padding: 8px 16px;
  border-radius: 5px;
  box-shadow: inset 0 1px 0 #ffffff0a, 0 2px 0 #19140f;
}
/* Browse is the everyday action, so it takes the site's green — the same green
   as Continue on the win modal and Browse Puzzles on the certificate, so one
   action reads the same colour wherever it appears. It is also the only colour
   on the page above the board, which the warm-black ground needed.
   Create stays secondary but no longer whispers: against the old grey its
   #2a231b ghost fill was nearly invisible, and on the darker ground it
   disappeared outright. It moves up to the warm brown with a lit edge — clearly
   a button, clearly not the primary one. */
.connections-page .btn-browse {
  color: #fff0cd;
  background: var(--cx-green);
  border: 1px solid var(--cx-green-edge);
}
.connections-page .btn-browse:hover { background: #447737; border-color: #9dc26a; }
.connections-page .btn-create {
  color: var(--cx-ink);
  background: var(--cx-secondary);
  border: 1px solid var(--cx-border);
}
.connections-page .btn-create:hover { color: #fff0cd; background: #5a432a; border-color: #edbd68; }
/* The rail label read small against a 600px board. Desktop has the room. */
@media (min-width: 721px) {
  .connections-page .btn-browse,
  .connections-page .btn-create { font-size: 1.15rem; }
}
/* The wider board reaches the hiscore rails before they hide at 1200, so on
   this page they step away earlier. */
@media (max-width: 1320px) {
  .connections-page .lb-fixed { display: none; }
}

/* --- How to play ---------------------------------------------------------- */
/* This sat as a full-width slab as loud as the board. It is a footnote. */
.connections-page .instructions.panel.info {
  padding: 9px 14px;
  background: var(--cx-raised);
  border: 1px solid var(--cx-hair);
  border-radius: 6px;
  box-shadow: none;
}
.connections-page .instructions.panel.info p { color: var(--cx-muted); font-size: .95rem; }
.connections-page .instructions.panel.info strong { color: var(--cx-title); }

/* --- Board and tiles ------------------------------------------------------ */
/* Tall NPC models need vertical room, so the board grows. Tiles stay square
   and the 4x4 grid stays aligned; only the module size changes. */
.connections-page {
  --grid-max: 700px;
  /* --tile-size and --board-width resolve var() at :root, so they must be
     redeclared here for the wider board to take effect. */
  --tile-size: calc((var(--grid-max) - (2 * var(--grid-padding)) - (2 * var(--grid-border)) - (3 * var(--grid-gap))) / 4);
  --board-width: calc(var(--grid-max) + 2 * var(--grid-padding) + 2 * var(--grid-border));
}
.connections-page #board {
  --board-gap: 8px;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: repeat(4, minmax(0, 1fr));
  aspect-ratio: 1;
  gap: var(--board-gap);
  padding: var(--board-gap);
  background: var(--cx-well);
  border: 1px solid var(--cx-border);
  border-radius: 10px 6px 10px 6px;
  box-shadow: inset 0 0 22px #0007, 0 10px 30px #0005;
}
/* Solved bands replace one row inside the same four-row board. */
.connections-page #board .grid,
.connections-page #board .solved-list {
  display: contents;
}
/* The board above takes its height from aspect-ratio and splits it into four
   minmax(0, 1fr) rows. WebKit doesn't resolve that pairing reliably: iOS Safari
   sizes the rows from content instead, so rows holding a two-line label
   ("K'ril Tsutsaroth") grew tall and pushed the board past its frame.
   The tiles are square on their own, so let the rows follow them rather than
   the other way round — same result, no reliance on the container's height. */
@media (max-width: 720px) {
  .connections-page #board {
    aspect-ratio: auto;
    grid-template-rows: repeat(4, auto);
  }
  /* aspect-ratio yields to min-content height, which would undo the above. */
  .connections-page #board .tile {
    min-height: 0;
  }
}
.connections-page #board .solved-panel {
  grid-column: 1 / -1;
  height: 100%;
  min-height: 0;
  min-width: 0;
}
.connections-page #board .tile {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  min-width: 0;
}
/* Tiles read as empty slots before: flat, unlit, no press. They are the one
   thing on the page you touch, so they get the raised treatment and a real
   push-in on select. */
.connections-page .tile {
  color: var(--osrs-yellow, #ffcf3f);
  background: linear-gradient(#332b21, var(--cx-chip));
  border: 1px solid var(--cx-inner);
  border-radius: 6px;
  box-shadow: inset 0 1px 0 #ffffff0d, 0 2px 0 #19140f;
}
.connections-page .tile:hover {
  background: linear-gradient(#3d3327, #332b21);
  border-color: var(--cx-border);
  box-shadow: inset 0 1px 0 #ffffff12, 0 2px 0 #19140f;
}
.connections-page .tile.selected {
  color: #eaf4ff;
  background: linear-gradient(#2f6ea8, #23547f);
  border-color: #6fb8e8;
  filter: none;
  transform: translateY(2px);
  box-shadow: inset 0 0 0 1px #ffffff26, inset 0 2px 6px #00000059;
}
.connections-page .tile.correct {
  color: #eaf5d8;
  background: linear-gradient(#3f5f30, #2f4a24);
  border-color: var(--cx-green-edge);
  box-shadow: inset 0 1px 0 #ffffff14, 0 2px 0 #19140f;
}
.connections-page .tile.incorrect {
  color: #ffd9cb;
  background: linear-gradient(#5a2b1e, #3a201a);
  border-color: #c8613f;
  box-shadow: inset 0 1px 0 #ffffff10, 0 2px 0 #19140f;
}
.connections-page .tile:focus-visible { outline: 2px solid var(--cx-focus); outline-offset: 2px; }
.connections-page .tile-label {
  color: inherit;
  font-size: 1.625rem;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* Solved bands sat in the same flat brown as everything else. */
/* Each band is painted its god colour inline by script.js. The slate rides on
   top in overlay so one greyscale tile tints itself to all four — but kept to a
   whisper: four full-width bands are most of this screen, and at the strength
   that suits a single button the texture just fights the category name. */
.connections-page .solved-panel {
  position: relative;
  isolation: isolate;
  border: 1px solid #0000005c;
  border-radius: 5px;
  box-shadow: inset 0 1px 0 #ffffff26, inset 0 -18px 30px #0000002e, 0 2px 0 #19140f;
}
.connections-page .solved-panel::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image: url('assets/textures/cracked-slate.png');
  background-size: 512px 512px;
  background-repeat: repeat;
  image-rendering: pixelated;
  mix-blend-mode: overlay;
  opacity: .2;
}
/* A band is ~640px wide, so at 512 the tile barely repeats within one. Starting
   each band at a different point in the tile stops the four of them lining up
   into a pattern down the page — the thing that read as "repeat" before. */
.connections-page .solved-panel:nth-child(2)::before { background-position: -197px -83px; }
.connections-page .solved-panel:nth-child(3)::before { background-position: -341px -226px; }
.connections-page .solved-panel:nth-child(4)::before { background-position: -88px -313px; }
.connections-page :is(.solved-title, .solved-items) { position: relative; z-index: 1; }
.connections-page .solved-title { color: #fff6e2; text-shadow: 0 2px 3px #000000a6; }
.connections-page .solved-items { color: #ffffffcc; text-shadow: 0 1px 3px #000000a6; }

/* --- Controls row and footer ---------------------------------------------- */
.connections-page .menu.panel {
  margin-top: 12px;
  padding: 10px 14px;
  background: var(--cx-raised);
  border: 1px solid var(--cx-inner);
  border-radius: 8px 6px 8px 6px;
  box-shadow: none;
}
.connections-page .menu .button-group { display: inline-flex; gap: 8px; }
.connections-page .guess-counter { color: var(--cx-muted); }
.connections-page .guess-counter #difficultyLabel { color: var(--cx-value); }
.connections-page .rsn-footer { margin-top: 10px; color: var(--cx-muted); }
.connections-page .rsn-footer #rsnBanner { flex: 1; }
.connections-page .rsn-footer a.btn-small { color: var(--cx-ink); }
.connections-page .rsn-footer a.btn-small:hover { color: #fff0cd; }
.connections-page .rsn-footer .connections-discord-link { color: #c9d2ff; }
.connections-page .rsn-footer .connections-discord-link:hover { color: #e2e7ff; }

/* --- Modals --------------------------------------------------------------- */
.connections-page .modal { background: rgba(8, 6, 4, .72); }
.connections-page .modal .dialog {
  padding: 24px;
  background: var(--cx-card);
  border: 1px solid var(--cx-border);
  border-radius: 12px 8px 12px 8px;
  box-shadow: inset 0 0 0 3px #211b16, 0 16px 48px #0006;
}
.connections-page .modal .dialog h2 {
  margin: 0 0 6px;
  color: var(--cx-title);
  font: 30px/1.1 'Quill 8', 'RuneScape Bold', Arial, serif;
}
.connections-page .modal .dialog .helper { color: var(--cx-muted); }
.connections-page .modal .dialog .modal-close {
  top: 14px; right: 14px;
  color: var(--cx-ink); background: var(--cx-secondary);
  border: 1px solid var(--cx-border); border-radius: 5px;
}
.connections-page .modal .dialog .modal-close:hover { background: #5a432a; border-color: #edbd68; }

/* --- Puzzle browser ------------------------------------------------------- */
.connections-page #puzzleModal .dialog { width: min(900px, 95vw); }
.connections-page .pzl-toolbar { margin-bottom: 12px; }
.connections-page .pzl-heading {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}
.connections-page #puzzleModal .pzl-heading h2 { margin: 0; flex: 1; }
.connections-page #puzzleModal .pzl-heading .modal-close {
  position: static;
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  font: 26px/1 Arial, sans-serif;
}
@media (min-width: 721px) {
  .connections-page .quest-header-desktop,
  .connections-page .quest-row {
    grid-template-columns: 64px minmax(0, 1.25fr) minmax(0, 1fr) minmax(90px, .85fr) minmax(100px, 1fr) minmax(75px, .8fr) minmax(75px, .8fr);
    column-gap: 14px;
  }
  /* The old spacer and action cells are unused on desktop. Keep the last
     cell available for the compact mobile completion count. */
  .connections-page .quest-header-desktop > :nth-child(5),
  .connections-page .quest-row > :nth-child(5),
  .connections-page .quest-actions-h,
  .connections-page .quest-actions { display: none; }
}
.connections-page .quest-list {
  padding: 0;
  background: var(--cx-well);
  border: 1px solid var(--cx-inner);
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px #14100b;
  gap: 0;
}
.connections-page .quest-header-desktop,
.connections-page .quest-header-mobile {
  padding: 9px 10px;
  background: #241e17;
  border-bottom: 1px solid var(--cx-inner);
}
.connections-page :is(.quest-header-desktop div, .quest-header-desktop button.sorter,
  .quest-header-left, .quest-header-right) {
  color: var(--cx-muted); font-size: .82rem; letter-spacing: .06em; text-transform: uppercase;
}
.connections-page .quest-row {
  padding: 9px 10px;
  border-radius: 0;
  border-bottom: 1px solid #ffffff08;
}
.connections-page .quest-row:nth-child(odd),
.connections-page .quest-row:nth-child(even) { background: transparent; }
.connections-page .quest-row:nth-child(even) { background: #ffffff05; }
.connections-page .quest-row:hover { background: #4a382540; }
/* Pure #00FF00 / #FF0000 were the harshest thing on the page. */
.connections-page .quest-name { color: var(--cx-value); }
.connections-page .quest-name.completed { color: #8fd07a; }
.connections-page .quest-name.uncompleted { color: var(--osrs-yellow, #ffcf3f); }
.connections-page .quest-meta { color: #a8916e; }
.connections-page .quest-added { color: var(--cx-muted); white-space: nowrap; }
.connections-page :is(.quest-rsn, .quest-completions, .quest-upvotes, .quest-rating) { color: var(--cx-muted); }
.connections-page .quest-rating { color: var(--cx-gold); }
.connections-page .quest-type-badge-col .type-badge {
  padding: 3px 7px;
  border-radius: 4px;
}
/* Words and Icons get their own colour so the format is readable at a glance
   rather than from the label alone — steel blue for text, green for icons. */
.connections-page .quest-type-badge-col .type-badge-words {
  color: #b9d4e8;
  background: #23303a;
  border: 1px solid #3d5a6e;
}
.connections-page .quest-type-badge-col .type-badge-icons {
  color: #a8dd8d;
  background: #223021;
  border: 1px solid #44693c;
}
/* The filter popover was semi-transparent over the rows behind it, so both
   became unreadable. It is a card now, and it sits clear of the table. */
.connections-page .pzl-filters {
  padding: 14px;
  background: var(--cx-card);
  border: 1px solid var(--cx-border);
  border-radius: 8px 6px 8px 6px;
  box-shadow: inset 0 0 0 2px #211b16, 0 14px 34px #000a;
  z-index: 30;
}
.connections-page .pzl-filter-row { gap: 12px; margin: 10px 0; }
.connections-page .pzl-filter-row > label {
  color: var(--cx-muted); font-size: .8rem; letter-spacing: .06em; text-transform: uppercase;
}
.connections-page .pzl-footer {
  gap: 12px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--cx-inner);
}
.connections-page :is(.pzl-count, .pzl-page) { color: var(--cx-muted); }

/* --- Create puzzle -------------------------------------------------------- */
.connections-page #creatorModal .dialog { width: min(860px, 95vw); }
/* A legend straddling a 2px gold border made every group look broken. Sit the
   label above its own quiet panel instead. */
.connections-page .fieldset.panel {
  padding: 14px;
  background: var(--cx-raised);
  border: 1px solid var(--cx-inner);
  border-radius: 8px 6px 8px 6px;
  box-shadow: none;
}
/* Without a background the panel border ran straight through the label. */
.connections-page .fieldset legend {
  margin-left: -4px;
  padding: 0 8px;
  color: var(--cx-title);
  background: var(--cx-raised);
  font-size: 1.05rem;
}
/* Injected by script.js with its own class, so it missed the .btn pass and kept
   the old gold-on-black look next to every item field. */
.connections-page .icon-search-btn {
  min-height: 42px;
  padding: 6px 12px;
  color: var(--cx-muted);
  background: var(--cx-secondary);
  border: 1px solid var(--cx-hair);
  border-radius: 5px;
  box-shadow: inset 0 1px 0 #ffffff0a, 0 2px 0 #19140f;
}
.connections-page .icon-search-btn:hover { color: var(--cx-ink); background: #5a432a; border-color: var(--cx-border); }
.connections-page .creator-grid { gap: 14px; }
.connections-page .form-row { grid-template-columns: 92px 1fr; gap: 10px; margin: 9px 0; }
.connections-page .form-row label { color: var(--cx-muted); }
/* Only the category names carried a gold ring, which read as an error state
   rather than as emphasis. Every field looks the same until it is focused. */
.connections-page .input.category-name { border-color: var(--cx-border); }
.connections-page .input.item-input { border-color: var(--cx-hair); }
.connections-page .input:focus { border-color: var(--cx-focus); }
.connections-page select.input {
  color: var(--cx-ink);
  background-color: #161b18;
}
.connections-page select.input option { color: var(--cx-ink); background: #241e17; }
.connections-page .creator-controls { gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--cx-inner); }
.connections-page .error { color: var(--cx-bad); }

/* --- Hiscore rails -------------------------------------------------------- */
/* These rails are the one light surface on the page — a parchment scroll drawn
   by #lbLeft/#lbRight pseudo-elements — so they take ink colours. The overlay
   cream used everywhere else was invisible against it. */
.connections-page .lb-title { color: #2a1f10; }
.connections-page .lb-header div { color: #6b5730; }
.connections-page :is(.lb-row .lb-name, .lb-row .lb-stat) { color: #33281a; }
.connections-page .lb-row.me :is(.lb-name, .lb-stat) { color: #7a4e08; }

@media (max-width: 720px) {
  .connections-page #board .tile { padding: 6px; }
  .connections-page .tile-label {
    font-size: clamp(16px, 4.3vw, 18px);
    line-height: 1.15;
    padding: 0;
    overflow-wrap: anywhere;
  }
}

@media (max-width: 640px) {
  .connections-page .modal .dialog { padding: 18px; }
  .connections-page .modal .dialog h2 { font-size: 24px; }
  .connections-page .fieldset.panel { padding: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .connections-page .tile { transition: none; }
}

/* --- Local dev tools (connections-dev.js, localhost only) ----------------- */
/* Deliberately plain and unbranded: the panel sits over the real board while
   you preview states, so it should never be mistaken for part of the game. */
.oc-dev-launcher {
  position: fixed; right: 12px; bottom: 12px; z-index: 10050;
  padding: 8px 12px; border: 1px solid #b99a68; border-radius: 6px;
  background: #241e18; color: #f1d49f; font: 13px system-ui, sans-serif; cursor: pointer;
}
.oc-dev-panel {
  position: fixed; right: 12px; bottom: 12px; z-index: 10050; width: 288px;
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border: 1px solid #8b7150; border-radius: 8px;
  background: #191512; color: #f3d49b; font: 13px system-ui, sans-serif;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
/* The display:flex above outranks the browser's [hidden] { display: none },
   so `panel.hidden = true` left the panel on screen — the close button, Alt+D
   and Escape all looked dead, and it opened by itself on page load. */
.oc-dev-panel[hidden] { display: none; }
.oc-dev-head { display: flex; align-items: center; justify-content: space-between; }
.oc-dev-x {
  border: 0; background: none; color: #d8bd8f; font-size: 18px; line-height: 1;
  padding: 0 4px; cursor: pointer;
}
.oc-dev-state { margin: 0; color: #c9b48f; font-size: 12px; line-height: 1.4; }
.oc-dev-group { display: flex; flex-direction: column; gap: 6px; }
.oc-dev-legend { color: #b8a68a; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
.oc-dev-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.oc-dev-panel button:not(.oc-dev-x) {
  flex: 0 1 auto; padding: 6px 9px; border: 1px solid #8b7150; border-radius: 4px;
  background: #332a21; color: #f1dfbd; font: inherit; cursor: pointer;
}
.oc-dev-panel button:not(.oc-dev-x):hover { background: #40342a; }
.oc-dev-label { display: inline-flex; align-items: center; gap: 4px; color: #c9b48f; font-size: 12px; }
.oc-dev-panel input[type="number"] {
  width: 48px; padding: 4px; border: 1px solid #8b7150; border-radius: 4px;
  background: #241e18; color: #f1dfbd; font: inherit;
}
.oc-dev-panel output { min-width: 20px; text-align: center; color: #ffe1a3; }
.oc-dev-foot { justify-content: space-between; border-top: 1px solid #4a3a28; padding-top: 10px; }
.oc-dev-note { color: #9d8b70; font-size: 11px; line-height: 1.35; }
@media (max-width: 640px) {
  /* Never taller than half the phone: the board behind it is the point. */
  .oc-dev-panel { right: 8px; left: 8px; width: auto; max-height: 55vh; overflow-y: auto; }
}

/* --- Rating thumbs -------------------------------------------------------- */
/* These were 64px gold-on-black tiles left over from the original pass: big
   enough to shove Browse Puzzles off the parchment, and the only control on the
   scroll that didn't look like the site's buttons. Sized and coloured to the
   .btn ladder instead, borrowing btn-primary's green and btn-danger's red for
   the two answers. Scoped here rather than styles.css (where the base rating
   CSS sits) because the widget only ever renders on this page. */
.connections-page .rating-buttons { gap: 12px; }
.connections-page .rating-btn {
  width: 46px;
  height: 46px;
  font-size: 24px;
  color: var(--cx-ink);
  background: var(--cx-secondary);
  border: 1px solid var(--cx-border);
  border-radius: 5px;
  box-shadow: inset 0 1px 0 #ffffff0a, 0 2px 0 #19140f;
}
.connections-page .rating-btn:hover { background: #5a432a; border-color: #edbd68; }
.connections-page .rating-btn:active { transform: translateY(1px); box-shadow: 0 1px 0 #19140f; }
.connections-page .rating-btn.rating-up.selected {
  background: var(--cx-green);
  border-color: var(--cx-green-edge);
}
.connections-page .rating-btn.rating-down.selected {
  background: #372020;
  border-color: #c85f5f;
}
/* A vote is final, so once one is cast the pair stops behaving like buttons:
   the chosen thumb keeps its colour, the other recedes, neither reacts. */
.connections-page .rating-buttons.rating-locked .rating-btn { cursor: default; }
.connections-page .rating-buttons.rating-locked .rating-btn:not(.selected) { opacity: .4; }
.connections-page .rating-buttons.rating-locked .rating-btn:hover {
  background: var(--cx-secondary);
  border-color: var(--cx-border);
}
.connections-page .rating-buttons.rating-locked .rating-up.selected:hover {
  background: var(--cx-green);
  border-color: var(--cx-green-edge);
}
.connections-page .rating-buttons.rating-locked .rating-down.selected:hover {
  background: #372020;
  border-color: #c85f5f;
}
.connections-page .rating-buttons.rating-locked .rating-btn:active {
  transform: none;
  box-shadow: inset 0 1px 0 #ffffff0a, 0 2px 0 #19140f;
}
/* The scroll's writing area is short, so the block between the award lines and
   the button is kept tight enough for all of it to sit on the parchment. */
.connections-page .scroll-rating { margin-top: 12px; }
/* The certificate stacks its button under the text rather than in the card's
   fixed bottom slot, which the thumbs would otherwise run into. */
.connections-page .scroll-actions-flow {
  position: static;
  margin-top: 14px;
}
/* The shared 32% start was tuned for the win modal's shorter text. With the
   award lines and the thumbs, the certificate's block ran past the bottom of
   the writing area while leaving ~80px of blank parchment above the title, so
   it starts higher here and sits centred on the cream instead. */
.connections-page .scroll-text-cert { top: 26%; }

/* --- Icon loading spinner ------------------------------------------------- */
/* Tiles used to show a broken-image glyph until their icon resolved. The tile
   carries the spinner because an <img> cannot have pseudo-elements. */
.connections-page .tile-content { position: relative; }
.connections-page .tile-content.icon-loading .tile-icon { opacity: 0; }
.connections-page .tile-content.icon-loading::after {
  content: '';
  position: absolute;
  top: calc(50% - var(--tile-icon-size, 92px) / 4);
  left: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border: 2px solid #ffffff1f;
  border-top-color: var(--cx-gold, #d9a441);
  border-radius: 50%;
  animation: tile-icon-spin .7s linear infinite;
  pointer-events: none;
}
@keyframes tile-icon-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .connections-page .tile-content.icon-loading::after { animation-duration: 2.4s; }
}
