/* Styles specific to the public pages (/browse, /tag/NAME, /bookmark/:id).
   Everything else — .toolbar, .count, .list, .row, .row-title-line,
   .row-url-line, .row-favicon, .row-sitename, .row-description, .row-thumb,
   .empty, .btn, .tag-pill, .tag-banner — comes from styles.css, which the
   dashboard already established these read-only rows (and its own tag
   filter banner) are visually a variant of. */

.row-tags-line {
  margin-top: 6px;
}

/* ---- ad slots (public/ads.js) ---- */

/* Shared by every page with an ad slot — originally just #adSlot on
   /view/:id's video-embed mode (frame.html/frame.js), moved here now that
   /browse (and /tag/NAME, /:username), /tags, and the bookmark permalink
   page each got one of their own (lib/adSlots.js's browse_top/tags_top/
   bookmark_top) — all of them already load this file, so one shared rule
   covers every placement instead of repeating it per page. No border/
   background/min-height of its own on purpose: whatever the ad network's
   embed code renders supplies its own look, and an empty, unconfigured slot
   (the common case until an admin sets one up — see /admin) should take up
   no visible space at all rather than showing as a blank box. */
.ad-slot {
  /* Explicit width, not just max-width — on frame.html specifically, body
     is `display: flex; flex-direction: column` (see frame.css), and a flex
     item with an auto cross-axis margin (the "margin: ... auto" centering
     trick below) doesn't stretch to fill the container the way a normal
     block box would; it shrink-wraps to its own content instead, which
     silently broke every section using this exact pattern in video-embed
     mode (this ad slot, .bm-card/#videoCard, .comments-section, and
     frame.css's .video-stats-bar/.related-grid) — a comment box rendering
     at ~200px instead of the intended 640px column was the visible
     symptom that led here. width: 100% (capped by max-width, then
     centered by the auto margins) is a no-op everywhere else that loads
     this file, since normal block layout already computes width:auto the
     same way — it only matters inside this flex context. */
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  /* margin:auto above only ever centers the ad-slot BOX itself — the ad
     network's own markup rendered inside it (an <ins>, an <iframe>, a
     <div> the network's script builds) doesn't necessarily fill that box's
     full width, and needs its own centering. `text-align: center` was the
     first thing tried here, but it only affects INLINE content — most real
     ad markup (an <ins> with a script-assigned fixed pixel width, or any
     <div>/<iframe> a network's own JS ends up rendering, both block-level
     by default) sat flush left regardless, confirmed by actually testing a
     saved embed rather than assuming. Flex + align-items:center centers a
     child regardless of whether that child turns out to be inline or
     block, which is what actually fixes it for arbitrary third-party
     markup instead of only the inline case. */
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ad-slot:not(:empty) {
  margin-bottom: 24px;
}

/* The bookmark permalink page's own #adSlotTop only (lib/publicPages.js) —
   see the comment right above that element for why it, alone among this
   class's three placements, needs a top margin of its own. Also
   :not(:empty)-gated, same reasoning as the bottom margin above: an empty,
   unconfigured slot should take up no visible space at all, margin
   included, not leave a gap where an ad would go. */
.ad-slot-page-top:not(:empty) {
  margin-top: 20px;
}

/* Most ad networks hand back a fixed-pixel-width iframe/ins/img in their
   embed code (a 300x250 box, a 728x90 leaderboard, …) — fine on desktop,
   where .ad-slot's own 640px column is comfortably wider than any standard
   ad unit, but a leaderboard-width creative is wider than a phone's own
   viewport, and without this it forces the WHOLE PAGE to scroll
   horizontally to show it, not just the ad. This doesn't make the ad's
   actual creative reflow (most ad networks don't support that, and
   stretching a fixed-size creative's height to match a scaled-down width
   would just distort it) — it caps whatever the embed renders at the
   slot's own width and lets the browser scale it down, which is enough to
   stop it from breaking the rest of the page's layout on a small screen. */
.ad-slot iframe,
.ad-slot img,
.ad-slot ins {
  max-width: 100%;
  height: auto;
}

/* ---- tag pill expand/collapse (public/browse.js's buildTagsHtml) ---- */

/* .tags-extra is a plain inline wrapper around the overflow .tag-pill
   links — no layout rules of its own needed, its children already flow
   exactly like the always-visible pills before it; [hidden] (the native
   HTML attribute, toggled by initTagToggles) is what actually shows/hides
   it. */
.tag-toggle {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 999px;
  padding: 3px 10px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  margin: 2px 4px 2px 0;
  vertical-align: middle;
}

.tag-toggle:hover {
  background: var(--accent-soft);
  color: var(--text);
  border-color: var(--accent-soft);
}

.row-meta {
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-muted);
}

