/* public/stylesheets/components.css
 *
 * Shared vocabulary. Views compose these; they do not write their own CSS.
 * If a view needs something not here, add it here rather than inline -- a
 * one-off style is a sign the system is missing a component.
 */

/* Text for screen readers only: off-screen, one pixel, never displayed. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Buttons -------------------------------------------------------------
 * Hierarchy: go -> neutral (.btn) -> quiet, with danger set apart.
 * go / time / danger are neighbouring hues in this palette, so they are
 * separated by TREATMENT as well:
 *   .btn--go     filled, the only filled warm control on a screen
 *   .btn--quiet  no fill, no border -- cancel and back
 *   .btn--danger outlined, never filled, always behind a confirm
 *   .countdown   not a button at all -- see below
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.btn:hover { border-color: var(--text-dim); text-decoration: none; }

.btn--go {
  background: var(--go);
  border-color: var(--go);
  color: var(--go-ink);
}

.btn--danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

.btn--danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }

/* Secondary: cancel, back. Quieter than neutral, never competing with go. */
.btn--quiet {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
}
.btn--quiet:hover { color: var(--text); border-color: transparent; }

/* Disabled by colour and cursor, never opacity: a faded control on the dark
   theme becomes unreadable rather than inactive. WCAG exempts inactive
   controls from contrast minimums; legibility is still the point. */
.btn:disabled,
.btn:disabled:hover {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text-dim);
  cursor: not-allowed;
}

/* A countdown is information, not an action. Never filled, never bordered --
   if it looks pressable, someone will press it. */
.countdown {
  color: var(--time);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* --- Forms --------------------------------------------------------------- */
.field { display: block; margin-bottom: var(--space-4); }
.field > label { display: block; margin-bottom: var(--space-1); color: var(--text-dim); font-size: var(--text-sm); }


/* The one-time generated password shown on users/welcome_password when the
   welcome letter couldn't be sent. SecureRandom.alphanumeric emits l/1/I and
   O/0, and this is a string a user has to transcribe under time pressure,
   often on a phone screen -- monospace tells those apart, the size bump makes
   it legible at arm's length, and user-select: all means one tap/click
   selects the whole thing instead of a partial drag. */
.generated-password {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: 0.02em;
  -webkit-user-select: all; /* Safari, incl. iOS Safari, supports unprefixed user-select only from 17.0 */
  user-select: all;
}

input[type="text"], input[type="email"], input[type="password"],
input[type="date"], input[type="datetime-local"], input[type="number"],
input[type="search"], input[type="url"], input[type="tel"],
textarea, select {
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-2) var(--space-3);
  background: var(--bg);
  border: 1px solid var(--border-input);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;   /* 16px floor from base.css -- stops iOS zooming on focus */
}

/* Rails emits no `rows`, so without this a textarea falls back to the
   browser default of 2 -- ~45px more than min-height above, but still only
   two lines for level text and game descriptions, the core authoring
   content. */
textarea { min-height: 8em; }

input:disabled, select:disabled, textarea:disabled {
  background: var(--surface);
  color: var(--text-dim);
  cursor: not-allowed;
}

/* A checkbox or radio wrapped in its label: the whole row is the target. */
.check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  cursor: pointer;
}
input[type="checkbox"], input[type="radio"] {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  vertical-align: middle;
  accent-color: var(--go);
}

