/* assets/feed.css -- THE FRONT DOOR'S OWN COMPOSITION.
   feed.php carried this as one inline <style> (12.4 KB) until 2026-09-09
   (C6 Feed batch, step 1). Moved byte for byte, in the same cascade slot:
   after site.css, the theme, feed-card.css and composer.css, exactly where
   the inline block sat. The card's rules are NOT here -- they crossed to
   feed-card.css on 2026-07-28 so the card could render on My Signal; this
   file is only what the feed PAGE places around the card: the layout, the
   rail, the search popover, the world line, the favourite star, the chips,
   the login prompt, the new-posts pill and the loader. Every comment
   travels with its rule. */
:root { --body: 'Sora', sans-serif; }
html, body { overflow-x: hidden; }
.feed-layout { --edge-pad: 20px; max-width: 600px; margin: 0 auto; padding: var(--page-top) var(--edge-pad) 80px; position: relative; z-index: 1; width: 100%; box-sizing: border-box; }
/* Content fluid, sidebar fixed. Every extra pixel goes to the thing that
   benefits from it -- the sidebar holds a search field, pills and three
   stat rows, all of which have a natural size, so scaling it would only
   manufacture whitespace. Was a frozen 640px + 300px inside max-width
   1000px, which left ~460px of dead margin per side at 1920 and made
   1440 and 1920 render identically. Now ramps ~630px at 1024 up to
   908px at 1440+, in one line rather than new breakpoints. */
/* Single content column. The sidebar is GONE -- no grid slot, no column,
   no panel -- so this is a plain centred block at every breakpoint. The
   max-width reserves at least 200px of viewport for the margin rail so
   the two floating icons always have somewhere to sit: at 1024 that
   leaves ~100px per side, at 1920 it leaves 460. */
@media (min-width: 1024px) { .feed-layout { max-width: min(1000px, calc(100vw - 200px)); } }
.feed-main { min-width: 0; }
/* Two quiet controls in the margin beside the content -- not a sidebar,
   not a panel. Anchored to the content edge with left:100% so they track
   the column at any width instead of being pinned to the viewport.
   Below 1024 there IS no margin, so they drop into the flow: the star
   goes top-right above the composer and the search button hides, because
   search is already served there by the mobile filter bar's magnifier. */
/* top aligns with the top edge of the composer, not the game label above
   it: these are utility controls for the feed content, and the content
   starts at the first interactive element. Derived, not eyeballed --
   140px layout padding + 20px strip height + 14px strip margin = 174.
   Written as a calc so anyone changing the strip can see what depends
   on it. */
.feed-rail { position: absolute; left: 100%; top: calc(140px + 20px + 14px); margin-left: 20px; display: flex; flex-direction: column; gap: 10px; z-index: 5; }
.feed-rail-btn:hover, .feed-rail-btn:focus-visible { color: var(--star); border-color: var(--ion); }
.feed-rail-btn[aria-expanded="true"] { color: var(--star); border-color: var(--ion); background: rgba(var(--ion-rgb), .12); }
/* A popover anchored to the search icon, NOT a bar in the flow. It is
   positioned out of the icon's own stacking context and overlays the
   content column, so the feed underneath never moves or reflows when it
   opens -- opening a search should not push someone's reading position
   down the page. Opens leftward (right: 100%) because the icon lives in
   the right margin and a rightward panel would leave the viewport.
   visibility is transitioned with a 0s delay on close so the panel stays
   hit-testable through the fade rather than vanishing instantly. */
.feed-rail-searchwrap { position: relative; }
.feed-search-pop {
  position: absolute; top: 0; right: calc(100% + 10px); width: 300px;
  padding: 14px 16px; background: rgba(10,15,26,.97); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 14px 38px rgba(0,0,0,.55);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  opacity: 0; visibility: hidden; transform: translateX(8px) scale(.98);
  transform-origin: top right; z-index: 30;
  transition: opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
}
.feed-search-pop.open {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .18s ease, transform .18s ease;
}
@media (prefers-reduced-motion: reduce) {
  .feed-search-pop { transition: none; }
}
.fsp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
@media (max-width: 1023px) {
  .feed-rail { position: static; margin: 0 0 12px; flex-direction: row; justify-content: flex-end; }
  .feed-rail .feed-rail-search { display: none; }
  .feed-search-pop { display: none; }
}
/* Hidden on desktop. It carries .feed-rail-btn so it matches the star
   exactly at 40px rather than being a second, slightly different 36px box
   beside it -- but .feed-rail-btn also sets display:inline-flex, so without
   this it would render in the desktop rail where a full search popover
   already exists. Declared after .feed-rail-btn deliberately: both are
   (0,1,0) and source order is the only thing deciding it. */
.fmt-search-toggle { display: none; }
/* The world's name and the way into it share a line. The 14px that used
   to sit under the strip moves to the row, so the spacing below the pair
   is the same as it was under the name alone -- the control is added
   without the page shifting. Wraps rather than shrinks on a narrow
   screen: nothing hides to make a layout fit. */