/* ---- thumbnail placeholder ---- */

/* Shown in a bookmark's .row-thumb slot instead of a real <img> whenever it
   has no captured image_url, or a captured one fails to load in the
   browser — public/browse.js's thumbPlaceholderHtml() and
   lib/publicPages.js's own copy for the permalink page. Same .row-thumb
   class as a real image, so it automatically picks up identical
   sizing/positioning in both list view (72px square, styles.css) and grid
   view (full-width bled card header, this file's .list.grid-view
   .row-thumb) — this rule only adds the placeholder's own look on top. */
.row-thumb-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--text-muted);
}

/* This element's own `display: flex` above is an author style, which wins
   the cascade over the browser's default `[hidden]{display:none}` (a
   user-agent style) at equal specificity — so without this rule, setting
   the `hidden` attribute (as the broken-image fallback in
   public/browse.js's renderList does) would do nothing and the
   placeholder would stay visibly flexed even while hidden. */
.row-thumb-placeholder[hidden] {
  display: none;
}

.row-thumb-placeholder svg {
  width: 40%;
  height: 40%;
  max-width: 32px;
  max-height: 32px;
}

/* ---- profile follow bar (/:username only) ---- */

.profile-meta {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}

/* Wraps memberSince + profileCounts so they stack as their own two-line
   column (X.com-style: join date above, follower/following counts below)
   instead of competing with the Follow button for the same row. */
.profile-meta-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.profile-counts {
  font-size: 13px;
  color: var(--text-muted);
}

.profile-joined {
  font-size: 13px;
  color: var(--text-muted);
}

.profile-joined:empty {
  display: none;
}

/* Unfollowed state reuses .btn-primary (solid accent) so it reads as the
   inviting default action; the "following" modifier below flips that to an
   outline once clicked, the common social-app convention for "you're
   already doing this, click to undo" rather than looking like a second
   equally-weighted call to action. */
#followBtn.following {
  background: none;
  border: 1px solid var(--border);
  color: var(--text);
}

/* ---- sort toggle (Newest / Top / Following) ---- */

.sort-toggle {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}

.sort-btn {
  padding: 9px 14px;
  font-size: 13px;
}

.sort-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* ---- List/Grid layout toggle ---- */

/* Same active-state look as .sort-btn above, just narrower — these two
   buttons are icons, not words, so they don't need .sort-btn's wider
   horizontal padding. */
.view-toggle {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}

.view-toggle-btn {
  padding: 9px 12px;
  font-size: 14px;
  line-height: 1;
}

.view-toggle-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}

/* Grid view reuses the exact same .row markup list view renders
   (browse.js's renderList never branches on view mode) — everything below
   is pure reflow. flex `order` moves the thumbnail from its list-view spot
   on the right to the top of the card, and drops the vote widget out of
   the left margin into its own row underneath the text. A card with no
   thumbnail (item.image_url unset) just renders shorter, the same as a
   thumbnail-less row already does in list view. */