input[type="file"] { font: inherit; }
input[type="file"]::file-selector-button {
  min-height: var(--tap);
  margin-right: var(--space-3);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* Set by FieldErrorMarkup (app/services/field_error_markup.rb), Rails'
   field_error_proc here. A control and its label are marked in place; the
   control's own messages follow it in .field-error.

   ORDER MATTERS: the invalid-control rule has the same specificity (0,1,1) as
   the shared text-input rule's `border` shorthand and the `input:disabled`
   rule above, so it only wins by coming after them. Moved above, every
   invalid input but the textarea silently kept its neutral border. */
label.is-invalid { color: var(--danger); }
.is-invalid:is(input, select, textarea) { border-color: var(--danger); }
.field-error {
  display: block;
  margin-top: var(--space-1);
  color: var(--danger);
  font-size: var(--text-sm);
}

fieldset.field { border: 0; padding: 0; }
fieldset.field > legend { margin-bottom: var(--space-1); color: var(--text-dim); font-size: var(--text-sm); }
/* `.field > label` (0,1,1) beats `.check` (0,1,0). An option's text is
   content, not a field caption, so a .check row directly inside a .field
   (a fieldset of radios) keeps its row layout and body text. */
.field > label.check {
  display: flex;
  margin-bottom: 0;
  color: var(--text);
  font-size: inherit;
}
/* An invalid checkbox or radio: FieldErrorMarkup marks the control
   (is-invalid, aria-invalid) and appends its .field-error span inside the
   label; the hand-written label.check is never marked itself. */
.field label.check:has(.is-invalid) { color: var(--danger); }
.check .field-error { margin-top: 0; }

/* --- Tables --------------------------------------------------------------
 * Real table at width; stacked cards below. The wrapper also lets a wide
 * table scroll rather than forcing the page to scroll sideways. */
.table-wrap { overflow-x: auto; }

/* The UA stylesheet gives <fieldset> min-inline-size: min-content, which
   overrides a grid track's `minmax(0, 1fr)` (.dash-grid, screens.css) --
   the fieldset refuses to shrink below its content's min-content width and
   drags the whole grid item wider instead of letting .table-wrap above
   scroll internally as designed. Reset it on every fieldset (not just
   #mygames) since any future .table--cards inside a fieldset.card would hit
   the same trap. */
fieldset { min-inline-size: 0; }

table { width: 100%; border-collapse: collapse; }
th, td { padding: var(--space-3); text-align: left; border-bottom: 1px solid var(--border); }
th {
  color: var(--text-dim);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
}

@media (max-width: 47.99rem) {
  .table--cards thead { display: none; }
  .table--cards tr {
    display: block;
    margin-bottom: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: var(--space-2);
  }
  .table--cards td { display: block; border: 0; padding: var(--space-1) var(--space-2); }
  /* Views set data-label on each cell so the column name survives the
     collapse -- without it a stacked row is a list of unlabelled values. */
  .table--cards td::before {
    content: attr(data-label);
    display: block;
    color: var(--text-dim);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.07em;
  }
}

/* .table--compact: a .table--cards table whose rows are short enough to read
 * as lines rather than labelled cards -- the operator's standings and live
 * channel, read at a glance mid-game. The per-cell labels go; each screen
 * arranges its own cells (screens.css: .standings, #livechannel). Long
 * unbroken names and answers wrap inside the card instead of widening it. */
@media (max-width: 47.99rem) {
  .table--compact td::before { content: none; }
  .table--compact td { padding: 0 var(--space-1); overflow-wrap: anywhere; }
}

/* The test-run invite link on the game page is one unbroken URL. Scoped to
 * it: a global rule broke code identifiers mid-word in the manual's tables. */
.invite-link { overflow-wrap: anywhere; }

/* --- File table thumbnails ------------------------------------------------
 * game_files/_file_table.html.erb (both :manage and :picker modes) renders
 * the thumbnail cell as a real <img> at the "thumb" variant -- up to 320px
 * wide, the variant's own resize_to_limit, with nothing here to constrain
 * it. Left alone that is one unconstrained image per row, on a form this
 * project measures at a 390px viewport (see the .playbar comment in
 * screens.css for how layout is measured here). Fixed box regardless of the
 * source photo's aspect ratio; object-fit crops rather than distorting. */
.file-table .file-thumb { width: 56px; }
.file-table .file-thumb-image {
  display: block;
  width: 48px;
  height: 48px;
  max-width: 48px;
  object-fit: cover;
  border-radius: var(--radius-sm);
}
.file-table .file-thumb-generic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px; /* a file icon in the image thumbnail's box; the label is .visually-hidden */
  overflow: hidden;
  color: var(--text-dim);
}

/* --- Flash, cards, panels, tags ------------------------------------------ */
.flash {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border-left: 3px solid var(--go);
  background: var(--surface);
  margin-bottom: var(--space-4);
}
/* :error is the only flash key this app ever sets (sessions_controller.rb);
   :alert is kept for anything that sets Rails' own conventional key. */
.flash--alert, .flash--error { border-left-color: var(--danger); }

/* The manual's "this is the Russian version" note (app/views/manual/show
   .html.erb) is neither success nor danger -- it is a plain fact about which
   file got served. Without this rule it inherited the base .flash's --go
   left border, the same green as a successful save, which told a reader who
   got no manual in their language that something had gone right. --border
   is the neutral token every other UI surface already uses for a dividing
   line with no valence of its own. */
.flash--notice { border-left-color: var(--border); }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
}

/* error_messages_for (app/helpers/application_helper.rb) and flash[:error]
   both render class="error" -- a validation/login-failure box. */
.error {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.error h2 { margin-bottom: var(--space-2); }
.error ul { margin-left: var(--space-4); list-style: disc; }

.tag {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: 999px;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  border: 1px solid var(--border);
}
.tag--live { color: var(--go); border-color: var(--go); }
.tag--danger { color: var(--danger); border-color: var(--danger); }

/* The signup honeypot (app/views/users/new.html.erb). Positioned off-screen
   rather than display:none or visibility:hidden -- some scripted clients skip
   fields whose computed style hides them, and the field is only useful if a
   robot believes it is real.

   Absolutely positioned so it is out of flow and can shift nothing: the form
   below it lays out exactly as if this element did not exist, on any viewport.
   Moved LEFT, not up or down, because overflow to the left of the viewport
   does not extend the scrollable area, while overflow to the right would add a
   horizontal scrollbar on a phone. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
