/* ASCII Hub: a thin layer on top of styles.css.
   No new palette, no new type stack, no new interaction language. Everything
   here reuses the site's existing tokens (--bg-primary, --accent, --border,
   --text-secondary…), its 6px/12px radii, its translateY(-1px) hover and its
   Inter body face. Monospace appears in exactly one place: the art itself. */

.hub-main { display: flex; flex-direction: column; gap: 2.5rem; width: 100%; }

.nav-sep {
  width: 1px; align-self: stretch; background: var(--border);
  margin: 0 0.25rem;
}

/* ---------------------------------------------------------- section head ---
   Matches the weight and rhythm of the homepage's own section headings. */
.hub-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 0.75rem; border-bottom: 1px solid var(--border);
}
.hub-head h2 { font-size: 1.35rem; margin: 0; color: var(--text-primary); }
.hub-head .hub-count { color: var(--text-secondary); font-size: 0.9rem; }

.hub-tabs { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.hub-tabs a {
  color: var(--text-secondary); text-decoration: none; font-size: 0.9rem;
  padding: 0.5rem 0.75rem; border-radius: 4px; transition: all 0.2s;
}
.hub-tabs a:hover { color: var(--accent); background-color: rgba(99,102,241,0.05); }
.hub-tabs a[aria-current] {
  color: var(--accent); font-weight: 600; background-color: rgba(99,102,241,0.1);
}

/* ---------------------------------------------------------------- cards ---
   Same shape and motion as .gallery-item on the Pinterest gallery page. */
.hub-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1.5rem; padding: 0;
}
.hub-grid.is-sparse { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }

.hub-card {
  display: flex; flex-direction: column;
  border-radius: 12px; border: 1px solid var(--border);
  background-color: var(--bg-secondary);
  overflow: hidden;
  box-shadow: 0 4px 6px rgba(0,0,0,0.2);
  transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
}
.hub-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 8px 15px rgba(0,0,0,0.3);
  border-color: var(--accent);
}
.hub-card-thumb {
  display: block; aspect-ratio: 4 / 3; position: relative;
  background-color: var(--bg-primary); overflow: hidden;
  container-type: size;   /* lets the art size itself against this box */
}
.hub-card-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.hub-card-thumb .hub-thumb-art {
  position: absolute; inset: 0; display: grid; place-content: center;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
  white-space: pre; line-height: 1; letter-spacing: 0;
  color: var(--text-primary); overflow: hidden;
  /* Fit by whichever runs out first, the width or the height. Character cells
     are about 0.62em wide, and line-height is 1, so rows map to em directly. */
  /* Defaults keep the thumbnail readable before hub.js sets the real values. */
  font-size: min(
    calc(100cqw / (var(--cols, 80) * 0.62)),
    calc(100cqh / var(--rows, 24))
  );
}
@supports not (container-type: size) {
  .hub-card-thumb .hub-thumb-art { font-size: 3.4px; }
}
.hub-card-body { padding: 0.85rem 1rem 1rem; display: flex; flex-direction: column; gap: 0.35rem; }
.hub-card-title {
  color: var(--text-primary); text-decoration: none; font-weight: 600; font-size: 0.95rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hub-card:hover .hub-card-title { color: var(--accent); }
.hub-card-by { color: var(--text-secondary); font-size: 0.85rem; text-decoration: none; }
.hub-card-by:hover { color: var(--accent); }
.hub-card-stats {
  display: flex; gap: 1rem; flex-wrap: wrap;
  color: var(--text-secondary); font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ art ---
   Fidelity rules. Wrapped ASCII is destroyed ASCII, and block characters must
   tile with no seams, so this is the one place a webfont is refused. */
.hub-art {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
  white-space: pre; line-height: 1; letter-spacing: 0;
  font-variant-ligatures: none; font-feature-settings: "liga" 0, "calt" 0;
  tab-size: 1; overflow-x: auto; overflow-y: hidden; margin: 0;
  color: var(--text-primary);
}
.hub-art-frame {
  border: 1px solid var(--border); border-radius: 12px;
  background-color: var(--bg-secondary); padding: 1.25rem;
  transition: background-color 0.12s linear, color 0.12s linear;
}
.hub-art-frame.is-flash { background-color: var(--accent); }
.hub-art-frame.is-flash .hub-art { color: #fff; }
@media (prefers-reduced-motion: reduce) {
  .hub-art-frame, .hub-art { transition: none; }
  .hub-art-frame.is-flash { background-color: var(--bg-secondary); border-color: var(--accent); }
  .hub-art-frame.is-flash .hub-art { color: var(--text-primary); }
}
.hub-art-fit .hub-art { font-size: var(--fit-size, 10px); }
.hub-art-actual .hub-art { font-size: 13px; }

.hub-size-toggle { display: inline-flex; gap: 0.25rem; }
.hub-size-toggle button {
  width: auto; margin: 0; padding: 0.35rem 0.75rem; font-size: 0.8rem;
  background-color: transparent; color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 6px; font-weight: 500;
}
.hub-size-toggle button[aria-pressed="true"] {
  background-color: var(--accent); color: #fff; border-color: var(--accent);
}

/* -------------------------------------------------------------- buttons ---
   Selectors are written as `a.hub-btn` / `button.hub-btn` on purpose. The site
   stylesheet carries `a:visited { color: #8b8cf8 }` at specificity 0,1,1,
   which outranks a bare `.hub-btn-primary` at 0,1,0 -- so a visited link
   button lost its text colour and went pale purple on a purple fill. Adding
   the element selector puts these at 0,1,1 or higher and settles it. */
.hub-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }

a.hub-btn, button.hub-btn, .hub-btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  width: auto; margin: 0; padding: 0.5rem 1rem;
  font-size: 0.85rem; font-weight: 500; font-family: inherit;
  border-radius: 6px; cursor: pointer; transition: all 0.2s;
  border: 1px solid var(--border); background-color: var(--bg-secondary);
  color: var(--text-primary); text-decoration: none;
}
a.hub-btn:visited { color: var(--text-primary); }

/* Hover fills the button instead of recolouring the label. Indigo text on the
   dark panel measured about 3.3:1, which is not readable; white on indigo is
   roughly 4.8:1 and the shape change reads better anyway. */
a.hub-btn:hover, button.hub-btn:hover, .hub-btn:hover {
  transform: translateY(-1px);
  background-color: var(--accent);
  border-color: var(--accent);
  color: #fff;
  text-decoration: none;
}
.hub-btn:active { transform: translateY(0); }
.hub-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hub-btn[disabled] { opacity: 0.5; cursor: not-allowed; transform: none; }
.hub-btn[disabled]:hover { background-color: var(--bg-secondary); border-color: var(--border); color: var(--text-primary); }

a.hub-btn-primary, button.hub-btn-primary, .hub-btn-primary,
a.hub-btn-primary:visited {
  background-color: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
a.hub-btn-primary:hover, button.hub-btn-primary:hover, .hub-btn-primary:hover {
  background-color: var(--accent-hover); border-color: var(--accent-hover); color: #fff;
}

a.hub-btn-quiet, button.hub-btn-quiet, .hub-btn-quiet,
a.hub-btn-quiet:visited {
  background-color: transparent; border-color: transparent; color: var(--text-secondary);
}
a.hub-btn-quiet:hover, button.hub-btn-quiet:hover, .hub-btn-quiet:hover {
  background-color: rgba(99,102,241,0.12);
  border-color: transparent;
  color: var(--text-primary);
}

a.hub-btn-danger, button.hub-btn-danger, .hub-btn-danger,
a.hub-btn-danger:visited { border-color: var(--danger); color: var(--danger); background-color: transparent; }
a.hub-btn-danger:hover, button.hub-btn-danger:hover, .hub-btn-danger:hover {
  background-color: var(--danger); border-color: var(--danger); color: #fff;
}

/* -------------------------------------------------------------- avatars ---
   Generated from the account, or from one of the person's own pieces. There
   are no avatar uploads anywhere in this product. */
.hub-avatar {
  font-family: "Roboto Mono", "Courier New", monospace;
  white-space: pre; line-height: 1; font-size: 9px; letter-spacing: 0;
  color: var(--accent); background-color: var(--bg-primary);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px; display: inline-block; vertical-align: middle;
}
.hub-avatar-lg { font-size: 14px; padding: 10px; border-radius: 12px; }

/* -------------------------------------------------------------- profile --- */
.hub-profile {
  display: flex; gap: 1.5rem; align-items: flex-start; flex-wrap: wrap;
  padding: 1.5rem; border: 1px solid var(--border); border-radius: 12px;
  background-color: var(--bg-secondary);
}
.hub-profile-id { flex: 1; min-width: 240px; }
.hub-profile-handle { font-size: 1.35rem; font-weight: 700; color: var(--text-primary); }
.hub-profile-name { color: var(--text-secondary); font-size: 0.95rem; }
.hub-profile-bio { color: var(--text-primary); margin: 0.5rem 0 0; max-width: 60ch; opacity: 0.85; }
.hub-stats { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-top: 0.75rem;
  color: var(--text-secondary); font-size: 0.9rem; }
.hub-stats b { color: var(--text-primary); font-variant-numeric: tabular-nums; }

.hub-chip {
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 0.15rem 0.5rem; border-radius: 4px; display: inline-block;
}
.hub-chip-live { color: var(--success-green); background-color: rgba(16,185,129,0.12); }
.hub-chip-pending { color: var(--warning); background-color: rgba(232,184,77,0.12); }
.hub-chip-held { color: var(--danger); background-color: rgba(232,115,77,0.12); }
.hub-chip-unlisted { color: var(--text-secondary); background-color: rgba(255,255,255,0.06); }
.hub-owner-row { display: flex; gap: 0.75rem; align-items: center; font-size: 0.8rem; }

/* ---------------------------------------------------------------- forms ---
   Matches the generator's own control styling. */
.hub-field { display: flex; flex-direction: column; gap: 0.5rem; max-width: 30rem; }
.hub-field > span { color: var(--text-secondary); font-size: 0.9rem; }
.hub-field input, .hub-field textarea, .hub-field select {
  width: 100%; padding: 0.75rem;
  background-color: rgba(255,255,255,0.05);
  border: 1px solid var(--border); border-radius: 6px;
  color: var(--text-primary); font-family: inherit; font-size: 0.95rem;
  transition: all 0.2s;
}
.hub-field textarea { resize: vertical; }
.hub-field input:focus, .hub-field textarea:focus, .hub-field select:focus {
  border-color: var(--accent); outline: none;
}
.hub-hint { font-size: 0.8rem; color: var(--text-secondary); }
.hub-hint.is-over { color: var(--danger); }
.hub-counter { font-variant-numeric: tabular-nums; }

.hub-note {
  padding: 0.85rem 1rem; border-radius: 6px; font-size: 0.9rem;
  border-left: 3px solid var(--border); background-color: var(--bg-secondary);
  color: var(--text-primary);
}
.hub-note-ok { border-left-color: var(--success-green); }
.hub-note-warn { border-left-color: var(--warning); }
.hub-note-err { border-left-color: var(--danger); }

/* ---------------------------------------------------------- empty state ---
   An invitation to act, never a shrug. */
.hub-empty {
  text-align: center; padding: 3rem 1.5rem;
  border: 1px dashed var(--border); border-radius: 12px;
  background-color: var(--bg-secondary);
}
.hub-empty h3 { margin: 0 0 0.5rem; color: var(--text-primary); font-size: 1.1rem; }
.hub-empty p { color: var(--text-secondary); max-width: 46ch; margin: 0 auto 1.25rem; }
.hub-empty .hub-empty-glyph {
  font-family: "Roboto Mono", "Courier New", monospace; white-space: pre;
  line-height: 1; color: var(--border); margin-bottom: 1rem; font-size: 0.8rem;
}

.hub-meta { display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;
  color: var(--text-secondary); font-size: 0.9rem; }
.hub-meta b { color: var(--text-primary); }
.hub-tag {
  color: var(--text-secondary); font-size: 0.8rem; text-decoration: none;
  border: 1px solid var(--border); border-radius: 4px; padding: 0.15rem 0.6rem;
  transition: all 0.2s;
}
.hub-tag:hover { color: var(--accent); border-color: var(--accent); }

.hub-remixes { border-left: 2px solid var(--border); padding-left: 1.25rem; }
.hub-pager { display: flex; gap: 1.5rem; justify-content: center;
  color: var(--text-secondary); font-size: 0.9rem; }
.hub-prose { max-width: 66ch; color: var(--text-primary); opacity: 0.9; }
.hub-prose p { margin: 0 0 1rem; }

.hub-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media (max-width: 768px) {
  .hub-grid, .hub-grid.is-sparse { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; }
  .hub-art-actual .hub-art { font-size: 11px; }
  .hub-profile { padding: 1rem; }
}

/* ------------------------------------------------- generator page strip ---
   The Hub's work surfaced on the homepage, directly under the generator, so
   the two halves of the site are actually connected rather than just
   cross-linked in the nav. */
.hub-strip { display: flex; flex-direction: column; gap: 0.5rem; }
.hub-strip[hidden] { display: none; }
.hub-strip-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 0.75rem; border-bottom: 1px solid var(--border);
}
.hub-strip-head h2 { margin: 0; font-size: 1.35rem; color: var(--text-primary); }
.hub-strip-all { color: var(--accent); text-decoration: none; font-size: 0.9rem; }
.hub-strip-all:hover { text-decoration: underline; }
.hub-strip-sub { color: var(--text-secondary); font-size: 0.95rem; margin: 0 0 1rem; }

/* Compact segmented control, same pill-toggle language as the generator's
   .mode-toggle -- a distinct class rather than reusing that one directly,
   since it's a different concept (sort order, not input mode) sitting in a
   different flex context (inline in a space-between header row). */
.hub-sort-toggle {
  display: flex;
  gap: 0.25rem;
  background-color: rgba(255, 255, 255, 0.03);
  padding: 0.2rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  flex: 0 0 auto;
}
.hub-sort-btn {
  min-height: 32px;
  min-width: 44px;
  padding: 0.25rem 0.75rem;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.hub-sort-btn:hover { color: var(--text-primary); }
.hub-sort-btn[aria-pressed="true"] {
  background-color: var(--accent);
  color: #fff;
}

/* ------------------------------------------------------------- rhythm ---
   Spacing is the layout's job, not a margin hand-placed on each element.
   Sections and forms are flex columns with a gap, so anything dropped into
   them is spaced correctly without the caller thinking about it. This is what
   was producing the cramped notices and the art sitting flush against its
   metadata. */
.hub-main > section,
.hub-main > form {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.hub-main form { display: flex; flex-direction: column; gap: 1.25rem; }

/* A flex column stretches its children, which would make a lone button span
   the page. Buttons and tags size to their content instead. */
.hub-main > section > .hub-btn,
.hub-main form > .hub-btn { align-self: flex-start; }

/* The heading already carries its own rule, so it needs no gap beneath it on
   top of the section gap. */
.hub-head { margin: 0; }

/* Standalone notices outside a flex parent still need air around them. */
.hub-note { margin: 0; }
.hub-main > .hub-note { margin: 0.5rem 0; }
.hub-note + .hub-note { margin-top: 0.75rem; }

/* Prose blocks inside a gapped column shouldn't add their own trailing space. */
.hub-prose > :first-child { margin-top: 0; }
.hub-prose > :last-child { margin-bottom: 0; }
.hub-profile-bio { margin-bottom: 0; }

/* The art and its metadata are separate rows of the section gap, so the
   metadata no longer sits flush against the frame. */
.hub-art-frame + .hub-actions,
.hub-art-frame + .hub-meta { margin-top: 0; }

/* Review queue entries are their own stacked cards. */
.hub-review { display: flex; flex-direction: column; gap: 0.75rem; }
.hub-review + .hub-review { margin-top: 1.5rem; }
.hub-report-row { display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; }
.hub-report-row + .hub-report-row { margin-top: 0.75rem; }

/* A closed account: the art stays, the person does not. */
.is-removed { color: var(--text-secondary); font-style: italic; }

/* Agreement checkboxes. The label is the hit target, not just the box. */
.hub-check {
  display: flex; align-items: flex-start; gap: 0.6rem;
  max-width: 44rem; cursor: pointer; color: var(--text-primary);
  font-size: 0.95rem; line-height: 1.5;
}
.hub-check input { width: 1rem; height: 1rem; margin-top: 0.25rem; flex: none; accent-color: var(--accent); }
.hub-check a { color: var(--accent); }


/* Unconfirmed account. This sits above the content on every hub page because
   the alternative, which shipped first, was a person posting and never being
   told their work was invisible to everyone else. */
.hub-unverified {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 0.9rem 1.25rem;
  border: 1px solid var(--warning);
  border-left-width: 3px;
  border-radius: 6px;
  background-color: rgba(232, 184, 77, 0.08);
  color: var(--text-primary);
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}
.hub-unverified strong { display: block; margin-bottom: 0.15rem; }
.hub-unverified span { color: var(--text-secondary); }
.hub-unverified form { margin: 0; }

/* Held posts in the review queue: small enough to scan, clipped so one long
   piece does not push the rest off the screen. Was an inline style attribute,
   which the CSP correctly refused. */
.hub-art-thumb { font-size: 7px; max-height: 220px; overflow: hidden; }

/* ---------------------------------------------------- save-to-list picker ---
   One shared panel (hub.js), moved next to whichever Save button was
   clicked -- the post page's or a feed card's. Reuses .hub-field, .hub-btn
   and .hub-hint; no new visual language for this. */
.hub-save-picker {
  display: flex; flex-direction: column; gap: 0.85rem;
  margin-top: 0.75rem; padding: 1rem;
  border: 1px solid var(--border); border-radius: 6px;
  background-color: var(--bg-secondary);
  max-width: 26rem;
}
.hub-save-picker[hidden] { display: none; }
.hub-save-picker .hub-field { max-width: none; }

/* The Save button hub.js adds to every feed card, sized down to fit the
   card body next to the existing stats row rather than the full-size
   .hub-btn used on the post page. */
.hub-card-save { font-size: 0.78rem; padding: 0.35rem 0.65rem; }
.hub-card-body > .hub-actions { margin-top: 0.15rem; }
