/* Hand-maintained project styles (there is no SCSS build in this repo). */

.alert-debug {
  color: black;
  background-color: white;
  border-color: #d6e9c6;
}

.alert-error {
  color: #b94a48;
  background-color: #f2dede;
  border-color: #eed3d7;
}

.card-constrained {
  max-width: 30rem;
}

.document-scroll {
  max-height: 24rem;
  overflow-y: auto;
}

/* --- game rows (crew library, player library, search results) --- */

/* min-width:0 lets a long title shrink/wrap instead of squeezing the action off the row. */
.game-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  min-width: 0;
}

.game-actions {
  flex-shrink: 0;
}

.game-name {
  min-width: 0;
  overflow-wrap: anywhere;
}

.game-identity {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* The art box reserves the thumbnail footprint so enlarging never reflows the row. */
.game-art {
  position: relative;
  flex-shrink: 0;
  width: 4.5rem;
  height: 2.5rem;
}

.game-art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Enlarged state, duplicated below for :hover — keep the two blocks in sync.
   Opens downward from the thumbnail so the row's own name stays readable; it does
   overlay the rows beneath, and pointer-events:none keeps their controls clickable.
   Focus is NOT gated on hover support — that would drop keyboard access on
   touch-primary devices used with a keyboard. */
.game-identity:focus-within .game-art img {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0.25rem;
  z-index: 1080;
  width: auto;
  height: auto;
  max-width: 18rem;
  max-height: 14rem;
  padding: 0.25rem;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  box-shadow: var(--bs-box-shadow);
  pointer-events: none;
}

@media (hover: hover) {
  .game-identity:hover .game-art img {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 0.25rem;
    z-index: 1080;
    width: auto;
    height: auto;
    max-width: 18rem;
    max-height: 14rem;
    padding: 0.25rem;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--bs-box-shadow);
    pointer-events: none;
  }
}

/* Assets file browser: a long unbroken filename (e.g. a tokenized upload
   name, capped at 128 chars) must not force the whole table into a
   horizontal scroll. Truncate the display name with an ellipsis (full name
   stays in the title tooltip and the rename field), and bound the inline
   rename input so it can't grow to the filename's width either. */