.list.grid-view {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

.list.grid-view .row {
  flex-direction: column;
  align-items: stretch;
}

/* Bleeds the thumbnail to the card's edges using the row's own padding
   (12px 14px, from styles.css's .row) as the bleed amount, so it reads as
   a real image card header rather than an inset picture. */
.list.grid-view .row-thumb {
  order: -1;
  width: calc(100% + 28px);
  height: 180px;
  margin: -12px -14px 10px;
  border-radius: 10px 10px 0 0;
  border: none;
}

.list.grid-view .row-main {
  order: 1;
}

.list.grid-view .vote-widget {
  order: 2;
  flex-direction: row;
  justify-content: flex-start;
  gap: 8px;
  margin-top: 10px;
}

/* ---- up/down vote widget ---- */

/* Shown wherever a public bookmark is listed — /browse, /tag/:name,
   /:username row lists (public/browse.js) and the /bookmark/:id permalink
   page (lib/publicPages.js's renderVoteWidget, with the .horizontal
   modifier below). Not shown on the dashboard — voting is a public/social
   action on someone ELSE's content; you can't vote on your own bookmark
   anyway (routes/votes.js rejects it, and app.js hides the widget for it).
   Colors are fixed hex, not var(--accent) — --accent flips to a pink/red in
   dark mode (see styles.css's :root[data-theme="dark"]), which would read
   oddly next to a red downvote, the same reason .row-delete's hover
   (styles.css) uses a fixed red rather than the theme accent. */
.vote-widget {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}

.vote-widget.horizontal {
  flex-direction: row;
  gap: 8px;
  margin-bottom: 14px;
}

.vote-btn {
  background: none;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1;
  border-radius: 4px;
  font-family: inherit;
}

.vote-btn:hover:not(:disabled) {
  background: var(--accent-soft);
}

.vote-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.vote-up.vote-active {
  color: #2e7d32;
}

.vote-down.vote-active {
  color: #c62828;
}

.vote-score {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  min-width: 1.4em;
  text-align: center;
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin: 24px 0 8px;
}

.pagination button {
  cursor: pointer;
}

.pagination button:disabled {
  opacity: 0.4;
  cursor: default;
}

#pageInfo {
  font-size: 13px;
  color: var(--text-muted);
}

/* ---- tag directory (/tags) ---- */

/* CSS multi-column rather than a grid/flex-wrap — the tag list is a single
   alphabetical sequence (not a set of independent cards), and columns keep
   it reading top-to-bottom-then-across, like a phone book or an index,
   which is the order someone scanning for a specific letter expects.
   column-width (not column-count) lets the browser pick however many
   columns actually fit at the current window size — 2 on a narrow phone,
   4+ on a wide desktop — rather than a fixed count that's cramped or wastes
   space. */
.tag-index {
  column-width: 200px;
  column-gap: 28px;
}

.tag-index-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: var(--text);
  font-size: 13.5px;
  /* Keeps one tag's name+count together rather than splitting across the
     column break — the single biggest thing that makes a CSS-columns list
     look broken if left to the default. */
  break-inside: avoid;
}

.tag-index-item:hover .tag-index-name {
  text-decoration: underline;
}

.tag-index-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.tag-index-count {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* ---- bookmark permalink page (/bookmark/:id) ---- */

/* Same 640px column and centering as .bm-card below, so the two read as
   stacked rows rather than a toolbar off on its own — "Back to browse" on
   the left (where you came from), "Random" on the right (where to go
   next), both above the fold regardless of how tall the card's own
   thumbnail/description/tags turn out to be. */
.bm-topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 640px;
  margin: 16px auto 0;
}

/* .tag-banner is shared with /tag/NAME (browse.html), which is full-width
   within its own container — scoped to just after .bm-topbar so it picks
   up this page's narrower 640px column instead of widening unexpectedly. */
.bm-topbar + .tag-banner {
  max-width: 640px;
  margin: 12px auto 0;
}

.bm-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  width: 100%; /* see .ad-slot's comment above — same flex-shrink-wrap fix, needed here too since #videoCard reuses this class. Safe with padding: styles.css's global `* { box-sizing: border-box }` already applies here. */
  margin: 16px auto 24px;
  max-width: 640px;
}

.bm-image {
  width: 100%;
  max-height: 320px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin-bottom: 16px;
}