.feed-world-line { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.feed-world-strip { font-family: var(--mono); font-size: .78rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-dimmer); }
@media (max-width: 1023px) {
  /* Only the display flip is needed now -- every other property comes from
     .feed-rail-btn, which is the whole point of sharing that class. */
  .fmt-search-toggle { display: inline-flex; }
  .fmt-search-toggle[aria-expanded="true"] { color: var(--star); border-color: var(--ion-dim); }
  /* The chips WRAP in the panel rather than scrolling horizontally. The old
     row scrolled because seven pills wrapped to three ragged rows and pushed
     the first post below the fold -- but inside a panel the reader has opened
     on purpose, vertical space is exactly what there is to spend, and wrapping
     is what makes every category visible at once instead of hiding the last
     one off the right edge. .fsp-chips already wraps; nothing extra needed. */
  /* An inline expanding panel, not an absolutely-positioned popover. The
     desktop popover is a fixed 300px opening leftward from a rail that sits
     in the page margin; on a phone there is no margin to open into, so the
     same component renders full width in flow directly beneath the rail --
     under the control that opened it, where a thumb already is. Styled to
     match the popover so it reads as the same thing in a different place. */
  .fmt-search { display: block; margin: 8px 0 4px; padding: 14px 16px;
    background: rgba(10,15,26,.97); border: 1px solid var(--line); border-radius: 14px; }
  /* display:flex above outranks the UA stylesheet [hidden]{display:none},
     so the collapsed search rendered open. Restore it explicitly. */
  .fmt-search[hidden] { display: none; }
  .fmt-search input { flex: 1; min-width: 0; padding: 9px 12px; background: rgba(5,7,13,.5); border: 1px solid var(--line); color: var(--text); border-radius: 8px; font-family: var(--body); }
  .fmt-search button { background: var(--ion); border: none; border-radius: 8px; padding: 0 14px; color: #03060d; cursor: pointer; font-weight: 600; }
}
.category-link { display: flex; align-items: center; justify-content: space-between; padding: 9px 0; color: var(--text-dim); text-decoration: none; font-size: .88rem; transition: color .2s; border-bottom: 1px solid var(--line-soft); }
.category-link:last-child { border-bottom: none; }
.category-link:hover, .category-link.active { color: var(--star); }
/* Positioned ABOVE the sidebar's own box (bottom:100% + gap), not as
   a flow child -- .feed-layout's own position:relative already
   establishes the containing block this needs. This means Search's
   position never moves by a single pixel whether this renders or
   not: present or absent, it contributes zero height to the
   sidebar's own flow.

   THE JOIN BUTTON'S OWN CSS AND JS ARE NO LONGER IN THIS FILE. They
   moved to includes/game-follow.php on 2026-08-08 so that markup, style
   and behaviour travel together and the button can render on every page
   of a world rather than only here. A component whose stylesheet stays
   behind is not a component -- that exact mistake shipped 1440px avatars
   the first time the feed post card was rendered anywhere else.

   What stays here is only what belongs to THIS page: the slot the button
   sits in, and the favourite star, which is a separate ongoing control
   with a different lifetime. */
/* RETIRED 2026-09-08 — .game-actions. This page's absolutely-positioned slot
   for the join button and the favourite star. Nothing emitted
   class="game-actions" any more: the live container is .feed-rail (above),
   which carries its OWN positioning — left:100%, vertical, with its own
   mobile rule — rather than the top:50px / right:20px this slot used. Removed
   at census zero, with the class matching zero nodes on every surface checked.

   THE ONE LESSON WORTH KEEPING, paid for on a real phone and still governing
   .feed-rail: do NOT make this control position:fixed. Fixed to the viewport
   means it floats permanently over whatever scrolls underneath, and it has no
   solid background by design, so it blends into a photo the moment one
   scrolls behind it. Absolute — scrolling away with the page — is right at
   every width, and mobile does not need a different behaviour from desktop. */
#worldJoinBtn { position: relative; }
/* Sized to the join button's own height so the two sit on one optical
   line, and to the site's 32px touch minimum at its narrowest. */
.world-fav-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  background: rgba(5,7,13,.72); border: 1px solid var(--line); border-radius: 9px;
  color: var(--text-dim); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.world-fav-btn:hover, .world-fav-btn:focus-visible { color: var(--star); border-color: var(--ion); }
/* Filled, not merely recoloured: the whole point of this control is
   being readable at a glance from across the page, and an outline star
   in a slightly different blue is not that. */
.world-fav-btn.is-favorite { color: #ffd479; border-color: rgba(255,212,121,.45); background: rgba(255,212,121,.12); }
.world-fav-btn.is-favorite svg path { fill: currentColor; }
.world-fav-btn[aria-busy="true"] { opacity: .55; pointer-events: none; }
/* The cap message. Absolutely positioned so appearing cannot shove the
   join button sideways -- a control that moves when you are told "no"
   invites a second, accidental click on whatever slid underneath. */
.world-fav-error {
  position: absolute; top: calc(100% + 8px); right: 0; width: 230px; margin: 0;
  padding: 8px 10px; background: var(--panel-2); border: 1px solid #8a2d3b; border-radius: 8px;
  color: #ff9baa; font-size: .76rem; line-height: 1.45; text-align: left; z-index: 6;
}

.chip { padding: 5px 11px; border-radius: 20px; border: 1px solid var(--line); color: var(--text-dim); font-size: .76rem; text-decoration: none; transition: all .2s; }
.chip:hover, .chip.active { color: var(--star); border-color: var(--ion-dim); background: rgba(var(--ion-rgb),.08); }
.feed-login-prompt { text-align: center; padding: 30px; background: rgba(12,19,34,.6); border: 1px solid var(--line); border-radius: 16px; margin-bottom: 24px; }
.feed-login-prompt p { color: var(--text-dim); margin-bottom: 16px; }
.new-posts-pill { display: none; position: sticky; top: 100px; z-index: 5; background: linear-gradient(135deg, var(--ion), var(--star)); color: #03060d; border: none; border-radius: 20px; padding: 9px 20px; font-weight: 600; font-size: .85rem; cursor: pointer; margin: 0 auto 16px; box-shadow: 0 6px 20px rgba(var(--ion-rgb),.4); animation: pillDrop .3s ease; }
@keyframes pillDrop { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
.feed-loading { text-align: center; padding: 24px; color: var(--text-dimmer); font-family: var(--mono); font-size: .85rem; display: none; }