.asset-filename {
  display: inline-block;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

.asset-rename-input {
  max-width: 12rem;
}

/* Assets file browser: the two thumbnail placeholders (preview not ready,
   preview withheld) take the thumbnail's 40px box, so a row keeps its height
   when the real image swaps in. Sized here rather than inline: djlint H021. */
.asset-thumb-not-ready,
.asset-thumb-withheld {
  width: 40px;
  height: 40px;
  line-height: 40px;
}

/* Preferences, the quiet-hours editor (preferences/_quiet_hours.html): the
   seven day checkboxes of a window sit on one line rather than stacked, which
   is what Django's CheckboxSelectMultiple renders by default. */
.quiet-hours-days > div > div {
  display: inline-block;
  margin-right: 0.75rem;
}

/* Top-10 editor. The drag handle only appears when a pointer is available:
   dragging is progressive enhancement, and the up/down buttons beside it are
   the real (and only accessible) control, so a touch or keyboard user should
   not be shown an affordance that does nothing for them. */
.toplist-handle {
  cursor: grab;
  user-select: none;
  line-height: 1;
  padding: 0.125rem 0.25rem;
}

@media (hover: none) {
  .toplist-handle {
    display: none;
  }
}

.toplist-dragging {
  opacity: 0.5;
}

.toplist-rank {
  min-width: 1.75rem;
}

/* Adding a world backup: the folder drop target. The whole page accepts the
   drop (static/js/worlds_backup.js); this box is where it says so, and lights up
   while a folder is dragged over the page. */
.worlds-drop {
  border: 2px dashed var(--bs-border-color);
}
.worlds-drop.is-dragover {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
  color: var(--bs-primary-text-emphasis) !important;
}

/* "Where is it?": the place the card points at when a chosen folder holds no
   world ("not there? look here", static/js/worlds_backup.js openLocate). */
.worlds-locate-focus {
  outline: 2px solid var(--bs-primary);
  outline-offset: 0.25rem;
  border-radius: var(--bs-border-radius);
}

/* An inline edit: a value with a pencil beside it, or its form in the value's
   place -- a world's name (worlds/partials/_world_header.html) and a backup's
   label (_backup_row.html). data-editing on the [data-inline-edit] box decides
   which, for its OWN view and form (the child combinator). Both lay themselves
   out here rather than with d-flex, whose !important would beat the rule that
   hides them. */
.inline-edit-view,
.inline-edit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
[data-inline-edit][data-editing] > .inline-edit-view,
[data-inline-edit]:not([data-editing]) > .inline-edit-form {
  display: none;
}
.inline-edit-form .form-control {
  flex: 1 1 14rem;
  max-width: 32rem;
}
/* Save and Cancel wrap as one, so a phone puts both under the field. */
.inline-edit-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

/* A backup row's folded list -- a Factorio save's mods
   (worlds/partials/_backup_row.html). Closed, the summary previews the first
   few names; open, the list names every one, so the preview gives way. Columns
   keep a long modpack short on a wide screen and one column on a phone, and a
   hanging indent keeps a version that wraps visibly part of its own entry. */
.world-fact-list[open] .world-fact-preview {
  display: none;
}
.world-fact-items {
  columns: 20rem;
  column-gap: 1.5rem;
}
.world-fact-items > li {
  break-inside: avoid;
  padding-left: 1rem;
  text-indent: -1rem;
}
.inline-edit-open {
  display: inline-flex;
  padding: 0.375rem;
  border: 0;
  border-radius: var(--bs-border-radius);
  background: none;
  color: var(--bs-secondary-color);
}
.inline-edit-open:hover,
.inline-edit-open:focus-visible {
  color: var(--bs-link-color);
  background-color: var(--bs-tertiary-bg);
}
/* Beside a row's title, a smaller pencil. */
.inline-edit-open-sm {
  padding: 0.125rem 0.25rem;
}
.inline-edit-open-sm svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* Unfilled ranks: visible as structure without competing with real content. */
.toplist-placeholder {
  border-style: dashed;
  background-color: transparent;
}

/* Top-10 member controls: reorder and remove sit on the same row, so they need
   real hit areas and visible separation. Packed together with .p-0 they were
   ~14px apart, which made "move this game down" and "delete this game" the same
   gesture within a slip of the mouse — found by doing exactly that. 32px is
   still compact but no longer a trap; the remove button is also confirmed. */
.toplist-member-action {
  min-width: 2rem;
  min-height: 2rem;
  line-height: 1;
  padding: 0.25rem 0.4rem;
}

/* The voice player's look — the bar-only skin, the waveform layer and the
   preview canvas — is the template's audio skin now (media_player.css,
   `.video-js.audio-player` and `.media-waveform`), since the Files page's
   audio player is the same player. What stays here is what only this page
   has: the annotations drawer's placement under an audio bar, below. */


/* The annotations panel's chapter list: the controller marks the chapter the
   player is in with aria-current (from the clock, on every timeupdate), and
   this is the whole of its rendering — a weight, a left rule in the theme's
   primary, nothing that moves layout. The seek button inside is a link-styled
   button; the mark goes on the row so a shared marker can take the same rule
   later without a second selector. */
[data-chapter][aria-current="true"] {
  border-left: 3px solid var(--bs-primary);
  padding-left: 0.4rem;
  margin-left: -0.6rem;
}

[data-chapter][aria-current="true"] [data-chapter-title] {
  font-weight: 600;
}

/* A row's folded editor (chapters and notes alike): the Edit summary sits at
   the row's right while closed, and the whole editor takes the row's full
   width once opened — a form squeezed into the right-hand flex item was the
   alternative, and it wrapped every input. */
[data-annotations-panel] .annotations-editor:not([open]) {
  margin-left: auto;
}

[data-annotations-panel] .annotations-editor[open] {
  flex-basis: 100%;
}

/* The notes UI inside the player (annotations.js): three text buttons in
   the control bar in the save-default control's shape, and the drawer that
   holds the panel. The buttons take the bar's own colours; the drawer takes
   the page's — it is the page's panel, moved into the player. */
/* Three classes deep on purpose: this sheet loads BEFORE Video.js's on the
   play page (and before the lazy-loaded one on /voice/), so a rule at the
   stock `.video-js .vjs-control` specificity loses to it. The harness loads
   the sheets the other way round and could not see that; the screenshots
   did (every button 4em wide, the labels overlapping). */
.video-js .vjs-control-bar .vjs-notes-button,
.video-js .vjs-control-bar .vjs-add-note-button,
.video-js .vjs-control-bar .vjs-start-over {
  width: auto;
  padding: 0 0.6em;
  white-space: nowrap;
}

/* The drawer over a VIDEO: a right-hand column above the bar (3em is the
   stock bar's height), scrolling on its own, in the page's colours — it
   sits inside the player's element, which is what keeps it usable in
   fullscreen. The player sets font-size 10px for its em-based chrome, so
   the drawer restores the page's type; Video.js resets every <button> it
   contains, so Bootstrap's .btn is restored below. */
.video-js .vjs-annotations {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 3em;
  width: 40%;
  max-width: 28rem;
  display: none;
  overflow: auto;
  padding: 0.75rem;
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
  font-family: var(--bs-body-font-family);
  font-size: var(--bs-body-font-size);
  line-height: var(--bs-body-line-height);
  text-align: left;
  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.25);
}