/* No real max-height to derive from (the placeholder isn't an <img>, so it
   has no natural aspect ratio) — a fixed height stands in for one, smaller
   than .bm-image's own max-height since a big empty icon reads as more
   "broken" than a smaller, quieter one would. */
.bm-image-placeholder {
  height: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  color: var(--text-muted);
}

/* Same cascade gotcha and fix as .row-thumb-placeholder[hidden] above —
   this element's own `display: flex` would otherwise beat the browser's
   default `[hidden]{display:none}` and defeat the broken-image fallback
   toggle in lib/publicPages.js's renderBookmarkPage. */
.bm-image-placeholder[hidden] {
  display: none;
}

.bm-image-placeholder svg {
  width: 48px;
  height: 48px;
}

.bm-title {
  font-size: 22px;
  margin: 0 0 10px;
}

.bm-url-line {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}

.bm-favicon {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  flex: 0 0 auto;
}

/* The bookmark permalink page's one deliberate way to open a new tab — a
   real link straight to the external page (see lib/publicPages.js's own
   comment on thumb, just above where this is used, for the full picture of
   why only this one thing on that page opens a new tab). Accent-colored
   like any other link, since it is one there. */
.bm-url {
  color: var(--accent);
  font-size: 13.5px;
  word-break: break-all;
}

/* The toolbar viewer's own copy of this same card (public/frame.js's
   buildVideoCardHtml) renders bm-url as plain text, not a link — that
   page's one new-tab opener is its own "Open in a tab" button up in the
   toolbar, not a second link buried in this card. Overridden back to muted,
   non-link styling specifically inside #videoCard so it doesn't read as
   clickable there, without touching the real link .bm-url still is on the
   permalink page above. */
#videoCard .bm-url {
  color: var(--text-muted);
}

.bm-sitename {
  font-size: 12px;
  color: var(--text-muted);
  background: var(--accent-soft);
  border-radius: 6px;
  padding: 2px 8px;
}

.bm-description {
  font-size: 14.5px;
  color: var(--text);
  line-height: 1.5;
  margin: 0 0 14px;
}

.bm-tags {
  margin-bottom: 14px;
}

.bm-meta {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 10px;
}

.bm-notfound {
  text-align: center;
  padding: 48px 24px;
}

.bm-notfound h1 {
  font-size: 20px;
  margin: 0 0 8px;
}

.bm-notfound p {
  color: var(--text-muted);
  margin: 0 0 20px;
}

/* ---- comments (public/comments.js) — the bookmark permalink page
   (.comments-section sits right after .bm-card, both server-rendered, see
   lib/publicPages.js) and the toolbar viewer's video-embed mode
   (.comments-section is built client-side after #videoCard, see frame.js).
   Same 640px column as .bm-card above in both places, so it reads as a
   continuation of the card rather than a separate, differently-proportioned
   block. */
.comments-section {
  width: 100%; /* see .ad-slot's comment above — same flex-shrink-wrap fix */
  max-width: 640px;
  margin: 0 auto 24px;
}

.comments-heading {
  font-size: 16px;
  margin: 0 0 12px;
}

.comments-loading {
  color: var(--text-muted);
  font-size: 13px;
}

.comments-login-prompt {
  color: var(--text-muted);
  font-size: 13.5px;
  margin: 0 0 16px;
}

.comment-form {
  margin-bottom: 20px;
}

.comment-input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  resize: vertical;
}

.comment-input:focus {
  outline: none;
  border-color: var(--accent);
}

.comment-form-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}

.comment-form-error {
  color: #d33;
  font-size: 12.5px;
}

.comment-row {
  padding: 12px 0;
  border-top: 1px solid var(--border);
}

.comment-row:first-child {
  border-top: none;
  padding-top: 0;
}

.comment-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-bottom: 4px;
}

.comment-author {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

.comment-author:hover {
  text-decoration: underline;
}

.comment-date {
  font-size: 12px;
  color: var(--text-muted);
}

.comment-delete {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 2px 4px;
}

.comment-delete:hover {
  color: #d33;
}

.comment-body {
  font-size: 14px;
  color: var(--text);
  line-height: 1.5;
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}
