html, body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
}

.frame-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
  /* Stays put when body.frame-contained below makes the page itself
     scroll (video-embed mode's contained layout). No-op otherwise — a
     sticky element in a non-scrolling container just behaves like a
     static one, so this is harmless in every other mode. */
  position: sticky;
  top: 0;
  z-index: 2;
}

.frame-brand {
  font-weight: 700;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  font-size: 14px;
  margin-right: 4px;
}

.frame-toolbar button {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: 8px;
  width: 34px;
  height: 34px;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  color: var(--text);
  flex: 0 0 auto;
}

.frame-toolbar button:hover {
  border-color: var(--accent);
}

.frame-address {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
}

/* Without this, a narrow window can squeeze a .btn (Go, "Full frame"/
   "Contained", Random, "Open in a tab") below its own content's width,
   wrapping its label onto two lines. .frame-address's own flex:1 1 auto +
   min-width:0 above already makes it the one thing in this row meant to
   give up space first, so pinning every .btn to its natural width is what
   makes that actually happen instead of the row shrinking everything
   evenly.
   width/height:auto are the actual fix, not flex/white-space — #widthToggleBtn
   is a real <button> element, so `.frame-toolbar button` above (meant for
   the plain icon buttons: back/forward/reload) ALSO matches it and forces
   a fixed 34x34px box; nothing in `.btn` itself (styles.css) ever
   contested that property, so its text got squeezed into a box far
   narrower than "⛶ Full frame" needs regardless of flex-shrink/white-space.
   `.frame-toolbar .btn` (two classes) outranks `.frame-toolbar button`
   (class+type) in specificity, so explicitly resetting width/height here
   is what actually wins back the auto, content-based sizing .btn intends. */
.frame-toolbar .btn {
  width: auto;
  height: auto;
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Mobile — this toolbar packs a brand link, 3 icon buttons, an address
   bar, and up to 4 more buttons/links into one row, which simply doesn't
   fit a phone-width screen; below this breakpoint it reflows onto three
   rows instead of overflowing (nothing shows/hides — everything present on
   desktop is still here, just wrapped):
     1. brand + back/forward/reload (the default flex order — nothing
        below sets order on these, so they render first, same relative
        order as desktop)
     2. the address bar, forced onto its own full-width row (flex-basis
        100% only breaks the row when the item AFTER it also moves past
        that break — see order:2 below)
     3. Go, the width toggle (video mode only), the mode indicator, Random,
        and "Open in a tab" — order:2 puts all of these after the address
        bar's forced break, and they wrap among themselves as needed rather
        than needing their own hand-placed breakpoint.
   `order` on more elements than strictly needed (Go/widthToggleBtn/
   frame-mode/randomLink/openNewTab all get the same order:2) is
   deliberate — a flat "these things come after the address bar" group is
   far less fragile to reason about or extend later than pinning each one
   to its own explicit row. */
@media (max-width: 640px) {
  .frame-toolbar {
    flex-wrap: wrap;
    row-gap: 8px;
  }

  .frame-address {
    order: 1;
    flex-basis: 100%;
  }

  #goBtn,
  #widthToggleBtn,
  .frame-mode,
  #randomLink,
  #openNewTab,
  #reportBtn {
    order: 2;
  }
}

.frame-address:focus {
  outline: none;
  border-color: var(--accent);
}