.video-js.vjs-annotations-open .vjs-annotations {
  display: block;
}

/* An open drawer keeps the bar: the stock skin fades it when the pointer is
   idle, and the toggle that closes the drawer lives in it (measured: the
   bar faded on half the frames under an open, idle drawer). */
.video-js.vjs-annotations-open.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* A player with the notes UI shows its bar BEFORE the first play, under the
   big play button: the stock skin hides the bar until playback starts, and
   the bar is now the only way to the notes and chapters — which sat under
   the player, readable at load, before they moved inside it. */
.video-js.vjs-has-annotations:not(.vjs-has-started) .vjs-control-bar {
  display: flex;
  visibility: visible;
  opacity: 1;
}

/* On a phone the drawer is a sheet over the whole picture, above the bar: a
   16:9 video is ~200 px tall at 375 px, and a sheet that left room for the
   picture showed two lines of the panel (measured). Closing it — the toggle
   or Esc — brings the picture back; the bar stays reachable throughout. */
@media (max-width: 575.98px) {
  .video-js .vjs-annotations {
    top: 0;
    left: 0;
    right: 0;
    width: auto;
    max-width: none;
    max-height: none;
  }

  /* Add note collapses into the drawer's own form ("Now" takes the same
     rule); the Notes toggle never hides — it is the only way in. */
  .video-js .vjs-control-bar .vjs-add-note-button {
    display: none;
  }

  /* A VIDEO with the notes UI wraps its bar on a phone the way the audio
     player does — the timeline on its own row, the controls under it — so
     the toggle and Start over never push fullscreen and picture-in-picture
     off the edge (seen at 375 px: the bar's tail clipped). The sheet then
     sits above the two rows. */
  .video-js.vjs-has-annotations:not(.audio-player) .vjs-control-bar {
    flex-wrap: wrap;
    height: auto;
  }

  .video-js.vjs-has-annotations:not(.audio-player) .vjs-control-bar .vjs-control {
    height: 3em;
  }

  .video-js.vjs-has-annotations:not(.audio-player) .vjs-control-bar .vjs-progress-control {
    flex-basis: 100%;
    order: -1;
    height: 2em;
  }

  .video-js.vjs-has-annotations:not(.audio-player) .vjs-annotations {
    bottom: 5em;
  }
}

/* On an AUDIO player — a recording, or an audio file opened from Files — the
   drawer is a block under the bar, in flow: the box grows and the row below
   moves down. */
.video-js.audio-player .vjs-annotations {
  position: static;
  width: auto;
  max-width: none;
  max-height: none;
  overflow: visible;
  padding: 0.5rem 0.75rem 0;
  box-shadow: none;
  background: transparent;
}