.frame-mode {
  font-size: 12px;
  color: var(--text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}

.frame-hint {
  padding: 6px 16px;
  margin: 0;
  font-size: 12.5px;
  color: var(--text-muted);
  background: var(--accent-soft);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.frame-body {
  border: none;
  width: 100%;
  flex: 1 1 auto;
}

/* Video-embed mode's default layout (frame.js's applyContainedMode) — a
   reasonably-sized, centered player instead of the full-bleed frame every
   other mode uses, with room below it for #videoCard. Toggled off by the
   toolbar's "Full frame" button, which reverts to the plain .frame-body
   rules above — the exact full-bleed behavior this always had. */
.frame-body.contained {
  flex: 0 0 auto;
  width: 100%;
  max-width: 900px;
  aspect-ratio: 16 / 9;
  margin: 20px auto 0;
  border-radius: 10px;
  box-shadow: 0 2px 16px rgba(0, 0, 0, 0.18);
  background: #000;
}

/* Only present while video-embed mode's contained layout is active (see
   above) — html/body's own height:100% + no-scroll is exactly right for
   every other mode (a full-bleed iframe that already fills the viewport),
   but here the page has real content below the fold (.videoCard), so the
   document needs to scroll instead of clipping it. */
body.frame-contained {
  height: auto;
  min-height: 100%;
  overflow-y: auto;
}

.frame-video-card {
  scroll-margin-top: 64px; /* clears the sticky toolbar if ever anchored to directly */
}

/* Views | Bookmarks | vote widget, directly under the contained player —
   see frame.js's buildVideoStatsBarHtml(). Same 900px column as the
   .contained player above it, narrower than .bm-card's own 640px below
   (browse.css) on purpose: it reads as an extension of the player, not
   part of the description card. */
.video-stats-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 18px;
  max-width: 900px;
  margin: 12px auto 0;
  padding: 10px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13.5px;
  color: var(--text-muted);
}

/* Same cascade gotcha as .row-thumb-placeholder[hidden]/.bm-image-placeholder[hidden]
   elsewhere in this app — .video-stats-bar's own `display: flex` above is
   author-origin and beats the browser's UA-origin `[hidden]{display:none}`
   at equal specificity, so without this override setting `hidden` from
   frame.js wouldn't actually hide it. */
.video-stats-bar[hidden] {
  display: none;
}

.video-stat {
  white-space: nowrap;
}

.video-stat strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* .vote-widget's own layout (browse.css) already reads fine inline here —
   just drop its default left margin so it sits flush with the other two
   stats instead of looking like a separate trailing block. */
.video-stats-bar .vote-widget {
  margin: 0;
}

/* "🔖 Bookmark" action — see frame.js's buildVideoBookmarkHtml(). The one
   item in this bar with margin-left:auto, so it sits pinned to the far
   right regardless of how many of the stats/vote-widget before it are
   present, the same "stats on the left, action on the right" shape as
   YouTube's own row this bar is already modeled on (frame.js's own
   comment). Shared base look across all three states (link, button, and
   the done span) — a small pill matching .tag-toggle's weight elsewhere in
   this app rather than a full .btn, since this sits inline among plain
   text stats, not in the toolbar. */
.video-bookmark-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}

.video-bookmark-btn:hover {
  border-color: var(--accent);
}

.video-bookmark-btn:disabled {
  opacity: 0.7;
  cursor: default;
}

/* The static "already saved" state (a <span>, not a button/link — nothing
   to click) — same "not the viewer's own copy, but does own SOME bookmark
   of this URL" meaning as viewerOwnsUrl everywhere else in this app (see
   app.js's wireVoteWidget), just surfaced here as its own affirmative state
   instead of a disabled control. */
.video-bookmark-btn-done {
  color: var(--text-muted);
  background: var(--accent-soft);
  cursor: default;
}

.video-bookmark-btn-done:hover {
  border-color: var(--border);
}

/* Video-embed mode's flag/report control — see frame.js's
   buildVideoStatsBarHtml()/wireVideoReportButton(). Same pill shape and
   sizing as .video-bookmark-btn (it sits right next to it in this bar), but
   deliberately NOT given that class's margin-left:auto — this one stays in
   normal flow, immediately before whichever bookmark-action state renders,
   which keeps that one pinned to the far right exactly as before. */
.video-report-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.video-report-btn:hover {
  border-color: var(--accent);
}

/* ---- Report modal (public/report.js) — this app's first modal/dialog, so
   nothing here reuses an existing pattern. Built fresh into <body> each
   time openReportModal() runs and removed on close, so none of this needs
   to be hidden/shown via [hidden] the way the rest of this app's toggled
   elements are — see report.js's own comment on why. */