.video-js .vjs-annotations .btn {
  border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);
  background-color: var(--bs-btn-bg);
  color: var(--bs-btn-color);
  padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);
  font-family: var(--bs-btn-font-family);
  font-size: var(--bs-btn-font-size);
  font-weight: var(--bs-btn-font-weight);
  line-height: var(--bs-btn-line-height);
  border-radius: var(--bs-btn-border-radius);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: none;
}

.video-js .vjs-annotations .btn:hover {
  color: var(--bs-btn-hover-color);
  background-color: var(--bs-btn-hover-bg);
  border-color: var(--bs-btn-hover-border-color);
}

.video-js .vjs-annotations .btn-link {
  border: none;
  background: none;
}

.video-js .vjs-annotations textarea,
.video-js .vjs-annotations input,
.video-js .vjs-annotations select {
  font-size: var(--bs-body-font-size);
  line-height: var(--bs-body-line-height);
}

.video-js .vjs-annotations a {
  color: var(--bs-link-color);
}

/* Dismissable onboarding surfaces (the setup card, the setup banner, the crew
   card). Their dismiss form posts with hx-swap="outerHTML swap:300ms" targeting
   the surface itself, and htmx 4 marks the swap TARGET .htmx-swapping for that
   delay — so the fade is these two rules and no script. An inner swap targets a
   descendant and never carries the class up to the surface. */
.dismiss-fade {
  transition: opacity 0.3s;
}

.dismiss-fade.htmx-swapping {
  opacity: 0;
}

/* Campaign lifecycle badges.
   AMBER IS DELIBERATELY ABSENT from these. It means "somebody has to do
   something", and on the campaigns surface exactly one thing does — an
   unresolved settings review (.campaign-needs-review below). Giving a
   lifecycle state the same colour puts two amber badges on one card meaning
   two different things, which is what the first mockup draft did.
   readers/campaigns.campaign_status_display owns which class a state gets;
   a template never picks one. */
.campaign-state-open {
  background: transparent;
  color: var(--bs-secondary-color);
  border: 1px solid var(--bs-border-color);
}
.campaign-state-prep {
  background: var(--bs-info-bg-subtle);
  color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color);
}
.campaign-state-live {
  background: var(--bs-success);
  color: var(--bs-success-bg-subtle);
}
.campaign-state-ended {
  background: var(--bs-tertiary-bg);
  color: var(--bs-secondary-color);
  border: 1px dashed var(--bs-border-color);
}
/* The one thing on this surface that asks somebody to act. */
.campaign-needs-review {
  background: var(--bs-warning-bg-subtle);
  color: var(--bs-warning-text-emphasis);
  border: 1px solid var(--bs-warning-border-subtle);
}

/* A world backup's picture, a 64 px square: stored at up to 128 px, so a
   photograph-like picture (a Factorio save's preview) stays sharp on a
   high-density screen. A row whose backup has no picture shows nothing. */
.world-backup-icon {
  width: 64px;
  height: 64px;
  flex: none;
  object-fit: cover;
}

/* Pixel art (a Minecraft world's own icon) keeps its hard edges when scaled. */
.world-backup-icon-pixels {
  image-rendering: pixelated;
}

/* A world map fills the window below the page's header; BlueMap needs room. */
.world-map-frame {
  height: calc(100vh - 12rem);
  min-height: 28rem;
}

/* The server uptime strip (servers/partials/_uptime_strip.html). A short strip's
   blocks carry their own figure, centred and small enough for a phone; the axis
   under the strip holds its date ticks, positioned by the view as percentages.
   "Every poll failed" is red STRIPES: a faded red already means "under 50% up,
   thinly sampled", and "we could not reach it at all" must not look the same. */
.uptime-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.uptime-axis {
  height: 1.25rem;
}

.uptime-failed {
  background: repeating-linear-gradient(
    45deg,
    var(--bs-danger) 0 3px,
    transparent 3px 6px
  );
  outline: 1px solid var(--bs-danger);
  outline-offset: -1px;
}