.report-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.report-modal {
  position: relative;
  width: 100%;
  max-width: 440px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
}

.report-modal h2 {
  margin: 0 0 16px;
  font-size: 18px;
  padding-right: 24px; /* clears the close button in the corner */
}

.report-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  border: none;
  background: none;
  font-size: 20px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px;
}

.report-modal-close:hover {
  color: var(--text);
}

.report-modal-field {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  margin-bottom: 14px;
}

.report-modal-field select,
.report-modal-field textarea {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 400;
  box-sizing: border-box;
  resize: vertical;
}

.report-modal-field select:focus,
.report-modal-field textarea:focus {
  outline: none;
  border-color: var(--accent);
}

/* Shown only when "Involves a minor" is the selected reason — see
   report.js's own comment on why this points straight to CyberTipline
   rather than asking for more detail in the note field below it. */
.report-modal-notice {
  font-size: 12.5px;
  margin: -6px 0 14px;
  padding: 10px 12px;
  background: #fdeaea;
  border: 1px dashed #c62828;
  border-radius: 8px;
  color: var(--text);
}

.report-modal-error {
  font-size: 13px;
  color: #c62828;
  margin: -6px 0 14px;
}

.report-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.report-modal-success {
  text-align: center;
}

.report-modal-success p {
  margin: 4px 0 18px;
}

/* ---- related-content grid (public/related.js) — video-embed mode only,
   below the comment thread. Same 640px column as .frame-video-card/
   .comments-section above, and the same ":not(:empty)" "take up no space
   until there's something to show" treatment as #adSlot — see
   initRelatedGrid()'s own comment for why it leaves the container
   completely empty (no heading either) rather than an empty-state message,
   unlike comments' "No comments yet." */
.related-grid {
  max-width: 640px;
  margin: 0 auto;
}

.related-grid:not(:empty) {
  margin-bottom: 24px;
}

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

/* Same auto-fill/minmax approach as browse.css's .list.grid-view, just a
   narrower minimum (140px vs 220px) — this grid sits inside a 640px column
   rather than the full page width, and its cards carry far less
   information (a thumbnail and one line of title, no vote widget/tags/
   description), so more, smaller cards per row reads better here than
   reusing that exact sizing would. */
.related-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 14px;
}

.related-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: inherit;
  text-decoration: none;
}

.related-thumb-wrap {
  position: relative;
  display: block;
}

.related-thumb {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
}

.related-video-badge {
  position: absolute;
  right: 6px;
  bottom: 6px;
  font-size: 12px;
  line-height: 1;
  padding: 4px 5px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.65);
}

.related-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.related-card:hover .related-title {
  color: var(--accent);
}

/* Mobile — these boxed sections (the video card, ad slot, comments,
   related grid) all default to filling 100% of the body's width with no
   side padding of their own (unlike /bookmark/:id's equivalent content,
   which sits inside styles.css's `.container` and its 20px side padding —
   see lib/publicPages.js). That's invisible on desktop, where each one's
   own max-width already leaves room for margin:auto to center a visible
   gutter either side — but below that max-width (any phone-width screen),
   the auto margins collapse to 0 and the content runs flush to the screen
   edges instead. `width: calc(100% - 40px)` reserves the same 20px gutter
   on each side .container already gives /bookmark/:id's version of these
   same sections, so the two pages read as the same width on a phone, not
   just the same classes — without touching each element's own max-width
   (desktop's 640px/900px split between the narrower cards and the wider
   stats bar is untouched — deliberate, see .video-stats-bar's own comment
   above). Scoped to this file (by id, not by the shared classes these
   elements also carry, e.g. .bm-card/.comments-section) so it can't affect
   /bookmark/:id, which already has its own correct gutter via `.container`
   and doesn't need this at all. */
@media (max-width: 640px) {
  #videoCard,
  #adSlot,
  #commentsSection,
  #videoStatsBar,
  #relatedGrid {
    width: calc(100% - 40px);
  }
}
