/* =====================================================================
   Feed (Reddit-style) component styles. Used by BOTH /feed/ (full page)
   and the homepage teaser. Relies on the brand CSS variables
   (--paper, --ink, --crimson, --line, ...) defined by the host page —
   it does NOT redefine :root, so it's safe to link anywhere.
   ===================================================================== */
/* Daily Wise Word Finder styles (shared with post.css — must stay an @import) */
@import url("/feed/word-find.css");

/* ---- header user widget (shared site-wide; injected by feed/user-widget.js) ---- */
.user-widget{ display:inline-flex; align-items:center; gap:8px; text-decoration:none; border-radius:999px;
  border:1px solid var(--line); padding:4px 12px 4px 4px; color:var(--ink); font-size:13px; font-weight:500;
  line-height:1; transition:.18s ease; white-space:nowrap }
.user-widget:hover{ border-color:var(--crimson); color:var(--crimson); transform:translateY(-1px) }
.user-widget__av{ width:30px; height:30px; border-radius:50%; object-fit:cover;
  border:1px solid var(--gold); background:var(--paper-2); display:block }
.user-widget__name{ max-width:12ch; overflow:hidden; text-overflow:ellipsis }
.user-widget--out{ padding:9px 14px; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-soft) }
/* Neutral PENDING pill shown until the cross-site SSO bridge settles, so a
   genuinely-signed-in visitor never flashes "Sign in" on a cold second-site load.
   A bare pill-shaped shimmer — no text, non-interactive (pointer-events:none). */
.user-widget--pending{ width:84px; height:34px; padding:0; border-color:var(--line);
  background:linear-gradient(100deg, var(--paper-2) 30%, color-mix(in srgb, var(--paper-2) 60%, var(--line)) 50%, var(--paper-2) 70%);
  background-size:200% 100%; animation:user-widget-shimmer 1.1s ease-in-out infinite;
  pointer-events:none; cursor:default }
.user-widget--pending:hover{ transform:none; border-color:var(--line) }
@keyframes user-widget-shimmer{ from{ background-position:200% 0 } to{ background-position:-200% 0 } }
@media (prefers-reduced-motion: reduce){ .user-widget--pending{ animation:none } }

.feed-list{ display:flex; flex-direction:column; gap:16px }
/* We relocate seen cards to the bottom and manually re-pin scroll in
   hydrate-index.js (demoteSeen). Let the browser's native scroll anchoring also
   compensate and the two corrections STACK → the feed jumps upward. Opt this
   container out so our explicit scrollBy is the single source of truth. */
.feed-list{ overflow-anchor:none }
.feed-list .recycled{ opacity:.72 }   /* a seen post, recycled to the bottom */

/* post card: [vote rail] [body] */
.feed-card{ display:grid; grid-template-columns:auto 1fr; gap:16px; padding:18px 20px;
  border:1px solid var(--line); border-radius:14px; background:var(--tile);
  transition:border-color .2s ease, box-shadow .2s ease, transform .2s ease }
.feed-card:hover{ box-shadow:0 16px 34px -22px var(--shadow); transform:translateY(-2px) }
.feed-card:hover{ border-color:color-mix(in srgb, var(--crimson) 35%, var(--line)) }

/* entrance — cards rise in. No fill-mode, so the .recycled (seen) dimming below
   still takes over once the animation ends; scoped off recycled clones so moved
   posts don't re-flash. */
@keyframes feed-card-in{ from{ opacity:0; transform:translateY(12px) } to{ opacity:1; transform:none } }
.feed-card:not(.recycled){ animation:feed-card-in .5s cubic-bezier(.2,.7,.2,1) }
@media (prefers-reduced-motion: reduce){ .feed-card:not(.recycled){ animation:none } }

/* vote rail */
.vote, .bvote{ display:flex; flex-direction:column; align-items:center; gap:2px; user-select:none }
/* On the full feed, the rail is a subtly offset panel — so it reads at a glance
   which posts are up/down votable. Bluesky pills (.bvote) get the SAME rail so
   imported posts line up with native ones. */
.feed-card:not(.compact) .vote, .bsky-card .bvote{ align-self:stretch; justify-content:center;
  margin:-18px 0 -18px -20px; padding:18px 13px;
  background:color-mix(in srgb, var(--crimson) 7%, var(--paper-2));
  border-right:1px solid color-mix(in srgb, var(--crimson) 22%, var(--line));
  border-radius:13px 0 0 13px; }
.bsky-card .bvote{ margin:-13px 0 -13px -16px; padding:13px 12px }   /* match the pill's tighter padding */
.vote button, .bvote button{ font:inherit; line-height:1; background:none; border:0; cursor:pointer; color:var(--ink-soft);
  font-size:15px; padding:3px 5px; border-radius:7px; transition:.12s ease }
.vote button:hover, .bvote button:hover{ background:var(--paper-2) }
.vote button:active, .bvote button:active{ transform:scale(.82) }                 /* satisfying press pop */
.vote button.up:hover, .bvote button.up:hover{ color:#3b6ea5 }                      /* up = blue */
.vote button.down:hover, .bvote button.down:hover{ color:var(--crimson) }           /* down = red */
.vote button.up[aria-pressed="true"], .bvote button.up[aria-pressed="true"]{ color:#3b6ea5 }
.vote button.down[aria-pressed="true"], .bvote button.down[aria-pressed="true"]{ color:var(--crimson) }
.vote .count, .bvote .bcount{ font-family:var(--display); font-weight:600; font-size:17px; color:var(--ink); min-width:2ch; text-align:center }
.bvote .bcount:empty::before{ content:"·"; color:var(--ink-soft); font-weight:500 }   /* placeholder dot when 0/unknown */

/* body */
.feed-body{ min-width:0 }
.feed-type{ font-family:var(--ui); font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--crimson) }
.feed-card h3{ font-family:var(--display); font-weight:600; font-size:21px; line-height:1.2; margin:4px 0 7px }
.feed-card h3 a{ color:inherit; text-decoration:none; transition:color .15s ease }   /* full-card title link */
.feed-card h3 a:hover{ color:var(--crimson) }
.feed-card .text{ font-size:15px; color:var(--ink); line-height:1.6; white-space:pre-wrap; margin:0 0 9px; overflow-wrap:anywhere }
.feed-card.poem .text{ font-family:var(--display); font-size:17px; font-style:italic }    /* poems read as verse */
.feed-meta{ font-size:12.5px; color:var(--ink-soft); display:flex; gap:12px; flex-wrap:wrap; align-items:center }
.feed-meta a{ color:var(--ink-soft); border-bottom:1px solid var(--crimson); text-decoration:none; padding-bottom:1px }
.feed-meta a:hover{ color:var(--crimson) }
/* comment-count chip (no underline; the speech-bubble carries the meaning) */
.feed-meta .feed-cc{ border-bottom:0; display:inline-flex; align-items:center; gap:4px }

/* compact variant (homepage teaser): static score, clamped body, no media */
.feed-card.compact{ padding:14px 16px; gap:13px }
.feed-card.compact h3{ font-size:17px; margin:2px 0 4px }
.feed-card.compact .text{ font-size:13.5px; margin:0; white-space:normal;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }

/* ---- daily content pings: low-chrome, compact rows (no "Daily" tag, dated/
   compressed title, body clamped to a line or two) so they read as lightweight
   pings, not full posts. Set by pingView() in _render.js / feed.js. ---- */
.feed-card.ping-daily{ padding:13px 18px }
.feed-card.ping-daily h3{ font-size:16.5px; line-height:1.3; margin:0 0 4px }
.feed-card.ping-daily.poem h3{ font-style:italic; font-weight:500 }   /* haiku reads as verse */
.feed-card.ping-daily .text{ font-size:13.5px; line-height:1.5; margin:0; white-space:normal }
/* clamp to 2 lines ONLY when a "Read →" is offered (there's more to expand into);
   self-contained pings (scripture, short tips) show their full text. */
.feed-card.ping-daily.clamp .text{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
.feed-card.ping-daily .feed-meta{ margin-top:5px }

/* status / loading states */
.feed-status{ text-align:center; color:var(--ink-soft); font-size:14px; padding:24px 0 }
.feed-sentinel{ height:1px }

/* ---- interactive "choose your adventure" event cards (Daily RPG scaffold) ----
   Single column (no vote rail); gold-tinted to read as special. Wired by feed/events.js. */
.feed-card.event{ grid-template-columns:1fr;
  border-color:color-mix(in srgb, var(--gold) 40%, var(--line));
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 7%, var(--tile)), var(--tile)); }
.feed-type.event{ color:var(--gold) }
.fb-choices{ display:flex; flex-wrap:wrap; gap:8px; margin:11px 0 0 }
.fb-choice{ font:inherit; font-size:14px; font-weight:500; color:var(--ink); cursor:pointer;
  background:var(--paper-2); border:1px solid var(--line); border-radius:10px; padding:9px 14px;
  transition:transform .12s ease, border-color .15s ease, background-color .15s ease }
.fb-choice:hover:not(:disabled){ border-color:var(--crimson); transform:translateY(-1px) }
.fb-choice:disabled{ cursor:default; opacity:.55 }
.fb-choice.chosen{ opacity:1; border-color:var(--gold); font-weight:600;
  background:color-mix(in srgb, var(--gold) 18%, var(--paper-2)) }
.fb-outcome{ margin:13px 0 0; padding:11px 15px; border-left:3px solid var(--gold);
  background:var(--paper-2); border-radius:0 8px 8px 0;
  font-family:var(--display); font-size:16px; font-style:italic; color:var(--ink) }
@media (prefers-reduced-motion: reduce){ .fb-choice:hover:not(:disabled){ transform:none } }

/* ---- Bluesky cards (live third-party posts) — same grid shape as native posts:
   a left vote rail + the content column, so imported posts line up with ours. ---- */
.bsky-card{ display:grid; grid-template-columns:auto 1fr; gap:14px;
  padding:13px 16px; border:1px solid var(--line); border-radius:14px; background:var(--tile);
  transition:border-color .15s ease, box-shadow .15s ease }
.bsky-card:hover{ border-color:color-mix(in srgb, var(--crimson) 45%, var(--line)); box-shadow:0 10px 26px -20px var(--shadow) }
.bsky-main{ min-width:0; cursor:pointer }                    /* the post body expands; the vote rail doesn't */
.bsky-main:focus-visible{ outline:2px solid var(--crimson); outline-offset:3px; border-radius:8px }
.bsky-repost{ font-size:12px; color:var(--ink-soft); margin:0 0 8px }
/* align the chip/⋮ cluster to the TOP of the name+handle block (not its center)
   so they sit level with the name — a centered cluster floated mid-way when the
   id block was two lines tall. */
/* A thin rule separates "who posted" from the post itself (owner ask). The
   explicit margin/padding/border-top:0 also HARD-RESETS any page-level bare
   `header{}` rule from leaking in: .bsky-head is a <header>, and the homepage
   masthead's `header{padding:34px 0 26px;border-bottom:…}` was bleeding 60px of
   phantom headroom + a stray divider into every card there — making the homepage
   header read TALLER than /feed/. Owning these here makes both surfaces identical. */
.bsky-head{ display:flex; align-items:flex-start; gap:10px; position:relative;
  margin:0; padding:0 0 9px; border-top:0; border-bottom:1px solid var(--line) }
/* the first body block sits a fixed, tasteful distance below the divider —
   normalises the slightly different per-block top margins (.bsky-text 10 vs
   .bsky-row/.bsky-images/.bsky-ext 11) into one consistent header→content gap. */
.bsky-head + .bsky-text,
.bsky-head + .bsky-row,
.bsky-head + .bsky-images,
.bsky-head + .bsky-ext,
.bsky-head + .bsky-video{ margin-top:10px }
.bsky-avatar{ width:38px; height:38px; border-radius:50%; object-fit:cover; flex:0 0 auto; background:var(--paper-2); border:1px solid var(--line) }
.bsky-avatar.ph{ display:block }
.bsky-id{ display:flex; flex-direction:column; min-width:0; flex:1; padding-top:1px }
.bsky-name{ font-family:var(--display); font-weight:600; font-size:16px; line-height:1.2; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
/* the handle must stay ONE line + ellipsize — a long "@name.bsky.social · 19m"
   was wrapping under the "Bluesky ↗" chip and ⋮ and reading as a broken cluster. */
.bsky-handle{ font-size:12.5px; color:var(--ink-soft); white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.bsky-badge{ font-size:10px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:#1185fe;
  border:1px solid currentColor; border-radius:999px; padding:3px 9px; flex:0 0 auto; line-height:1 }
a.bsky-badge.bsky-link{ text-decoration:none; cursor:pointer; transition:.15s ease }
a.bsky-badge.bsky-link:hover{ background:#1185fe; color:#fff }
/* compact pill: clamp the text to a few lines so a long post never goes tall —
   the full text lives in the expanded modal (.bskm-text). */
.bsky-text{ font-size:14.5px; line-height:1.5; color:var(--ink); overflow-wrap:anywhere; margin:10px 0 0;
  display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden }
.bsky-row .bsky-text{ -webkit-line-clamp:4 }

/* COMPACT image post: thumbnail LEFT, text RIGHT (owner ask 2026-06-11) — keeps
   bsky photo posts from eating vertical space. The thumb links to the full photo;
   "+N" hints at more (full set on Bluesky). */
.bsky-row{ display:flex; align-items:flex-start; gap:12px; margin:11px 0 0 }
.bsky-thumb{ flex:0 0 auto; position:relative; width:92px; height:92px; border-radius:10px; overflow:hidden;
  border:1px solid var(--line); background:var(--paper-2); display:block }
.bsky-thumb img{ width:100%; height:100%; object-fit:cover; display:block }
.bsky-thumb-more{ position:absolute; right:4px; bottom:4px; font-size:11px; font-weight:600; line-height:1;
  color:#fff; background:rgba(0,0,0,.62); border-radius:6px; padding:2px 6px }
.bsky-row .bsky-text{ margin:0; flex:1; min-width:0 }
@media (max-width:480px){ .bsky-thumb{ width:78px; height:78px } .bsky-row{ gap:10px } }

.bsky-images{ display:grid; gap:6px; margin:11px 0 0 }
.bsky-images.n2,.bsky-images.n4{ grid-template-columns:1fr 1fr }
.bsky-images.n3{ grid-template-columns:1fr 1fr 1fr }
.bsky-images img{ width:100%; aspect-ratio:16/10; object-fit:cover; border-radius:10px; border:1px solid var(--line); display:block }

.bsky-ext{ display:flex; gap:0; margin:11px 0 0; border:1px solid var(--line); border-radius:10px; overflow:hidden;
  text-decoration:none; color:inherit; transition:border-color .15s ease }
.bsky-ext:hover{ border-color:var(--crimson) }
.bsky-ext-thumb{ width:96px; height:96px; object-fit:cover; flex:0 0 auto; background:var(--paper-2) }
.bsky-ext-body{ display:flex; flex-direction:column; gap:3px; padding:10px 12px; min-width:0 }
.bsky-ext-title{ font-family:var(--display); font-weight:600; font-size:15px; line-height:1.25; color:var(--ink);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
.bsky-ext-desc{ font-size:12.5px; color:var(--ink-soft); display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden }
.bsky-ext-host{ font-size:11px; color:var(--ink-soft); letter-spacing:.04em; text-transform:uppercase }
.bsky-video{ margin:11px 0 0; padding:14px; border:1px dashed var(--line); border-radius:10px; color:var(--ink-soft); font-size:13px }
/* Video facades — one design family. The brand here is Final Boss, not the
   provider's: every facade shares the same dark-glass play badge + tag, and the
   gradients sit in one muted tonal range so the row reads as ours (calm/parchment),
   not three competing platform billboards. The provider is named only in the tag. */
.bsky-yt-play, .bsky-vimeo-play, .bsky-tiktok-play{ width:52px; height:52px; display:grid; place-items:center;
  border-radius:50%; background:color-mix(in srgb, #000 56%, transparent); color:#fff; font-size:20px;
  padding-left:3px; pointer-events:none; box-shadow:0 2px 10px -2px #0009 }
.bsky-vid-tag, .bsky-vimeo-tag, .bsky-tiktok-tag{ position:absolute; top:9px; right:9px; font-size:9.5px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; color:#fff; background:color-mix(in srgb, #000 50%, transparent);
  -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); padding:2px 7px; border-radius:6px; pointer-events:none }
/* YouTube preview in the pill — thumbnail + play badge; clicking the pill opens the
   modal where the real iframe plays. Shorts get a portrait frame. */
.bsky-yt{ position:relative; margin:11px 0 0; border-radius:10px; overflow:hidden; border:1px solid var(--line);
  aspect-ratio:16/9; background:#000 }
.bsky-yt.short{ aspect-ratio:9/16; max-width:220px }
.bsky-yt-thumb{ width:100%; height:100%; object-fit:cover; display:block }
/* Vimeo facade (mirrors .bsky-yt) — branded placeholder; real player mounts in the modal.
   Muted teal-navy (not Vimeo's bright cyan) so it sits beside YouTube's neutral thumb on
   warm parchment without shouting. */
.bsky-vimeo{ position:relative; margin:11px 0 0; border-radius:10px; overflow:hidden; border:1px solid var(--line);
  aspect-ratio:16/9; background:linear-gradient(145deg,#0c1c26,#16323f); display:grid; place-items:center; cursor:pointer }
/* TikTok facade — VERTICAL (9/16) short-form, like a YouTube Short. Faint teal/pink hue
   nod kept, but darkened into the same tonal range as the others. */
.bsky-tiktok{ position:relative; margin:11px 0 0; border-radius:10px; overflow:hidden; border:1px solid var(--line);
  aspect-ratio:9/16; max-width:200px; background:linear-gradient(165deg,#0a0a0c,color-mix(in srgb,#25f4ee 12%,#0a0a0c) 55%,color-mix(in srgb,#fe2c55 16%,#0a0a0c));
  display:grid; place-items:center; cursor:pointer }

.bsky-meta{ display:flex; align-items:center; gap:12px; margin-top:12px; font-size:12.5px; color:var(--ink-soft) }
.bsky-meta a{ margin-left:auto; color:var(--ink-soft); text-decoration:none; border-bottom:1px solid #1185fe; padding-bottom:1px }
.bsky-meta a:hover{ color:#1185fe }
/* source-feed label (which generator/list this post came from) */
.bsky-source{ display:inline-flex; align-items:center; gap:4px; min-width:0; color:var(--ink-soft);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:52% }

/* ⋯ per-feed menu (feed/bluesky.js): name the source + hide that feed */
/* tighten the ⋮ to the chip (−4px) so the source-link + menu read as one
   right-hand cluster rather than three evenly-spaced items, and pull it to the
   card edge so the glyph isn't floating in dead space. */
.bsky-menu-btn{ flex:0 0 auto; appearance:none; border:0; background:transparent; cursor:pointer;
  color:var(--ink-soft); font-size:18px; line-height:1; padding:2px 4px 2px 6px; margin:-2px -4px 0 -4px;
  border-radius:8px; letter-spacing:.04em }
.bsky-menu-btn:hover{ color:var(--crimson); background:color-mix(in srgb, var(--crimson) 9%, transparent) }
.bsky-menu{ position:absolute; top:100%; right:0; z-index:30; margin-top:6px; min-width:188px; max-width:240px;
  background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:8px;
  box-shadow:0 14px 34px -16px var(--shadow); display:none; flex-direction:column; gap:4px }
.bsky-menu.is-open{ display:flex }   /* CLOSED is the base (display:none); only .is-open shows it — no [hidden]/specificity fragility */
.bsky-menu-h{ font-size:12px; color:var(--ink-soft); padding:4px 8px 6px; border-bottom:1px solid var(--line);
  overflow:hidden; text-overflow:ellipsis }
.bsky-menu-h strong{ color:var(--ink); font-weight:600 }
.bsky-menu-hide{ appearance:none; text-align:left; font:inherit; font-size:13px; font-weight:500; cursor:pointer;
  color:var(--ink); background:transparent; border:0; border-radius:8px; padding:7px 8px }
.bsky-menu-hide:hover{ background:color-mix(in srgb, var(--crimson) 12%, transparent); color:var(--crimson) }
.bsky-menu-note{ font-size:12px; color:var(--ink-soft); padding:6px 8px; font-style:italic }

/* EXPANDED FRAMED VIEW (feed/bluesky.js openBskyModal) — the on-site "fuller view"
   a pill opens into; a home for gamified pop-ups later. The text is FULL here (no
   clamp). The "Like & reply on Bluesky" button jumps to source. */
.bsky-modal{ position:fixed; inset:0; z-index:1000; display:flex; align-items:center; justify-content:center;
  padding:18px; background:color-mix(in srgb, #000 58%, transparent); -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px) }
.bsky-modal[hidden]{ display:none }
.bskm-card{ position:relative; width:100%; max-width:540px; max-height:88vh; overflow:auto;
  overscroll-behavior:contain;   /* boundary pulls stay inside the card → reliable swipe-to-dismiss/next */
  background:var(--tile); border:1px solid var(--line); border-radius:16px; padding:20px 20px 18px;
  box-shadow:0 30px 80px -24px var(--shadow); outline:none }   /* tabindex=-1 focus target; never keyboard-tabbed, so no ring */
/* a real ~40px tap target (was 32×33); the modal auto-focuses Close on open, so
   show the brand ring only for keyboard users (:focus-visible) — the UA's default
   blue/red box read as a stray error box to mouse users. */
.bskm-close{ position:absolute; top:8px; right:8px; appearance:none; border:0; background:transparent; cursor:pointer;
  font-size:24px; line-height:1; color:var(--ink-soft); width:40px; height:40px; display:grid; place-items:center;
  border-radius:10px; outline:none; transition:.15s ease }
.bskm-close:hover{ color:var(--crimson); background:color-mix(in srgb, var(--crimson) 9%, transparent) }
.bskm-close:focus-visible{ box-shadow:0 0 0 2px var(--crimson) }
/* clear the 40px close button (was 28px → a long handle could slide under it) */
.bskm-head{ display:flex; align-items:center; gap:11px; margin:0 40px 14px 0 }
.bskm-av{ width:44px; height:44px; border-radius:50%; object-fit:cover; flex:0 0 auto; background:var(--paper-2); border:1px solid var(--line) }
.bskm-av.ph{ display:block }
.bskm-id{ display:flex; flex-direction:column; min-width:0 }
/* the modal handle should ellipsize on one line too (matches the pill) */
.bskm-id .bsky-handle{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.bskm-text{ font-size:16px; line-height:1.6; color:var(--ink); white-space:pre-wrap; overflow-wrap:anywhere; margin:0 0 14px }
.bskm-imgs{ display:flex; flex-direction:column; gap:8px; margin:0 0 14px }
/* RESERVE a box before the image decodes so the modal doesn't grow/jump as each
   photo pops in. 4/3 here is the FALLBACK; renderBskyCard now sets an inline
   `aspect-ratio` from the post's NATIVE dimensions when bsky provides them, so the
   box matches the real photo and a portrait no longer re-settles as it loads.
   object-fit:contain shows it full (no crop); max-height caps a tall portrait so it
   can't dominate the modal. The box is deterministic either way — no CLS. */
.bskm-imgs img{ width:100%; min-height:0; aspect-ratio:4/3; max-height:72vh; object-fit:contain;
  background:var(--paper-2); border-radius:12px; border:1px solid var(--line); display:block }
/* Bluesky is now the SECONDARY action — your native Like (vote) + comments are the
   star of the post detail. A quiet text link, not the old prominent blue pill. */
.bskm-bsky-link{ font-size:12.5px; font-weight:600; text-decoration:none; white-space:nowrap;
  color:var(--ink-soft); display:inline-flex; align-items:center; gap:4px }
.bskm-bsky-link:hover{ color:#1185fe; text-decoration:underline }
/* framed external link-card post: a clear "Go to site ↗" escape hatch to the
   source, sitting beside "Also on Bluesky ↗" (we render our version, not an iframe) */
.bskm-links{ display:inline-flex; align-items:center; gap:14px; flex-wrap:wrap }
.bskm-ext-link{ font-size:12.5px; font-weight:600; text-decoration:none; white-space:nowrap;
  color:var(--crimson); display:inline-flex; align-items:center; gap:4px }
.bskm-ext-link:hover{ text-decoration:underline }
/* modal YouTube iframe (real player) + the footer row (vote rail ↔ Bluesky link) */
.bskm-yt{ position:relative; margin:0 0 14px; border-radius:12px; overflow:hidden; aspect-ratio:16/9; background:#000 }
.bskm-yt.short{ aspect-ratio:9/16; max-width:330px; margin-inline:auto }
.bskm-yt iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; display:block }
/* modal Vimeo iframe (real player) */
.bskm-vimeo{ position:relative; margin:0 0 14px; border-radius:12px; overflow:hidden; aspect-ratio:16/9; background:#000 }
.bskm-vimeo iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; display:block }
/* modal TikTok player — VERTICAL, centered, capped to the viewport (short-form reel) */
.bskm-tiktok{ position:relative; margin:0 auto 14px; border-radius:12px; overflow:hidden; aspect-ratio:9/16;
  width:100%; max-width:325px; max-height:80vh; background:#000 }
.bskm-tiktok iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; display:block }
.bskm-foot{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap }
.bskm-vote{ flex-direction:row; gap:8px; align-items:center; margin:0 }
.bskm-vote .bcount{ min-width:1ch }
/* comment thread inside the modal (self-contained — post.css isn't loaded on the homepage) */
.bskm-comments{ margin:16px 0 0; border-top:1px solid var(--line); padding-top:13px }
.bskm-c-head{ font-family:var(--ui); font-size:13px; font-weight:700; letter-spacing:.02em; color:var(--ink); margin:0 0 10px }
.bskm-comments .comment-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:9px }
.bskm-comments .comment{ background:color-mix(in srgb, var(--tile) 55%, transparent); border:1px solid var(--line); border-radius:10px; padding:9px 11px }
.bskm-comments .comment.is-pending{ opacity:.85; border-style:dashed }
.bskm-comments .comment-head{ display:flex; align-items:center; gap:8px; font-family:var(--ui); font-size:12px }
.bskm-comments .comment-name{ font-weight:700; color:var(--ink) }
.bskm-comments .comment-time{ color:var(--ink-soft) }
.bskm-comments .comment-pending{ margin-left:auto; font-size:10.5px; font-weight:600; color:var(--crimson);
  border:1px solid color-mix(in srgb, var(--crimson) 40%, var(--line)); border-radius:999px; padding:1px 7px }
.bskm-comments .comment-text{ margin:5px 0 0; font-size:14px; line-height:1.5; color:var(--ink); white-space:pre-wrap; overflow-wrap:anywhere }
.bskm-comments .comment-empty{ color:var(--ink-soft); font-size:13px; list-style:none }
.bskm-comments .comment-mod{ display:flex; flex-wrap:wrap; gap:8px; margin-top:7px }
.bskm-comments .comment-mod button{ font:inherit; font-size:12px; cursor:pointer; border:1px solid var(--line); border-radius:8px; background:var(--paper); color:var(--ink); padding:3px 9px }
.bskm-comments .comment-mod button:hover{ border-color:var(--crimson); color:var(--crimson) }
.bskm-comments .comment-mod button:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }
/* trailing share; keep the two-word label on one line (see post.css note) */
.bskm-comments .comment-mod .c-bsky{ margin-left:auto; white-space:nowrap }
.bskm-comments .comment-mod .c-bsky:hover{ border-color:#1185fe; color:#1185fe }   /* Bluesky blue */
.bskm-comments .comment-mod .c-bsky:focus-visible{ outline-color:#1185fe }
/* "Reply on Bluesky" (gated Sync feature): the active in-thread post action, so it
   reads filled-blue vs the muted "Share" intent link. .done = posted confirmation. */
.bskm-comments .comment-mod .c-bsky-reply{ border-color:#1185fe; color:#fff; background:#1185fe; white-space:nowrap }
.bskm-comments .comment-mod .c-bsky-reply:hover{ filter:brightness(1.08) }
.bskm-comments .comment-mod .c-bsky-reply:focus-visible{ outline-color:#1185fe }
/* done = posted: the button stays disabled in markup, so dim ONLY the not-yet-done
   disabled state ("Posting…"); the success confirmation reads crisp (full opacity),
   not faded like a dead control. (Scoping the dim to :not(.done) avoids a cascade
   order fight — both selectors are equal specificity.) */
.bskm-comments .comment-mod .c-bsky-reply.done{ background:transparent; color:#1185fe; cursor:default }
.bskm-comments .comment-mod .c-bsky-reply:disabled:not(.done){ opacity:.6; cursor:default }
.bskm-c-auth{ margin-top:12px }
.bskm-comments .comment-form{ display:flex; flex-direction:column; gap:8px }
.bskm-comments .comment-input{ width:100%; font:inherit; font-size:14px; padding:9px 11px; border:1px solid var(--line);
  border-radius:10px; background:var(--paper); color:var(--ink); resize:vertical; box-sizing:border-box }
.bskm-comments .comment-actions{ display:flex; align-items:center; gap:10px }
.bskm-comments .comment-submit{ font:inherit; font-weight:600; cursor:pointer; border:0; border-radius:999px; background:var(--crimson); color:#fff; padding:7px 16px; white-space:nowrap }
.bskm-comments .comment-submit:disabled{ opacity:.6; cursor:default }
.bskm-comments .comment-err{ font-size:12.5px; color:var(--crimson); min-width:0 }
.bskm-comments .comment-err.ok{ color:var(--hq-green,#1f6d4c) }
.bskm-comments .comment-gate{ font-size:13px; color:var(--ink-soft) }
.bskm-comments .comment-gate a{ color:var(--crimson); font-weight:600 }
@media (prefers-reduced-motion: reduce){ .bsky-card{ transition:none } }

/* ---- "ping" cards: small, title-only, NARROWER (twitch-live / personal-best) ---- */
.feed-card.ping{ grid-template-columns:1fr; max-width:480px; margin-left:auto; margin-right:auto;
  padding:14px 20px; text-align:center;
  background:linear-gradient(180deg, color-mix(in srgb, var(--ink) 4%, var(--tile)), var(--tile)); }
.ping-inner{ display:flex; flex-direction:column; align-items:center; gap:9px }
.ping-title{ font-family:var(--display); font-weight:600; font-size:18px; line-height:1.25; margin:0; color:var(--ink) }
.ping-cta{ font-family:var(--ui); font-size:12.5px; font-weight:600; letter-spacing:.02em; text-decoration:none;
  color:var(--crimson); border:1px solid color-mix(in srgb, var(--crimson) 40%, var(--line)); border-radius:999px;
  padding:6px 15px; transition:.15s ease }
.ping-cta:hover{ background:var(--crimson); color:#fff; border-color:var(--crimson); transform:translateY(-1px) }
.ping-live{ display:inline-flex; align-items:center; gap:6px; font-size:10px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:#e02d2d }
.ping-live::before{ content:""; width:7px; height:7px; border-radius:50%; background:#e02d2d;
  box-shadow:0 0 0 0 rgba(224,45,45,.6); animation:ping-dot 1.8s ease-out infinite }
@keyframes ping-dot{ 0%{ box-shadow:0 0 0 0 rgba(224,45,45,.55) } 70%,100%{ box-shadow:0 0 0 7px rgba(224,45,45,0) } }
/* red "Live Now" highlight while the stream is on */
.feed-card.ping.live{ border-color:#e02d2d;
  box-shadow:0 0 0 1px #e02d2d, 0 10px 32px -12px rgba(224,45,45,.55);
  background:linear-gradient(180deg, color-mix(in srgb, #e02d2d 12%, var(--tile)), var(--tile)); }
@media (prefers-reduced-motion: reduce){ .ping-live::before{ animation:none } }

/* "🔔 Notify me when live" opt-in button */
.ping-bell-wrap{ display:flex; justify-content:center; margin:0 0 6px }
.ping-bell{ font:inherit; font-size:12.5px; font-weight:500; color:var(--ink-soft); cursor:pointer;
  background:var(--tile); border:1px solid var(--line); border-radius:999px; padding:7px 15px; transition:.15s ease }
.ping-bell:hover{ color:var(--crimson); border-color:var(--crimson); transform:translateY(-1px) }

/* ---- chess puzzle card (static board from FEN; answer in the comments) ---- */
.feed-card.chess-puzzle{ grid-template-columns:1fr; padding:16px 18px; text-align:center;
  background:linear-gradient(180deg, color-mix(in srgb, var(--ink) 4%, var(--tile)), var(--tile)); }
/* min-width:0 — .cp-inner is a GRID item, so its auto min-width otherwise blows out
   to the min-content of the pill's nowrap title row, pushing the Play button past
   the card edge (off-screen on a 375px phone, + horizontal page scroll). */
.cp-inner{ display:flex; flex-direction:column; align-items:center; gap:10px; min-width:0 }
.cp-kicker{ font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--crimson) }
.cp-prompt{ font-family:var(--display); font-weight:600; font-size:19px; line-height:1.25; margin:0 }
.cp-prompt a{ color:inherit; text-decoration:none } .cp-prompt a:hover{ color:var(--crimson) }
.cp-foot{ font-size:12.5px; color:var(--ink-soft) }
.cp-foot a{ color:var(--ink); text-decoration:none; border-bottom:1px solid var(--crimson); padding-bottom:1px }
.cp-foot a:hover{ color:var(--crimson) }
/* ---- the board (shared by feed cards + the post page) -------------------
   A PERFECT SQUARE at any width: aspect-ratio:1/1 on the board + an 8-col grid
   of 1fr cells that are themselves square (aspect-ratio:1/1). No fixed cell
   heights → no rectangular squares on any viewport. Cap + center the board. */
/* width:100% (capped) so the board always fits its CARD, never the viewport —
   92vw overflowed the padded card on mobile and clipped the h-file/right edge. */
.chessb{ display:grid; grid-template-columns:repeat(8,1fr); width:100%; max-width:360px; aspect-ratio:1/1; margin:2px auto;
  position:relative; border:3px solid #6b4a2f; border-radius:8px; overflow:hidden;
  box-shadow:0 10px 28px -14px rgba(0,0,0,.5); }
.chessb .sq{ position:relative; aspect-ratio:1/1; width:100%; user-select:none }
/* Lichess-style cream/brown squares. Use background-COLOR (not the `background`
   shorthand) so it never resets the piece PNG (background-image) layered on by
   the .cp-* rules below — the shorthand expands to background-image:initial. */
.chessb .sq.l{ background-color:#efe2c4 } .chessb .sq.d{ background-color:#b07a4f }

/* ---- piece art (Chess Puzzle Blitz PNGs) — a centered, contained background
   image on each occupied cell. Replaces the old Unicode glyphs (.pw/.pb). ---- */
.chessb .sq.cp{ background-repeat:no-repeat; background-position:center; background-size:88%; }
/* IMPORTANT: set only background-COLOR here, never the `background` shorthand —
   the shorthand resets background-image to none and (being higher-specificity
   than the .cp-* rules below) would wipe the piece PNG, leaving blank squares. */
.chessb .sq.cp.l{ background-color:#efe2c4 }
.chessb .sq.cp.d{ background-color:#b07a4f }
.cp-wp{ background-image:url(/images/chess/wp.png) } .cp-bp{ background-image:url(/images/chess/bp.png) }
.cp-wn{ background-image:url(/images/chess/wn.png) } .cp-bn{ background-image:url(/images/chess/bn.png) }
.cp-wb{ background-image:url(/images/chess/wb.png) } .cp-bb{ background-image:url(/images/chess/bb.png) }
.cp-wr{ background-image:url(/images/chess/wr.png) } .cp-br{ background-image:url(/images/chess/br.png) }
.cp-wq{ background-image:url(/images/chess/wq.png) } .cp-bq{ background-image:url(/images/chess/bq.png) }
.cp-wk{ background-image:url(/images/chess/wk.png) } .cp-bk{ background-image:url(/images/chess/bk.png) }

/* ---- coordinate labels (files a–h, ranks 1–8), Lichess-style: small, low
   contrast, tucked in the board edges; they flip with the board (data-file is
   set on the bottom display row, data-rank on the right column by the renderer). */
.chessb .sq[data-file]::after{ content:attr(data-file); position:absolute; left:2px; bottom:1px;
  font:600 9px var(--ui); line-height:1; opacity:.62; pointer-events:none }
.chessb .sq[data-rank]::before{ content:attr(data-rank); position:absolute; right:2px; top:1px;
  font:600 9px var(--ui); line-height:1; opacity:.62; pointer-events:none }
/* label color = the OPPOSITE square tone so it stays legible on both colors. */
.chessb .sq.l[data-file]::after,.chessb .sq.l[data-rank]::before{ color:#7a5230 }
.chessb .sq.d[data-file]::after,.chessb .sq.d[data-rank]::before{ color:#efe2c4 }

/* ===== Daily Chess Puzzle — the INTERACTIVE card (feed/chess-play.js) =====
   Styles the Play prelude, the live board (selected square, legal-move dots,
   last-move highlight, check flash), the timer + penalty flash, and the inline
   per-puzzle leaderboard. Brand parchment/crimson, dark-mode + reduced-motion
   aware, with designed empty/loading/error states (no blank boxes). */
.feed-card.daily-chess{ grid-template-columns:1fr; padding:16px 18px; text-align:center;
  background:linear-gradient(180deg, color-mix(in srgb, var(--crimson) 5%, var(--tile)), var(--tile)); }

/* ---- MINIMIZED SPAWN → EXPAND ON PLAY (task 1) -------------------------------
   The card spawns COMPACT: .dc-prelude.dc-min holds a SMALL board preview + the
   Play button. On Play the host swaps in the interactive shell and the card gets
   .dc-expanded, which grows .dc-board-area small→full and fades in the bar/actions/
   status. The board's max-width is the single animated property (cheap, contained,
   aspect-ratio drives height) so the grow is smooth; modern browsers animate it
   well. The minimized state reserves its OWN small size in plain CSS (no JS at
   spawn) so it hydrates jank-free — only the deliberate Play press animates. */
/* .cp-inner is a centering column flex, so .dc-host (a flex item) would shrink to
   its content width — collapsing the board to its ~88px min-content (just the
   coordinate labels). Stretch the host so the board area fills the card width. */
.dc-host{ align-self:stretch; width:100% }
/* The board box. Default (interactive / expanded) cap is 360px. The grow is driven
   by transitioning max-width; will-change hints the compositor. */
.dc-board-area{ width:100%; max-width:360px; aspect-ratio:1/1; margin:2px auto;
  display:flex; align-items:center; justify-content:center;
  transition:max-width .3s cubic-bezier(.2,.7,.2,1); will-change:max-width }
/* the board fills the box (static prelude board is a direct child; the interactive
   board mounts via .dc-mount). Strip margins so the box alone sizes/centers it. */
.dc-board-area > .chessb, .dc-board-area .dc-mount, .dc-board-area .dc-mount > .chessb{ margin:0 }
.dc-board-area .dc-mount{ width:100%; display:flex; align-items:center; justify-content:center }

/* COMPACT PILL (minimized): a small horizontal row — tiny board thumbnail · title +
   "Diff N · Mate in N" + date · Play button on the right. On Play the host is swapped
   for the interactive shell + .dc-expanded grows the board and pushes the feed down. */
.dc-mini{ display:flex; align-items:center; gap:12px; width:100%; text-align:left }
.dc-mini-board{ flex:0 0 auto; width:54px; line-height:0 }
.dc-mini-board .chessb{ width:54px; max-width:54px; margin:0; border-width:2px; border-radius:5px;
  box-shadow:0 3px 9px -6px rgba(0,0,0,.55) }
/* no coordinate labels at thumbnail size — they're noise on a 54px board */
.dc-mini-board .chessb .sq[data-file]::after,
.dc-mini-board .chessb .sq[data-rank]::before{ display:none }
.dc-mini-board .chessb .sq.cp{ background-size:92% }
.dc-mini-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; line-height:1.25 }
.dc-mini-title{ font-family:var(--display,serif); font-weight:700; font-size:15px; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.dc-mini-meta{ font-family:var(--ui); font-size:12px; font-weight:600; color:var(--crimson);
  letter-spacing:.02em }
.dc-mini-date{ font-family:var(--ui); font-size:12px; color:var(--ink-soft,#6a6253) }
.dc-mini .dc-play{ flex:0 0 auto; margin:0 }

/* DONE TODAY: the in-feed daily was already solved on this device (per-day
   localStorage flag → feed.js markDailyDone adds .dc-done). Grey the mini
   thumbnail and stamp a green ✓ over it so a returning visitor sees at a glance
   which dailies they've finished. Scoped to the COMPACT pill (:not(.dc-expanded))
   so replaying — which expands the card and swaps in the live board — is never
   greyed. The thumbnail is the first 54px of the .dc-mini flex row, so we centre
   the badge there. */
.dc-done:not(.dc-expanded) .dc-mini{ position:relative }
.dc-done:not(.dc-expanded) .dc-mini-board,
.dc-done:not(.dc-expanded) .wf-mini-grid{ filter:grayscale(1); opacity:.45;
  transition:opacity .2s ease, filter .2s ease }
.dc-done:not(.dc-expanded) .dc-mini::after{
  content:"\2713";                                  /* ✓ */
  position:absolute; left:27px; top:50%; transform:translate(-50%,-50%);
  width:26px; height:26px; border-radius:50%;
  display:grid; place-items:center;
  background:#2e7d32; color:#fff;
  font-family:var(--ui); font-weight:800; font-size:15px; line-height:1;
  border:2px solid var(--paper,#fff);
  box-shadow:0 2px 7px -1px rgba(0,0,0,.55);
  pointer-events:none;
}
/* the compact title gets a subtle "done" muting too, so the whole row reads as
   completed — not just the thumbnail */
.dc-done:not(.dc-expanded) .dc-mini-title{ color:var(--ink-soft,#6a6253) }
/* COMPLETED daily CTA: feed.js markDailyDone swaps the crimson "▶ Play" for
   "✓ Done · Leaderboard 🏆" + .dc-play-done, so a finished daily points at the day's
   BOARD instead of reading as a fresh Play. Calm gold pill (distinct from the crimson
   Play). A solved daily ALSO sinks in the stream (feed-stream-core isDemoted) so the
   NEW morning puzzle leads — these two changes are the 2026-06-15 "focus on today's
   new puzzle" fix. Applies to every daily feed card (chess/word/history/art). */
.dc-play.dc-play-done{
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 24%, var(--tile,#fff)),
                                     color-mix(in srgb, var(--gold) 13%, var(--tile,#fff)));
  color:var(--ink,#2b2620);
  border-color:color-mix(in srgb, var(--gold) 55%, var(--line,#d8cfbe));
  box-shadow:0 2px 0 color-mix(in srgb, var(--gold) 40%, var(--line,#d8cfbe));
}
.dc-play.dc-play-done:hover{ transform:translateY(-1px); filter:brightness(1.04) }
/* compact while minimized: tighter padding, left-aligned (the pill, not a centered card) */
.feed-card.daily-chess:not(.dc-expanded){ padding:10px 12px; text-align:left }
/* minimized = show ONLY the pill; the kicker row + footer links return on expand */
.feed-card.daily-chess:not(.dc-expanded) .dc-kicker-row,
.feed-card.daily-chess:not(.dc-expanded) .dc-foot{ display:none }
/* the prompt h3 stays in the DOM (SEO / no-JS / screen readers) but is visually
   screen-reader-only — the visible UI is the pill → play shell. */
.feed-card.daily-chess .dc-prompt{ position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0 }

/* The interactive shell mounts a NEW .dc-board-area (no .dc-min wrapper). Until the
   card gets .dc-expanded it must START at the SAME small size as the preview so the
   grow animates from small→full (otherwise the fresh element would just appear full
   with no transition). .dc-expanded then grows it to the 360px cap. */
.daily-chess:not(.dc-expanded) .dc-board-area{ max-width:180px }
.daily-chess.dc-expanded .dc-board-area{ max-width:360px }
/* The bar / actions / status start hidden-ish and ease in as the board grows, so
   the expand reads as one coordinated motion rather than a pop. */
.daily-chess .dc-bar, .daily-chess .dc-actions, .daily-chess .dc-status{
  transition:opacity .28s ease .06s, transform .28s ease .06s }
.daily-chess:not(.dc-expanded) .dc-bar,
.daily-chess:not(.dc-expanded) .dc-actions,
.daily-chess:not(.dc-expanded) .dc-status{ opacity:0; transform:translateY(6px) }
.daily-chess.dc-expanded .dc-bar,
.daily-chess.dc-expanded .dc-actions,
.daily-chess.dc-expanded .dc-status{ opacity:1; transform:none }
.dc-kicker-row{ display:flex; align-items:center; justify-content:center; gap:8px; position:relative }
/* Calendar/archive shortcut. Sized to a ~40px thumb target (was 36×24, a miss on
   mobile); centered glyph via inline-grid so the box can grow without offsetting it. */
.dc-cal{ display:inline-grid; place-items:center; min-width:40px; min-height:40px; font-size:17px; line-height:1;
  text-decoration:none; filter:grayscale(.2); border:1px solid var(--line); border-radius:9px; transition:.15s ease }
.dc-cal:hover{ border-color:var(--crimson); transform:translateY(-1px) }
/* Minimized prelude: a centered column — small board preview above the Play
   button. (No ZLS spacers any more; the expand on Play is deliberate, task 1.) */
.dc-prelude{ display:flex; flex-direction:column; align-items:center; gap:0 }
.dc-play{ font:inherit; font-family:var(--ui); font-weight:600; font-size:14px; letter-spacing:.02em;
  color:#fff; background:var(--crimson); border:1px solid color-mix(in srgb, var(--crimson) 70%, #000);
  border-radius:999px; padding:9px 20px; cursor:pointer; transition:.16s ease;
  /* touch-action:manipulation kills the ~300ms tap delay + double-tap-zoom so the
     FIRST tap registers (no "took a few taps" on mobile / scroll-snap feeds). */
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
  box-shadow:0 8px 20px -10px color-mix(in srgb, var(--crimson) 80%, #000) }
/* same fast-tap treatment for the in-board actions */
.dc-hint,.dc-reset{ touch-action:manipulation; -webkit-tap-highlight-color:transparent }
.dc-play:hover{ transform:translateY(-1px); filter:brightness(1.06) }
.dc-play:disabled{ opacity:.6; cursor:default; transform:none }

/* the play bar: timer + goal + transient flash */
.dc-bar{ display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; margin:0 0 8px }
.dc-timer{ font-family:var(--ui); font-variant-numeric:tabular-nums; font-weight:700; font-size:20px;
  color:var(--ink); min-width:5ch }
.dc-goal{ font-size:11px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--crimson) }
.dc-flash{ font-size:13px; font-weight:600; padding:2px 8px; border-radius:999px }
.dc-flash.pen{ color:#fff; background:var(--crimson); animation:dc-pop .35s ease }
.dc-flash.bad{ color:var(--crimson); background:color-mix(in srgb, var(--crimson) 14%, transparent) }
@keyframes dc-pop{ 0%{ transform:scale(.7); opacity:0 } 60%{ transform:scale(1.12) } 100%{ transform:scale(1); opacity:1 } }

/* interactive board: reuse .chessb base; add live-state cues */
/* touch-action:none so a DRAG on the board doesn't scroll the page (task 2). */
.dc-board{ touch-action:none }
/* cursor affordance: grab over pieces of the side-to-move, grabbing while dragging.
   (Plain pointer on empty/non-movable squares for click-to-move.) */
.dc-board .sq{ cursor:pointer }
.dc-board .sq.cp{ cursor:grab }
.dc-board.dc-drag-active, .dc-board.dc-drag-active .sq{ cursor:grabbing }
/* SELECTED SOURCE — HIGH CONTRAST (task 3): the picked-up piece must stand out
   clearly from BOTH the piece art and the wood squares, in light AND dark mode.
   We use a DOUBLE inset ring — a bright gold core ring with a dark outer ring
   around it — so it reads against cream squares (dark edge) and brown squares
   (gold edge) alike, plus a stronger gold wash. The dark outer ring is what gives
   the extra pop the old single 3px gold ring lacked on the dark squares / dark mode.
   (Legal-move dots/.cap rings stay distinct — different shape + neutral color.) */
.dc-board .sq.sel{
  box-shadow:inset 0 0 0 3px var(--gold), inset 0 0 0 5px rgba(0,0,0,.55), inset 0 0 12px rgba(255,200,40,.55);
  background-color:color-mix(in srgb, var(--gold) 62%, transparent) }
.dc-board .sq.last{ background-color:rgba(205,210,80,.55) }
/* last-move tint on a piece cell: override only the COLOR so the PNG survives. */
.dc-board .sq.last.cp.l{ background-color:rgba(205,210,80,.55) }
.dc-board .sq.last.cp.d{ background-color:rgba(180,185,70,.62) }
/* legal-move dot on an empty square; a ring on a capturable square (Lichess). */
.dc-board .sq.dot::after{ content:""; position:absolute; inset:0; margin:auto; width:30%; height:30%;
  border-radius:50%; background:rgba(0,0,0,.16) }
.dc-board .sq.cap{ box-shadow:inset 0 0 0 4px rgba(0,0,0,.16) }
/* ENEMY KING IN CHECK/MATE-IN-PROGRESS (.chk): the king the player is ATTACKING.
   Deliberately a SOFT pulsing red GLOW from the centre — NOT a hard inset ring —
   so it reads as "this king is under fire" and is unmistakably distinct from the
   wrong-move cue below (a hard ring + flat red fill = "you erred"). Reserving the
   hard-ring red for the error keeps the two red meanings from colliding, and the
   centre-glow never competes with the .last / .sel rings either. Brand crimson
   (var(--crimson)) instead of raw iOS red so it sits in the parchment palette. */
.dc-board .sq.chk{ box-shadow:inset 0 0 14px 2px color-mix(in srgb, var(--crimson) 78%, transparent);
  background-color:color-mix(in srgb, var(--crimson) 30%, transparent); animation:dc-check .9s ease-in-out infinite }
@keyframes dc-check{ 0%,100%{ box-shadow:inset 0 0 10px 1px color-mix(in srgb, var(--crimson) 55%, transparent) }
  50%{ box-shadow:inset 0 0 16px 3px color-mix(in srgb, var(--crimson) 90%, transparent) } }
/* WRONG-move flash: a bold red overlay that pulses on the bad target square — the
   sharp HARD ring is the "no", visually opposite to the soft check glow above. */
.dc-board .sq.dc-wrong{ box-shadow:inset 0 0 0 4px #ff3b30; animation:dc-wrong .36s ease }
.dc-board .sq.dc-wrong::before{ content:""; position:absolute; inset:0; background:rgba(255,59,48,.5); z-index:1 }
@keyframes dc-wrong{ 0%{ filter:brightness(1) } 50%{ filter:brightness(1.25) } 100%{ filter:brightness(1) } }
/* CHECKMATE marker (owner ask 2026-06-12): on solve, the mated king's square turns
   a celebratory green (overriding the red in-check overlay) and a small green ✓
   badge pops in its corner — an unmistakable "puzzle complete" cue. */
.dc-board .sq.dc-mated{ box-shadow:inset 0 0 0 4px #1f9d57 !important;
  background-color:color-mix(in srgb, #2fae67 52%, transparent) !important; animation:none !important }
.dc-board .sq.dc-mated .dc-mate-badge{ position:absolute; z-index:6; top:5%; right:5%;
  width:42%; height:42%; max-width:24px; max-height:24px; border-radius:50%; display:grid; place-items:center;
  background:#1f9d57; color:#fff; font-weight:800; line-height:1; font-size:clamp(10px,3.2vw,14px);
  box-shadow:0 1px 5px rgba(0,0,0,.4); border:1.5px solid #fff; animation:dc-mate-pop .42s cubic-bezier(.2,1.6,.3,1) }
@keyframes dc-mate-pop{ 0%{ transform:scale(0); opacity:0 } 60%{ transform:scale(1.25) } 100%{ transform:scale(1); opacity:1 } }
@media (prefers-reduced-motion:reduce){ .dc-board .sq.dc-mated .dc-mate-badge{ animation:none } }

/* a piece on the source square is hidden while its floating copy slides. */
.dc-board .sq.dc-hidden-piece{ background-image:none !important }
.dc-board .sq.dc-hidden-piece.l{ background:#efe2c4 } .dc-board .sq.dc-hidden-piece.d{ background:#b07a4f }
/* the floating slide piece: absolutely positioned over the board, animates transform. */
.dc-board .dc-float{ position:absolute; z-index:5; pointer-events:none;
  background-repeat:no-repeat; background-position:center; background-size:88%; will-change:transform }
/* DRAG (task 2/7): the piece carried under the pointer. Slightly ENLARGED with a
   soft shadow (Lichess/Chess.com feel) so it reads as "lifted". top/left are set
   per-frame in JS; transform only scales (kept on the GPU for 60fps). */
.dc-board .dc-float.dc-dragging{ z-index:8; transform:scale(1.18);
  filter:drop-shadow(0 6px 10px rgba(0,0,0,.45)); transition:none; cursor:grabbing }
/* ENEMY-KING WIGGLE (task 4): on Play, the king being mated wiggles once (~1.4s,
   a few small rotate/translate oscillations) so the target is obvious. KING PIECE
   ONLY — the wiggle now rides on a transient FLOATING piece (.dc-king-float, an
   overlay positioned over the king's square in JS) so the board TILE never moves;
   the real piece on that square is hidden for the duration (.dc-hidden-piece).
   transform-origin centers the rotation on the piece. Disabled under reduce (below). */
/* A QUICK ~0.3s shake (was 1.4s — it read as the screen "locking" after Play).
   pointer-events:none so the float never eats a tap on the king's square. */
.dc-board .dc-float.dc-king-float{ z-index:6; pointer-events:none;
  animation:dc-king-wiggle .3s ease-in-out; transform-origin:50% 60% }
@keyframes dc-king-wiggle{
  0%   { transform:rotate(0) }
  25%  { transform:rotate(-9deg) }
  55%  { transform:rotate(7deg) }
  80%  { transform:rotate(-4deg) }
  100% { transform:rotate(0) }
}

/* illegal-move shake on the whole board. */
.dc-board.dc-shake{ animation:dc-shake .3s ease }
@keyframes dc-shake{ 0%,100%{ transform:translateX(0) } 20%,60%{ transform:translateX(-5px) } 40%,80%{ transform:translateX(5px) } }

.dc-actions{ display:flex; gap:10px; justify-content:center; margin:10px 0 4px }
/* secondary actions — padded to a ~40px tap height so they're thumb-friendly. */
.dc-hint,.dc-reset{ font:inherit; font-size:12.5px; font-weight:500; color:var(--ink-soft);
  background:var(--tile); border:1px solid var(--line); border-radius:999px; padding:9px 16px; cursor:pointer; transition:.15s ease }
.dc-hint:hover,.dc-reset:hover{ color:var(--crimson); border-color:var(--crimson); transform:translateY(-1px) }
.dc-hint-cost{ opacity:.7; font-size:11px }
/* HINT-USED / disabled button states (task 2 / task 6): dimmed, no hover lift, not
   clickable. .dc-hint-used (and any disabled action) reads clearly as "already used"
   so the player knows Hint is spent until the next move re-arms it. */
.dc-hint:disabled,.dc-reset:disabled,.dc-hint.dc-hint-used{
  opacity:.5; cursor:default; }
.dc-hint:disabled:hover,.dc-reset:disabled:hover,.dc-hint.dc-hint-used:hover{
  color:var(--ink-soft); border-color:var(--line); transform:none }
/* Reserve TWO lines always: the status copy is one line on wide cards but wraps
   to two on a ~280px mobile board. Locking it to a 2-line min-height keeps the
   live shell's below-board height constant across viewports, so the static
   prelude's matching ::after reservation cancels the Play-press shift on BOTH
   mobile and desktop (not just one). */
.dc-status{ font-size:13.5px; color:var(--ink-soft); margin:6px 0 0; min-height:2.6em }
.dc-status.win{ color:var(--gold); font-weight:600 }
.dc-status.warn{ color:var(--crimson) }
/* "Share your solve" — appears under the win status (chess-play addShareButton) */
.dc-share{ font:inherit; font-family:var(--ui); font-size:12.5px; font-weight:600; cursor:pointer;
  margin:8px auto 0; display:inline-block; padding:6px 16px; border-radius:999px;
  border:1px solid var(--crimson); color:var(--crimson); background:transparent }
.dc-share:hover{ background:var(--crimson); color:#fff }
.dc-share:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }

/* inline per-puzzle leaderboard (rendered on solve) */
.dc-board-lb{ margin:14px auto 0; max-width:340px; text-align:left }
.dc-lb-head{ font-size:11px; font-weight:600; letter-spacing:.1em; text-transform:uppercase;
  color:var(--crimson); text-align:center; margin:0 0 8px }
.dcl-status{ font-size:13px; color:var(--ink-soft); text-align:center; padding:10px 0 }
.dcl-status.dcl-err{ color:var(--crimson) }
.dcl-avg{ margin:7px 0 0; font-family:var(--ui); font-size:11.5px; color:var(--ink-soft);
  text-align:center; font-variant-numeric:tabular-nums }
.dcl-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px }
.dcl-row{ display:grid; grid-template-columns:2ch 1fr auto auto; align-items:center; gap:8px;
  padding:6px 10px; border-radius:8px; font-size:13.5px; background:color-mix(in srgb, var(--ink) 3%, transparent) }
.dcl-row.me{ background:color-mix(in srgb, var(--gold) 18%, transparent); font-weight:600 }
.dcl-rank{ color:var(--ink-soft); font-variant-numeric:tabular-nums }
.dcl-name{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.dcl-pen{ font-size:11px; color:var(--crimson) }
.dcl-time{ font-variant-numeric:tabular-nums; font-weight:600 }
/* compact-board "See full board" expander (Anagram Ladder / Daily Decode top-3) */
.dcl-more{ display:block; margin:8px auto 0; padding:4px 6px; border:0; background:none; cursor:pointer;
  font-family:var(--ui); font-size:12.5px; font-weight:600; color:var(--crimson) }
.dcl-more:hover{ text-decoration:underline }
.dcl-more:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px; border-radius:6px }
/* breathing room between the inline board and the "Practice again" button below it */
.ag-board + .ag-btn{ margin-top:14px }

@media (prefers-reduced-motion: reduce){
  /* Kill motion, but KEEP the wrong-move red overlay visible (just no pulse) —
     the JS also snaps slides instead of animating under reduced-motion. The
     king-wiggle is fully suppressed (JS also early-returns under reduce, so no
     float is even created). */
  .dc-flash.pen,.dc-board .sq.chk,.dc-board .sq.dc-wrong,.dc-board.dc-shake,
  .dc-board .dc-float.dc-king-float{ animation:none; transform:none }
  .dc-board .dc-float.dc-dragging{ filter:none }   /* keep the drag, just drop the shadow flourish */
  .dc-play:hover,.dc-hint:hover,.dc-reset:hover,.dc-cal:hover{ transform:none }
  /* EXPAND still happens (task 1) but WITHOUT the animated grow/fade — it snaps to
     full so there's no bounce/slide; the board is just immediately full size. */
  .dc-board-area{ transition:none }
  .daily-chess .dc-bar, .daily-chess .dc-actions, .daily-chess .dc-status{ transition:none }
  .daily-chess:not(.dc-expanded) .dc-bar,
  .daily-chess:not(.dc-expanded) .dc-actions,
  .daily-chess:not(.dc-expanded) .dc-status{ opacity:1; transform:none }
}

/* ---- YouTube video posts (type:youtube — voteable feed cards with an embed) ---- */
.feed-type.yt{ color:#c00 }
.yt-embed{ display:block; position:relative; width:100%; aspect-ratio:16/9; margin:6px 0 10px; padding:0; border:0;
  cursor:pointer; background:#000; border-radius:10px; overflow:hidden }
.yt-thumb{ width:100%; height:100%; object-fit:cover; display:block; transition:transform .3s ease, filter .2s ease }
.yt-embed:hover .yt-thumb{ transform:scale(1.04); filter:brightness(.88) }
.yt-play{ position:absolute; inset:0; margin:auto; width:62px; height:44px; border-radius:12px; background:rgba(200,0,0,.92);
  color:#fff; font-size:18px; display:grid; place-items:center; pointer-events:none; transition:background .2s ease }
.yt-embed:hover .yt-play{ background:#c00 }
.yt-badge{ position:absolute; top:10px; right:10px; font-size:10px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:#fff; background:rgba(0,0,0,.6); padding:3px 8px; border-radius:999px; pointer-events:none }
.yt-embed iframe{ position:absolute; inset:0; width:100%; height:100%; border:0 }
/* full description/transcript stays in the HTML (SEO) but is visually clamped */
.feed-card.youtube .yt-text{ display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; white-space:normal }

/* ---- editing-blog link cards (type:"blog"/"article") -----------------------
   A clean, on-brand link card mirroring the Final Boss Editing blog index: a 16/9
   cover image (reserved box → no layout shift) with a small "Blog" badge, then a
   Garamond title, a 2-line excerpt, and a meta/CTA row. The WHOLE body links OUT to
   finalbossediting.com (target=_blank); it keeps the standard vote rail so blog
   superposts are still up/down votable. No "public domain" wording anywhere. */
.feed-card.blog{ padding:0; overflow:hidden; }            /* cover bleeds to the card edge */
/* the rail keeps its inset-panel look; cancel the negative margins the default rule
   adds (this card has no padding) so the rail still fills the card height cleanly. */
.feed-card.blog:not(.compact) .vote{ margin:0; border-radius:13px 0 0 13px; }
/* HORIZONTAL layout: a SQUARE cover on the left, text on the right — a leaner feed
   pop-in (image adjacent to the title/excerpt/meta) instead of a tall cover-on-top. */
.blog-link{ display:flex; align-items:center; gap:14px; padding:12px 14px;
  text-decoration:none; color:inherit; min-width:0; }
.blog-cover{ position:relative; flex:0 0 auto; width:128px; aspect-ratio:1/1; border-radius:10px;
  background:linear-gradient(135deg, var(--paper-2), color-mix(in srgb, var(--crimson) 14%, var(--tile))); overflow:hidden; }
.blog-cover-img{ width:100%; height:100%; object-fit:cover; display:block;
  transition:transform .3s ease, filter .2s ease; }
.feed-card.blog:hover .blog-cover-img{ transform:scale(1.04); filter:brightness(.94); }
/* placeholder (no image, or image failed → .feed-cover--ph added by onerror): the
   square stays put with a subtle brand diamond, so a missing cover reads clean. */
.blog-cover.feed-cover--ph::after{ content:"\25C7"; position:absolute; inset:0; display:grid; place-items:center;
  font-family:var(--display); font-size:24px; color:color-mix(in srgb, var(--gold) 65%, transparent); }
.blog-badge{ position:absolute; top:8px; left:8px; z-index:1;
  font-family:var(--ui); font-size:9px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:#fff; background:color-mix(in srgb, var(--crimson) 90%, #000); padding:2px 7px; border-radius:999px;
  box-shadow:0 4px 12px -6px rgba(0,0,0,.5); }
.blog-body{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:5px; padding:0; }
.blog-title{ display:block; font-family:var(--display); font-weight:600; font-size:18px; line-height:1.22;
  color:var(--ink); margin:0; transition:color .15s ease; }
.feed-card.blog:hover .blog-title{ color:var(--crimson); }
.blog-desc{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
  font-size:13.5px; line-height:1.5; color:var(--ink-soft); margin:0; }
.blog-meta{ display:flex; gap:12px; flex-wrap:wrap; align-items:center;
  font-size:12px; color:var(--ink-soft); margin-top:2px; }
.blog-more{ margin-left:auto; font-weight:600; color:var(--crimson);
  border-bottom:1px solid color-mix(in srgb, var(--crimson) 45%, transparent); padding-bottom:1px; }
.feed-card.blog:hover .blog-more{ border-bottom-color:var(--crimson); }
/* compact (homepage teaser) + small screens: a smaller square so text keeps room */
.feed-card.blog.compact .blog-cover{ width:96px; }
.feed-card.blog.compact .blog-title{ font-size:16px; }
@media (max-width:560px){
  .blog-cover{ width:84px; border-radius:8px; }
  .blog-title{ font-size:15px; }
  .blog-desc{ -webkit-line-clamp:3; font-size:13px; }
}
@media (prefers-reduced-motion: reduce){
  .feed-card.blog:hover .blog-cover-img{ transform:none; }
}

/* ---- "Back to top" pill (feed views) — fixed bottom-right, brand crimson,
        hidden until the top of the page scrolls out of view (toggled by
        /feed/back-to-top.js). Matches the rounded-pill aesthetic of
        .home-link / .lb-tab. Respects safe-area insets. ---- */
.back-to-top{
  position:fixed; z-index:60;
  right:calc(env(safe-area-inset-right,0px) + 18px);
  bottom:calc(env(safe-area-inset-bottom,0px) + 18px);
  font:inherit; font-family:var(--ui); font-size:12.5px; font-weight:600;
  letter-spacing:.04em; text-transform:uppercase;
  display:inline-flex; align-items:center; gap:6px;
  color:#fff; background:var(--crimson);
  border:1px solid color-mix(in srgb, var(--crimson) 70%, #000);
  border-radius:999px; padding:9px 15px; cursor:pointer;
  box-shadow:0 10px 26px -12px var(--shadow);
  opacity:0; transform:translateY(10px); pointer-events:none;
  transition:opacity .25s ease, transform .25s ease, background .15s ease;
}
.back-to-top span{ font-size:14px; line-height:1 }
.back-to-top.is-visible{ opacity:.92; transform:none; pointer-events:auto }
.back-to-top:hover{ opacity:1; transform:translateY(-1px) }
.back-to-top:active{ transform:translateY(0) scale(.96) }
@media (prefers-reduced-motion: reduce){
  .back-to-top{ transition:opacity .15s ease }
  .back-to-top.is-visible{ transform:none }
  .back-to-top:hover{ transform:none }
  .back-to-top:active{ transform:none }
}

/* persistent "← Games" home pill (feed/feed-tail.js) — bottom-LEFT companion to
   the crimson "↑ Top" pill, shown while reading the feed appended under a post
   so the header scrolling away never strands the reader. Calm parchment so the
   two pills read as distinct (one takes you up, one takes you home). */
.to-games{
  position:fixed; z-index:60;
  left:calc(env(safe-area-inset-left,0px) + 18px);
  bottom:calc(env(safe-area-inset-bottom,0px) + 18px);
  font:inherit; font-family:var(--ui); font-size:12.5px; font-weight:600;
  letter-spacing:.04em; text-transform:uppercase;
  display:inline-flex; align-items:center; gap:6px;
  color:var(--ink); background:color-mix(in srgb, var(--paper) 86%, transparent);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid var(--line); border-radius:999px; padding:9px 15px;
  text-decoration:none; box-shadow:0 10px 26px -14px var(--shadow);
  transition:transform .18s ease, border-color .18s ease, color .18s ease;
}
.to-games span{ font-size:14px; line-height:1 }
.to-games:hover{ color:var(--crimson); border-color:var(--crimson); transform:translateY(-1px) }
.to-games:active{ transform:translateY(0) scale(.96) }
@media (prefers-reduced-motion: reduce){ .to-games{ transition:border-color .15s ease } .to-games:hover,.to-games:active{ transform:none } }

/* "Keep exploring the Feed" — the stream appended under a post page
   (functions/_render.js postDoc + feed/feed-tail.js). The section just frames
   the heading; the cards inside reuse the normal .feed-card styles above. */
.feed-tail{ margin-top:18px }
.feed-tail-h{ font-family:var(--display); font-weight:500; font-size:clamp(22px,4vw,30px);
  line-height:1.1; letter-spacing:-.01em; margin:2px 0 16px; color:var(--ink) }
.feed-tail-h em{ font-style:italic; color:var(--crimson) }

/* =====================================================================
   HISTORY QUIZ + ART MATCH — the two "small" in-feed dailies. Both spawn as a
   compact pill (.hq-mini / .am-mini) that EXPANDS on Play (history-quiz-play.js /
   art-match-play.js) into the timed game, then shows the solve-time board
   (.dc-board-lb / .dcl-* — shared with chess/mystery). Brand parchment/crimson,
   gold accents, dark-mode + reduced-motion aware.
   ===================================================================== */
.feed-card.daily-history, .feed-card.daily-art{ --hq-green:#1f6d4c }
@media (prefers-color-scheme: dark){ .feed-card.daily-history, .feed-card.daily-art{ --hq-green:#4aa97c } }
.hq-inner, .am-inner{ display:flex; flex-direction:column; gap:2px }
/* .cp-inner centres its column (align-items:center), so the host would shrink-wrap
   its content and the compact pill sat LEFT-of-centre. Stretch the host + make the
   pill full-width so it spans + centres in the card, matching the chess Zip Mate
   pill (.dc-host/.dc-mini do the same). On the post page the host is text-align:left
   (rule below) — stretch there is harmless. */
.hq-host, .am-host{ align-self:stretch; width:100% }
/* compact spawn pill (mirrors the chess dc-mini layout) */
.hq-mini{ display:flex; align-items:center; gap:12px; width:100%; margin:8px 0 2px; padding:10px 12px;
  border:1px solid var(--line); border-radius:12px; background:color-mix(in srgb, var(--tile) 60%, transparent) }
.hq-mini-icon{ flex:0 0 auto; width:40px; height:40px; display:grid; place-items:center; font-size:21px; line-height:1;
  border-radius:10px; border:1px solid color-mix(in srgb, var(--gold) 45%, var(--line));
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 14%, var(--tile)), var(--tile)) }
.hq-mini-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px }
.hq-mini-title{ font-family:var(--display,serif); font-weight:700; font-size:15.5px; color:var(--ink) }
.hq-mini-meta{ font-family:var(--ui); font-size:11.5px; color:var(--ink-soft); overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* solved-today calm state */
.daily-history.hq-solved .hq-mini-icon::after, .daily-art.am-solved .hq-mini-icon::after{ content:"" }
.daily-history.hq-solved .dc-play::before, .daily-art.am-solved .dc-play::before{ content:"\2713 " }

/* ---- History Quiz game ---- */
.hq-game{ padding:6px 2px 2px }
.hq-progress{ display:flex; align-items:center; justify-content:space-between; gap:10px; font-family:var(--ui);
  font-size:11px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin:0 0 8px }
.hq-pen, .am-pen{ color:var(--crimson); font-variant-numeric:tabular-nums }
.hq-q{ font-family:var(--display,serif); font-size:clamp(16px,2.6vw,19px); line-height:1.35; color:var(--ink); margin:0 0 14px }
/* the drop zone reads as an active target at rest (faint crimson wash inside the
   dashed rule) so it's obviously "drop here", not just an empty outline. */
.hq-target{ display:grid; place-items:center; min-height:58px; border:2px dashed color-mix(in srgb, var(--crimson) 42%, var(--line));
  border-radius:12px; margin:0 0 16px; padding:9px 12px; text-align:center;
  background:color-mix(in srgb, var(--crimson) 4%, transparent);
  transition:background .15s ease, border-color .15s ease }
/* the hint carries a small downward cue so the gesture reads at a glance */
.hq-target-hint{ font-family:var(--ui); font-size:12.5px; color:var(--ink-soft) }
.hq-target-hint::before{ content:"\2193 "; opacity:.7 }
.hq-target.hq-filled{ border-style:solid; border-color:var(--hq-green); background:color-mix(in srgb, var(--hq-green) 12%, transparent) }
.hq-ans{ font-family:var(--display,serif); font-weight:700; font-size:16px; color:var(--hq-green) }
.hq-target.hq-shake{ animation:hqShake .42s ease }
@keyframes hqShake{ 0%,100%{ transform:translateX(0) } 20%,60%{ transform:translateX(-6px) } 40%,80%{ transform:translateX(6px) } }
.hq-chips{ display:flex; flex-wrap:wrap; gap:9px; justify-content:center }
/* chips sit on a real surface with a faint lift so they read as grab-able tokens,
   not flat text — reinforcing the "pick me up and drag" affordance. */
.hq-chip{ font-family:var(--ui); font-weight:600; font-size:14px; color:var(--ink); cursor:grab; touch-action:none;
  border:1px solid var(--line); border-radius:999px; padding:9px 16px; background:var(--paper,transparent);
  box-shadow:0 1px 2px -1px var(--shadow); transition:border-color .15s ease, background .15s ease, box-shadow .15s ease, transform .05s ease; user-select:none }
.hq-chip:hover{ border-color:var(--crimson); box-shadow:0 4px 10px -5px var(--shadow); transform:translateY(-1px) }
.hq-chip:active{ transform:translateY(0) scale(.97) }
.hq-chip.hq-dragging{ position:relative; z-index:5; cursor:grabbing; border-color:var(--gold); box-shadow:0 12px 26px -12px var(--shadow) }
.hq-chip.hq-right{ border-color:var(--hq-green); background:color-mix(in srgb, var(--hq-green) 14%, transparent); color:var(--hq-green) }
.hq-chip.hq-wrong{ border-color:var(--crimson); background:color-mix(in srgb, var(--crimson) 12%, transparent); animation:hqShake .42s ease }
.hq-chip.hq-locked{ pointer-events:none; opacity:.55 }
.hq-chip.hq-locked.hq-right{ opacity:1 }
/* done + board */
.hq-done, .am-done-panel{ padding:6px 2px 2px }
.hq-done-h, .am-done-h{ font-family:var(--display,serif); font-size:17px; color:var(--ink); margin:0 0 14px; text-align:center }
.hq-done-h b, .am-done-h b{ color:var(--hq-green); font-variant-numeric:tabular-nums }
/* board heading — gives the bare .dcl-list an actual title (the factory emits no
   header), matching the chess board's .dc-lb-head so the board reads as a board. */
.hq-board-h, .am-board-h{ font-family:var(--ui); font-size:11px; font-weight:600; letter-spacing:.1em;
  text-transform:uppercase; color:var(--crimson); text-align:center; margin:0 0 2px }
.hq-foot, .am-foot{ margin:12px 0 0; font-family:var(--ui); font-size:12.5px; text-align:center }
.hq-foot a, .am-foot a{ color:var(--crimson); text-decoration:none; font-weight:600 }
.hq-foot a:hover, .am-foot a:hover{ text-decoration:underline }

/* ---- Art Match game ---- */
.am-status{ font-family:var(--ui); font-size:12.5px; color:var(--ink-soft); text-align:center; margin:6px 0 12px }
.am-cols{ display:grid; grid-template-columns:minmax(110px,1fr) 1.25fr; gap:12px; align-items:start }
.am-artists{ display:flex; flex-direction:column; gap:9px }
.am-artist{ font-family:var(--display,serif); font-weight:600; font-size:14.5px; color:var(--ink); text-align:left; cursor:pointer;
  border:1px solid var(--line); border-radius:10px; padding:11px 12px; background:var(--paper,transparent);
  touch-action:none; user-select:none; transition:border-color .15s ease, background .15s ease, transform .05s ease }
.am-artist:hover{ border-color:var(--crimson) }
.am-artist.am-sel{ border-color:var(--gold); background:color-mix(in srgb, var(--gold) 13%, transparent); box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 30%, transparent) }
.am-artist.am-dragging{ position:relative; z-index:6; cursor:grabbing; border-color:var(--gold); box-shadow:0 12px 26px -12px var(--shadow) }
.am-artist.am-done{ border-color:var(--hq-green); background:color-mix(in srgb, var(--hq-green) 12%, transparent); color:var(--hq-green); pointer-events:none }
/* matched artist gets a leading ✓ so the resolved row reads as "done" at a glance */
.am-artist.am-done::before{ content:"\2713\00a0"; font-weight:700 }
.am-artist.am-wrong, .am-art.am-wrong{ border-color:var(--crimson); animation:hqShake .42s ease }
.am-arts{ display:flex; flex-direction:column; gap:10px }
.am-art{ display:flex; align-items:center; gap:11px; cursor:pointer; text-align:left; border:1px solid var(--line);
  border-radius:12px; padding:8px; background:color-mix(in srgb, var(--tile) 55%, transparent);
  transition:border-color .15s ease, background .15s ease }
.am-art:hover{ border-color:var(--crimson) }
/* the painting under a dragged artist chip — gold "drop here" highlight (art-match-play.js
   toggles .am-over via rect hit-testing as the chip moves) */
.am-art.am-over{ border-color:var(--gold); background:color-mix(in srgb, var(--gold) 9%, var(--tile));
  box-shadow:0 0 0 2px color-mix(in srgb, var(--gold) 38%, transparent) }
.am-art-img{ flex:0 0 auto; width:64px; height:64px; border-radius:8px; overflow:hidden; background:var(--paper-2,#eee);
  display:grid; place-items:center }
.am-art-img img{ width:100%; height:100%; object-fit:cover; display:block }
.am-art-img.am-img-ph::after{ content:"\1F5BC"; font-size:24px; opacity:.5 }
.am-art-title{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px;
  font-family:var(--ui); font-size:13px; font-weight:600; color:var(--ink); line-height:1.3 }
/* solved attribution: the painting keeps its title; the artist drops in beneath
   it in green as a small "✓ <artist>" credit (italic serif to nod to the gallery
   wall-label feel) instead of clobbering the title. */
.am-art-by{ font-family:var(--display,serif); font-style:italic; font-weight:500; font-size:12px;
  color:var(--hq-green) }
.am-art.am-done{ border-color:var(--hq-green); background:color-mix(in srgb, var(--hq-green) 12%, transparent); pointer-events:none }
.am-art.am-done .am-art-title{ color:var(--hq-green) }
.am-progress{ display:flex; align-items:center; justify-content:center; gap:10px; margin:12px 0 0; font-family:var(--ui);
  font-size:12px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-soft) }
.am-count{ font-variant-numeric:tabular-nums }
/* the solve-time board on both games reuses .dc-board-lb / .dcl-* (chess/mystery) */
.hq-board, .am-board{ margin:14px 0 0 }
/* post-page hosts */
.hq-post, .am-post{ text-align:center; margin:0 0 8px }
.hq-post .hq-host, .am-post .am-host{ text-align:left }
.hq-post .dc-play, .am-post .dc-play{ margin:6px auto 2px }
/* stack to one column on phones. Keep DOM order (artists first, THEN paintings):
   the flow is "tap an artist, then its painting", so the actor column should lead.
   (Was order:-1 on .am-arts, which surfaced paintings first and inverted the
   gesture's mental model.) */
@media (max-width:460px){ .am-cols{ grid-template-columns:1fr } }
@media (prefers-reduced-motion: reduce){
  .hq-target.hq-shake, .hq-chip.hq-wrong, .am-artist.am-wrong, .am-art.am-wrong{ animation:none }
  .hq-chip:hover, .hq-chip:active{ transform:none } }

/* =====================================================================
   DAILY LOGIC PUZZLE teaser card (Morning Mystery, type:"daily-mystery") — a
   clean link-out pill in the feed: gold-badged icon · kicker/title/tagline ·
   crimson Play CTA. The puzzle itself plays on /games/morning-mystery/ (its own
   solve-time leaderboard), so this card is a single anchor, no in-feed board.
   ===================================================================== */
.feed-card.daily-mystery{ display:block; padding:0; overflow:hidden }
.dm-link{ display:flex; align-items:center; gap:14px; padding:15px 18px; text-decoration:none; color:var(--ink) }
.dm-icon{ flex:0 0 auto; width:44px; height:44px; display:grid; place-items:center; font-size:24px; line-height:1;
  border-radius:12px; border:1px solid color-mix(in srgb, var(--gold) 45%, var(--line));
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 14%, var(--tile)), var(--tile)) }
.dm-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px }
.dm-kicker{ font-family:var(--ui); font-size:10.5px; font-weight:600; letter-spacing:.13em; text-transform:uppercase; color:var(--crimson) }
.dm-title{ font-family:var(--display,serif); font-weight:700; font-size:18px; line-height:1.15; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.dm-sub{ font-family:var(--ui); font-size:12.5px; color:var(--ink-soft); overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.dm-cta{ flex:0 0 auto; font-family:var(--ui); font-weight:600; font-size:13px; color:#fff; background:var(--crimson);
  border-radius:999px; padding:8px 16px; white-space:nowrap; transition:background .15s ease }
.feed-card.daily-mystery:hover{ border-color:var(--crimson) }
.daily-mystery .dm-link:hover .dm-cta{ background:color-mix(in srgb, var(--crimson) 82%, #000) }
/* solved today on this device → the CTA turns green with a ✓ (parity with the strip pill) */
.daily-mystery.dm-solved .dm-cta{ background:#1f6d4c }
.daily-mystery.dm-solved .dm-cta::before{ content:"\2713 " }
@media (max-width:460px){ .dm-icon{ width:40px; height:40px; font-size:21px } .dm-title{ font-size:16.5px } }

/* =====================================================================
   IN-FEED DAILY PUZZLES strip (homepage) — a 2-up grid of compact daily
   shortcut pills (Zip Mate, Wise Word Finder, Anagram Quotes, Hidden Hunt,
   future Morning Mystery). The first pills are ringed as the BONUS ZONE
   (two-game completion bonus). Reworked 2026-06-12; rendered by
   feed/daily-strip.js from the shared registry + the player's order/hidden
   prefs. Deliberately lighter than the arcade tiles.
   ===================================================================== */
.daily-strip{ margin:30px auto 4px; max-width:820px;
  /* solved-state green: brightened in dark mode so the ✓ keeps contrast on dark paper */
  --ds-green:#1f6d4c }
@media (prefers-color-scheme: dark){ .daily-strip{ --ds-green:#4aa97c } }
/* ANTI-CLS — SHARED CONVENTION: any above-the-fold host that paints LATE (its
   module/data lands after first paint) reserves its settled height while loading by
   carrying `data-loading` + `--reserve:<px>`, and its module drops `data-loading`
   (or sets `hidden`) on first paint. Without this, the late 0→Npx paint shoves
   everything below it down (the "few seconds later, the page jumps" bug). One rule,
   reusable — see .daily-strip below for the canonical use. */
[data-loading]{ min-height:var(--reserve, 0px) }
/* ANTI-CLS (weave): the feed-woven ambient hosts (relocated into the stream by
   feed/feed-ambient.js) stay HIDDEN until placed — so they can NEVER briefly squat
   at full height above the feed before the weave runs (the same late-paint shove,
   one layer down; and the owner asked they never squat there — report 2026-06-10).
   Hidden by DEFAULT from byte 0 (a module setting host.hidden=false can't reveal
   them); feed-ambient adds .fa-placed only once it seats a host between two stream
   cards. Both surfaces that carry these hosts (homepage + /feed/ SSR) load
   feed-ambient, so "hidden until placed" is the complete state machine. */
.daily-event-host:not(.fa-placed), .anagram-host:not(.fa-placed),
.decode-host:not(.fa-placed), .quest-host:not(.fa-placed),
.learn-word-host:not(.fa-placed), .crossword-host:not(.fa-placed),
.goals-host:not(.fa-placed), .widgets-host:not(.fa-placed){ display:none }

/* ---- Daily Mini Crossword (feed/crossword-daily.js) — TRUE 5×5 blocked
        crossword: numbered grid + ACROSS/DOWN clue lists (ponderclub / NYT-Mini
        style). Owns the "free daily mini crossword" SEO term in the page copy. ---- */
.cw-h{ font-family:var(--display); font-weight:600; font-size:17px; line-height:1.25; margin:6px 0 6px }
.cw-h em{ color:var(--crimson); font-style:normal }
.cw-sub{ color:var(--ink-soft); font-size:13px; line-height:1.45; margin:0 0 12px }
.cw-loading{ color:var(--ink-soft); font-size:14px; margin:10px 0 }
/* the active-clue banner above the grid (ponderclub shows the current clue prominently) */
.cw-banner{ display:flex; gap:8px; align-items:baseline; min-height:38px; margin:0 0 10px; padding:9px 12px;
  border:1px solid var(--line); border-left:3px solid var(--crimson); border-radius:8px;
  background:color-mix(in srgb, var(--crimson) 5%, var(--tile)); font-size:13.5px; line-height:1.35; color:var(--ink) }
.cw-banner b{ color:var(--crimson); font-family:var(--ui); font-weight:700; flex:0 0 auto }
.cw-banner:empty{ display:none }
.cw-grid{ display:grid; grid-template-columns:repeat(var(--cw-n,5),1fr); gap:3px; width:100%; max-width:288px; margin:0 0 14px }
.cw-cell{ position:relative; aspect-ratio:1/1; background:var(--tile,#fff); border:1px solid var(--line); border-radius:4px;
  transition:background .12s ease, box-shadow .12s ease }
/* BLACK (blocked) squares — solid, no input */
.cw-cell.cw-black{ background:var(--ink,#1c1814); border-color:var(--ink,#1c1814); border-radius:4px }
@media (prefers-color-scheme: dark){ .cw-cell.cw-black{ background:#0c0a08; border-color:#0c0a08 } }
/* current ENTRY highlight (whole across/down run) */
.cw-cell.cw-hl{ background:color-mix(in srgb, var(--crimson) 10%, var(--tile)) }
.cw-cell.cw-hl input{ background:transparent }
/* current CELL — stronger gold highlight */
.cw-cell.cw-cur{ box-shadow:inset 0 0 0 2px var(--crimson) }
.cw-cell.cw-cur input{ background:color-mix(in srgb, var(--gold) 26%, transparent) }
.cw-cell input{ width:100%; height:100%; border:0; background:transparent; text-align:center; padding:0;
  font-family:var(--display); font-weight:700; font-size:clamp(15px,4.6vw,21px); text-transform:uppercase; color:var(--ink); caret-color:var(--crimson) }
.cw-cell input:focus{ outline:0 }
/* Check / Reveal feedback — NB: .ok / .bad live on the INPUT (cellAt returns the input),
   so target `.cw-cell input.ok`, NOT `.cw-cell.ok input` (a prior bug had it the wrong way). */
.cw-cell input.ok{ background:color-mix(in srgb, var(--cw-ok,#2e7d32) 18%, var(--tile)); color:var(--cw-ok,#2e7d32) }
.cw-cell input.bad{ background:color-mix(in srgb, var(--crimson) 16%, var(--tile)); color:var(--crimson) }
@media (prefers-color-scheme: dark){ .cw-cell input.ok{ --cw-ok:#5fcf6a } }
/* clue NUMBER, tiny, top-left of a numbered cell */
.cw-num{ position:absolute; top:1px; left:2px; font-size:8px; font-weight:700; color:var(--ink-soft); font-family:var(--ui); pointer-events:none; z-index:1 }
.cw-actions{ display:flex; flex-wrap:wrap; gap:8px; margin:0 0 14px }
.cw-btn{ font:inherit; font-family:var(--ui); font-weight:600; font-size:13px; padding:8px 14px; border-radius:10px;
  border:1px solid var(--line); background:var(--tile,#fff); color:var(--ink); cursor:pointer }
.cw-btn:hover{ border-color:var(--crimson); color:var(--crimson) }
.cw-clue:focus-visible, .cw-btn:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }
.cw-msg{ margin:0 0 12px; min-height:1.2em; font-size:13px; font-weight:600; color:var(--crimson) }
.cw-msg[hidden]{ display:none }
.cw-msg.warn{ color:var(--crimson) }
.cw-msg.ok{ color:var(--ink-soft); font-weight:500 }
/* ACROSS + DOWN clue lists — two columns on desktop, stacked on mobile */
.cw-clues{ display:grid; grid-template-columns:1fr 1fr; gap:14px }
@media (max-width:520px){ .cw-clues{ grid-template-columns:1fr; gap:10px } }
.cw-cluelist{ min-width:0 }
.cw-clues-h{ font-family:var(--ui); font-weight:700; font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--crimson); margin:0 0 4px }
.cw-clue{ display:flex; gap:8px; align-items:baseline; width:100%; min-height:36px; text-align:left; font:inherit; font-size:13px; line-height:1.35;
  padding:7px 9px; border:0; background:transparent; border-radius:8px; cursor:pointer; color:var(--ink) }
.cw-clue:hover{ background:color-mix(in srgb, var(--ink) 5%, transparent) }
.cw-clue.active{ background:color-mix(in srgb, var(--crimson) 11%, transparent); color:var(--ink) }
.cw-clue b{ color:var(--crimson); font-family:var(--ui); flex:0 0 auto; min-width:14px }
/* the solve payoff — a gold "achievement" card that breathes in once */
.cw-reward{ background:color-mix(in srgb, var(--gold) 14%, var(--tile)); border:1px solid color-mix(in srgb, var(--gold) 55%, var(--line));
  border-radius:12px; padding:13px 15px; margin:14px 0 4px; animation:cwReward .45s cubic-bezier(.2,.7,.3,1) both }
@keyframes cwReward{ from{ opacity:0; transform:translateY(6px) } to{ opacity:1; transform:none } }
.cw-done{ font-family:var(--ui); font-size:14px; margin:0 0 8px }
.cw-xp{ color:var(--gold); font-weight:700 }
.cw-quote{ font-family:var(--display); font-style:italic; color:var(--ink); font-size:16px; line-height:1.4; margin:0 }
.cw-quote b{ color:var(--crimson); font-style:normal }
.cw-quote-who{ display:block; font-style:normal; font-size:13px; color:var(--ink-soft); margin-top:5px }
.cw-share{ margin:12px 0 0; border-color:var(--crimson); color:var(--crimson) }
.cw-board{ margin:14px auto 12px }
@media (prefers-reduced-motion: reduce){ .cw-cell, .cw-reward{ animation:none; transition:none } }
/* the daily strip: ~259px desktop / ~340px the 1-col mobile stack. daily-strip.js
   removes [data-loading] on first paint (and an empty strip — player hid every
   daily — collapses cleanly via `hidden`). */
.daily-strip[data-loading]{ --reserve:259px }
@media (max-width:560px){ .daily-strip[data-loading]{ --reserve:340px } }
/* flex-start (not center) so the Edit pill stays level with the FIRST line of the
   label when "...DAILY PUZZLES · JUN 12, 2026" wraps to two lines on narrow phones. */
.ds-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin:0 0 7px }
.ds-head-label{ font-family:var(--ui); font-size:11px; font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--crimson) }
.ds-date{ color:var(--ink-soft); font-weight:500 }   /* date reads quieter than the label */
.ds-edit{ flex:0 0 auto; font-family:var(--ui); font-size:10.5px; font-weight:600; letter-spacing:.08em;
  text-transform:uppercase; color:var(--ink-soft); text-decoration:none; border:1px solid var(--line);
  border-radius:999px; padding:4px 13px; transition:.15s ease; white-space:nowrap }
.ds-edit:hover{ color:var(--crimson); border-color:var(--crimson); transform:translateY(-1px) }
.ds-zone-note{ margin:0 0 12px; font-family:var(--ui); font-size:12.5px; color:var(--ink-soft); text-align:center }
.ds-zone-note b{ color:var(--crimson); font-weight:700 }

.ds-grid{ display:grid; grid-template-columns:repeat(2, 1fr); gap:10px; position:relative }
@media (max-width:560px){ .ds-grid{ grid-template-columns:1fr } }
/* pills sit ABOVE the shared .ds-zone-frame (which is absolutely positioned behind
   them) so the frame's gold tint shows only in the gaps + outer margin, not over
   the pill faces. */
.ds-grid > .ds-pill{ position:relative; z-index:1 }
/* "odd numbered in-feed puzzles are centered" — a lone last pill (odd count)
   centers in the 2-col grid instead of clinging to the left column. */
@media (min-width:561px){
  .ds-grid > .ds-pill:last-child:nth-child(odd){ grid-column:1 / -1; max-width:calc(50% - 5px); justify-self:center }
}

/* shorter pills (2026-06-12): tighter vertical padding + gap so the strip reads
   as a compact shortcut rail and the "+bonus EXP" note above it carries the eye. */
.ds-pill, .ds-row{ display:flex; align-items:center; gap:11px; width:100%; min-width:0; text-align:left; text-decoration:none;
  font:inherit; cursor:pointer; color:var(--ink); border:1px solid var(--line); border-radius:13px; padding:8px 13px;
  background:color-mix(in srgb, var(--tile) 60%, transparent); transition:.16s ease }
/* a flex item's default min-width:auto let the nowrap name + fixed side column
   blow past the grid track at ~375px, clipping the "Play →"/"0 of 5"/"+40" CTAs
   off the pill's right edge. min-width:0 lets the .ds-text ellipsize instead. */
.ds-pill:hover, .ds-row:hover{ border-color:var(--crimson); transform:translateY(-1px) }
.ds-check{ flex:0 0 auto; width:21px; height:21px; display:grid; place-items:center;
  border:2px solid var(--line); border-radius:6px; font-size:14px; font-weight:700;
  color:var(--ds-green); background:var(--paper,transparent) }
/* the left box doubles as a DRAG GRIP: unsolved pills show faint grip dots (a
   "drag me" cue) that firm up on hover; solved pills show the ✓ instead. The
   grip is the only drag handle, so a tap elsewhere on the pill still plays. */
.ds-grip{ cursor:grab; touch-action:none }
.ds-grip:active{ cursor:grabbing }
.ds-pill:not(.ds-solved):not(.ds-future) .ds-grip::before{ content:"⠿"; color:var(--ink-soft);
  opacity:.4; font-size:13px; line-height:1; font-weight:400 }
.ds-pill:hover:not(.ds-solved):not(.ds-future) .ds-grip::before{ opacity:.72; color:var(--crimson) }
.ds-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; line-height:1.25 }
.ds-name{ font-family:var(--display,serif); font-weight:700; font-size:15px; color:var(--ink);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.ds-sub{ font-family:var(--ui); font-size:11.5px; color:var(--ink-soft,#6a6253);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.ds-mins{ font-family:var(--ui); font-weight:500; font-size:10.5px; color:var(--ink-soft,#6a6253);
  border:1px solid var(--line-2); border-radius:999px; padding:1px 7px; vertical-align:1px }
/* phones: the nowrap+ellipsis .ds-name clips this pill mid-glyph — drop the nicety */
@media (max-width:460px){ .ds-mins{ display:none } }
.ds-side{ flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:5px }
/* the per-pill reward reads in GOLD, not crimson: it's a reward (the site's XP/
   economy color — same as the zone ring + bonus note), so it shouldn't compete
   with the crimson Play CTA right below it. Keeps reward (gold) and action
   (crimson) visually distinct on the pill's right rail. */
.ds-xp{ font-family:var(--ui); font-weight:600; font-size:10px; letter-spacing:.03em; color:var(--gold);
  border:1px solid color-mix(in srgb, var(--gold) 45%, transparent); border-radius:999px; padding:1px 7px;
  white-space:nowrap; font-variant-numeric:tabular-nums }
.ds-cta{ flex:0 0 auto; font-family:var(--ui); font-weight:600; font-size:12.5px; color:#fff;
  background:var(--crimson); border-radius:999px; padding:5px 13px; letter-spacing:.02em;
  font-variant-numeric:tabular-nums; white-space:nowrap }
/* BONUS ZONE: the zone pills are NO LONGER ringed individually — a SINGLE shared
   gold frame (.ds-zone-frame, positioned by daily-strip.js) wraps the whole zone
   so it reads as ONE region you must clear, not N separate "complete me" boxes.
   The pills inside stay visually plain. */
.ds-pill.ds-zone:hover{ border-color:var(--crimson) }
/* the shared frame: one rounded gold rectangle drawn behind + around the zone
   pills (top row on desktop, top stack on mobile). Geometry is measured from the
   live pill rects in JS, so it tracks 2-across vs vertical automatically. */
.ds-zone-frame{ position:absolute; z-index:0; pointer-events:none; border-radius:17px;
  border:1.5px solid color-mix(in srgb, var(--gold) 60%, var(--line));
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 11%, transparent), color-mix(in srgb, var(--gold) 3%, transparent));
  box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 16%, transparent), 0 8px 20px -14px color-mix(in srgb, var(--gold) 50%, transparent);
  transition:top .18s ease, left .18s ease, width .18s ease, height .18s ease }
.ds-zone-frame[hidden]{ display:none }
/* solved state — check + cta + xp all turn green */
.ds-pill.ds-solved .ds-check, .ds-row.ds-solved .ds-check{ border-color:var(--ds-green);
  background:color-mix(in srgb, var(--ds-green) 12%, transparent) }
.ds-pill.ds-solved .ds-cta, .ds-row.ds-solved .ds-cta{ background:#1f6d4c }   /* deep green fill, both modes */
.ds-pill.ds-solved .ds-xp{ color:var(--ds-green); border-color:color-mix(in srgb, var(--ds-green) 45%, transparent) }
/* future ("Morning Mystery — Soon"): a calm teaser, never a dead link */
.ds-pill.ds-future{ cursor:default; opacity:.72 }
.ds-pill.ds-future:hover{ border-color:var(--line); transform:none }
.ds-cta.ds-soon{ color:var(--ink-soft); background:transparent; border:1px solid var(--line) }
/* the Hidden Hunt pill's tally reads as info, not a Play CTA (no jump target) — give
   it the calm tile look so it doesn't promise an action it can't deliver. */
.ds-hunt-cta{ color:var(--ink-soft); background:transparent; border:1px solid var(--line); cursor:help }
/* "how to play" tooltip the hunt pill opens on click (daily-strip.js; body-anchored,
   positioned inline). Calm parchment popover matching the hunt toast. */
.ds-hunt-tip{ position:fixed; z-index:80; font-family:var(--ui,Inter,system-ui,sans-serif);
  font-size:12.5px; line-height:1.35; color:var(--ink,#1c1814); background:var(--paper,#f5efe2);
  border:1px solid var(--crimson,#a31621); border-radius:12px; padding:8px 12px;
  box-shadow:0 8px 22px -10px rgba(0,0,0,.45); animation:dsHuntTip .16s ease-out }
@keyframes dsHuntTip{ from{ opacity:0; transform:translateY(-4px) } to{ opacity:1; transform:none } }

/* SEE ALL / COLLAPSED: only the first `desktopCount`/`mobileCount` pills show;
   the rest are .ds-extra and hidden until "See all" expands the strip inline.
   The button carries the only "there's more" affordance (a chevron + the total)
   so no fade overlays — and obscures — the live Play CTAs on the bottom row. */
.ds-grid.ds-collapsed .ds-pill.ds-extra{ display:none }
.ds-seeall{ display:block; margin:11px auto 0; font-family:var(--ui); font-size:11px; font-weight:600;
  letter-spacing:.07em; text-transform:uppercase; color:var(--ink-soft); background:transparent;
  border:1px solid var(--line); border-radius:999px; padding:7px 18px; cursor:pointer;
  transition:color .15s ease, border-color .15s ease, transform .15s ease }
.ds-seeall::after{ content:" ⌄"; font-size:13px; vertical-align:1px }
.ds-seeall[aria-expanded="true"]::after{ content:" ⌃" }
.ds-seeall:hover{ color:var(--crimson); border-color:var(--crimson); transform:translateY(-1px) }

/* DRAGGING: the picked-up pill lifts (gold rim + shadow + slight scale) and rides
   above its neighbours; live DOM reorder happens underneath as the pointer moves. */
.ds-pill.ds-dragging{ opacity:.88; cursor:grabbing; border-color:var(--gold);
  box-shadow:0 16px 34px -12px var(--shadow); transform:scale(1.025); position:relative; z-index:6 }
.ds-pill.ds-dragging .ds-grip{ cursor:grabbing }

/* ZONE FLASH: when the two-game zone bonus actually banks (xp.js fires
   "fb-bonus-zone"), the zone pills pulse gold — the visual counterpart to the
   EXP "points rush" xp.js bursts toward the top bar at the same moment. */
.ds-pill.ds-zone-flash{ animation:dsZoneFlash 1.5s ease }
@keyframes dsZoneFlash{
  0%{ box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 20%, transparent) }
  28%{ box-shadow:0 0 0 5px color-mix(in srgb, var(--gold) 68%, transparent); transform:translateY(-2px) }
  64%{ box-shadow:0 0 0 3px color-mix(in srgb, var(--gold) 36%, transparent) }
  100%{ box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 20%, transparent) }
}
/* when the two-game zone bonus banks, the whole shared frame pulses gold */
.ds-zone-frame.ds-zone-frame-flash{ animation:dsZoneFrameFlash 1.5s ease }
@keyframes dsZoneFrameFlash{
  0%{ box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 16%, transparent) }
  30%{ box-shadow:0 0 0 6px color-mix(in srgb, var(--gold) 60%, transparent); border-color:var(--gold) }
  66%{ box-shadow:0 0 0 3px color-mix(in srgb, var(--gold) 32%, transparent) }
  100%{ box-shadow:0 0 0 1px color-mix(in srgb, var(--gold) 16%, transparent) }
}

/* the in-feed card a Play button pans to gets a brief gold flash so the eye lands */
.ds-flash{ animation:dsFlash 1.6s ease }
@keyframes dsFlash{ 0%,100%{ box-shadow:none } 16%{ box-shadow:0 0 0 3px color-mix(in srgb, var(--gold) 55%, transparent) } }
/* calm all-done state: a designed closing CARD — closure + the streak (return
   hook) + countdown to tomorrow, never a nag */
.ds-calm{ display:flex; flex-direction:column; gap:3px; text-align:center;
  margin:12px 2px 2px; padding:13px 16px; font-family:var(--ui); font-size:12.5px; line-height:1.5;
  border:1px solid color-mix(in srgb, var(--ds-green,#1f6d4c) 28%, var(--line));
  border-radius:14px; background:color-mix(in srgb, var(--ds-green,#1f6d4c) 7%, transparent) }
.ds-calm-h{ font-family:var(--display,serif); font-weight:700; font-size:16px; color:var(--ds-green,#1f6d4c) }
.ds-streak{ font-weight:700; color:var(--gold,#b8860b); font-variant-numeric:tabular-nums }
.ds-streak-new{ color:var(--ink-soft); font-weight:600 }
.ds-calm-sub{ color:var(--ink-soft) }
.ds-archive{ display:block; margin-top:5px; font-style:normal; color:var(--ink-soft) }
.ds-archive a{ color:var(--crimson); text-decoration:none; font-weight:600 }
.ds-archive a:hover{ text-decoration:underline }
/* wellness checkpoint where fresh feed content ends and encores begin */
.feed-rest{ margin:26px auto; width:100%; max-width:560px; text-align:center; font-family:var(--ui);
  font-size:13px; font-style:italic; color:var(--ink-soft);
  border-top:1px solid var(--line-2); border-bottom:1px solid var(--line-2); padding:13px 8px }
@media (prefers-reduced-motion: reduce){ .ds-row:hover, .ds-pill:hover{ transform:none } .ds-flash{ animation:none }
  .ds-pill.ds-zone-flash{ animation:none } .ds-pill.ds-dragging{ transform:none } .ds-seeall:hover{ transform:none }
  .ds-zone-frame{ transition:none } .ds-zone-frame.ds-zone-frame-flash{ animation:none } }

/* =====================================================================
   DAILY LOGIC PUZZLE subpage (/games/morning-mystery/) — the inline solve-time
   board + the "Keep exploring the Feed" tail (feed/mm-subpage.js). The Morning
   Mysteries app has its OWN palette, so we re-declare the bossgames brand vars
   SCOPED to .mm-tail (they cascade to the feed cards + board inside it) without
   touching the app's :root.
   The host app is PERMANENTLY dark (css/style.css sets `color-scheme: dark` with
   no light variant), so the tail is ALWAYS dark too — keying it off
   prefers-color-scheme made the heading / "See all" / divider render in dark-on-
   parchment colors on the app's dark backdrop (invisible) for light-mode OS users.
   The cards survived (they paint their own --tile background) but the naked text
   on the page background did not. So: dark parchment-on-dark is the only palette.
   ===================================================================== */
.mm-tail{
  --paper:#16120e; --paper-2:#1f1812; --tile:#211a13;
  --ink:#efe6d0; --ink-soft:#a99c84; --crimson:#cf4a43; --gold:#c79a44;
  --line:rgba(239,230,208,.14); --line-2:rgba(239,230,208,.07); --shadow:rgba(0,0,0,.6);
  --display:"EB Garamond",Georgia,serif; --ui:"Inter",system-ui,sans-serif;
  max-width:712px; margin:10px auto 64px; padding:0 16px; color:var(--ink);
  font-family:var(--ui) }
.mm-tail-rule{ display:flex; align-items:center; gap:14px; margin:30px 0 20px; color:var(--gold) }
.mm-tail-rule::before, .mm-tail-rule::after{ content:""; height:1px; background:var(--line); flex:1 }
.mm-tail-rule span{ font-family:var(--display); font-size:18px; letter-spacing:.3em }
.mm-tail-head{ display:flex; align-items:baseline; justify-content:space-between; gap:14px; flex-wrap:wrap; margin:0 0 16px }
.mm-tail-head h2{ font-family:var(--display); font-weight:600; font-size:clamp(20px,3.4vw,26px); margin:0; color:var(--ink) }
.mm-tail-head .home-link{ font-family:var(--ui); font-size:13px; font-weight:500; letter-spacing:.04em; text-transform:uppercase;
  text-decoration:none; color:var(--ink-soft); border:1px solid var(--line); padding:8px 13px; border-radius:999px; white-space:nowrap }
.mm-tail-head .home-link:hover{ color:var(--crimson); border-color:var(--crimson) }
.mm-feed-status{ text-align:center; color:var(--ink-soft); font-size:13px; font-style:italic; margin:14px 0 }
/* inline solve-time board (shown after solve) — reuses the .dc-board-lb/.dcl-* board styles */
.mm-board{ margin:18px 0 0; border:1px solid color-mix(in srgb, var(--gold) 40%, var(--line)); border-radius:14px;
  padding:14px 16px; background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 8%, var(--tile)), var(--tile)) }
.mm-board-head{ font-family:var(--ui); font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase;
  color:var(--crimson); text-align:center; margin:0 0 10px }

/* =====================================================================
   "🎯 YOUR GOALS" card (homepage teaser + /feed/) — the client-side
   personal-goals surface and the debut of the site-wide XP counter.
   Rendered entirely by feed/goals.js from the local "fb-rpg" blob; the
   SSR shell ships only an empty #goalsCard host. Brand parchment/crimson,
   gold-tinted (like the RPG event cards) so it reads as the "meta-game"
   layer, dark-mode + reduced-motion aware, with a designed empty state.
   ===================================================================== */
/* width:100% on every ambient host: feed-ambient.js relocates these sections
   INTO the flex .feed-list, where their auto margins cancel the flex stretch and
   they shrink-to-fit their content (the poem card collapsed to ~264px). An
   explicit width keeps them filling the 560px column in both placements. */
.goals-host{ margin:26px auto 0; width:100%; max-width:560px }
.goals-card{ border:1px solid color-mix(in srgb, var(--gold) 40%, var(--line)); border-radius:14px;
  padding:16px 18px;
  background:linear-gradient(180deg, color-mix(in srgb, var(--gold) 8%, var(--tile)), var(--tile)) }
/* nudge ("📌 N goals to tackle today"): a touch warmer + a soft crimson ring so
   it gently catches the eye on review, without being naggy. */
.goals-card.goals-nudge{ border-color:color-mix(in srgb, var(--crimson) 45%, var(--line));
  box-shadow:0 0 0 1px color-mix(in srgb, var(--crimson) 28%, transparent),
             0 14px 30px -20px var(--shadow) }

.goals-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin:0 0 10px }
.goals-title{ font-family:var(--display); font-weight:600; font-size:19px; line-height:1.2; margin:0; color:var(--ink) }
.goals-xp{ flex:0 0 auto; font-family:var(--ui); font-weight:700; font-size:13px; letter-spacing:.02em;
  color:#fff; background:var(--crimson); border-radius:999px; padding:4px 12px;
  font-variant-numeric:tabular-nums; white-space:nowrap }
.goals-greet{ display:block; font-size:13px; color:var(--ink-soft); margin:-4px 0 10px }

/* designed empty state — never a blank box (best-practices) */
.goals-empty{ font-family:var(--display); font-style:italic; font-size:15px; color:var(--ink-soft);
  margin:2px 0 12px }

.goals-list{ list-style:none; margin:0 0 12px; padding:0; display:flex; flex-direction:column; gap:8px }
.goals-item{ display:flex; align-items:center; gap:11px; padding:8px 10px; border-radius:10px;
  background:color-mix(in srgb, var(--ink) 3%, transparent); transition:background .15s ease }
.goals-item:hover{ background:color-mix(in srgb, var(--gold) 9%, transparent) }
/* the unchecked checkbox — the deliberate RPG/XP affordance. A square box that
   fills gold on completion; full keyboard + focus support (role=checkbox span). */
.goals-check{ flex:0 0 auto; width:24px; height:24px; display:grid; place-items:center; cursor:pointer;
  border:2px solid color-mix(in srgb, var(--crimson) 45%, var(--line)); border-radius:7px;
  font-size:15px; font-weight:700; color:#fff; line-height:1; transition:.15s ease;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation }
.goals-check:hover{ border-color:var(--crimson); transform:translateY(-1px) }
.goals-check:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }
.goals-item.is-done .goals-check{ background:var(--gold); border-color:var(--gold);
  color:#2a1f06; box-shadow:0 0 0 3px color-mix(in srgb, var(--gold) 22%, transparent) }
.goals-text{ flex:1 1 auto; min-width:0; font-size:15px; line-height:1.4; color:var(--ink); overflow-wrap:anywhere }
.goals-item.is-done .goals-text{ text-decoration:line-through; color:var(--ink-soft) }
.goals-del{ flex:0 0 auto; font:inherit; font-size:13px; line-height:1; color:var(--ink-soft); cursor:pointer;
  background:none; border:0; border-radius:7px; padding:6px 8px; transition:.12s ease;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation }
.goals-del:hover{ color:var(--crimson); background:color-mix(in srgb, var(--crimson) 10%, transparent) }
.goals-del:focus-visible{ outline:2px solid var(--crimson); outline-offset:1px }

/* add-a-goal row */
.goals-add{ display:flex; gap:8px }
.goals-input{ flex:1 1 auto; min-width:0; font:inherit; font-family:var(--ui); font-size:14px; color:var(--ink);
  background:var(--paper,var(--tile)); border:1px solid var(--line); border-radius:10px; padding:9px 12px;
  transition:border-color .15s ease }
.goals-input:focus{ outline:none; border-color:var(--crimson) }
.goals-input::placeholder{ color:var(--ink-soft) }
.goals-addbtn{ flex:0 0 auto; font:inherit; font-family:var(--ui); font-weight:600; font-size:14px; color:#fff;
  background:var(--crimson); border:1px solid color-mix(in srgb, var(--crimson) 70%, #000); border-radius:10px;
  padding:9px 18px; cursor:pointer; transition:.16s ease;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation }
.goals-addbtn:hover{ transform:translateY(-1px); filter:brightness(1.06) }
.goals-addbtn:active{ transform:translateY(0) scale(.97) }

/* subtle signed-out affordance — never a hard gate */
.goals-signin{ margin:11px 0 0; font-size:12px; color:var(--ink-soft); text-align:center }

@media (prefers-reduced-motion: reduce){
  .goals-check:hover,.goals-addbtn:hover,.goals-addbtn:active{ transform:none }
}

/* =====================================================================
   FEED WIDGETS (feed/widgets.js) — registry-based per-user ambient cards.
   The host stays hidden until it has content (no CLS). Weather + Poetry reuse
   the brand ping / ping-daily card chrome; .widget-* adds the shared head
   (kicker + X turn-off), the weather readout, and the poem block. Dark-mode +
   reduced-motion aware, focus-visible, a real <select> for refresh frequency.
   ===================================================================== */
.widgets-host{ margin:22px auto 0; width:100%; max-width:560px }   /* width:100% — see .goals-host */
.daily-event-host{ margin:22px auto 0; width:100%; max-width:560px }
.widget-card{ margin:14px auto;
  /* widget cards have no vote rail — collapse the feed-card 2-col grid or the
     card's children scatter across both columns (the mobile "sideways kicker") */
  grid-template-columns:1fr }
.widget-card + .widget-card{ margin-top:14px }

/* shared head: small uppercase kicker + an X turn-off control */
.widget-head{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0 0 6px }
.widget-kicker{ font-size:10px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--crimson) }
.widget-x{ flex:0 0 auto; font:inherit; font-size:13px; line-height:1; color:var(--ink-soft); cursor:pointer;
  background:none; border:0; border-radius:7px; padding:5px 7px; transition:.12s ease;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation }
.widget-x:hover{ color:var(--crimson); background:color-mix(in srgb, var(--crimson) 10%, transparent) }
.widget-x:focus-visible{ outline:2px solid var(--crimson); outline-offset:1px }

/* ---- weather OFFER (ping-sized): sub-line + three pill actions + ZIP form ---- */
.widget-offer-inner{ gap:8px }
.widget-offer-sub{ margin:0; font-size:12.5px; color:var(--ink-soft); line-height:1.4 }
.widget-offer-actions{ display:flex; flex-wrap:wrap; justify-content:center; gap:8px }
.widget-cta-muted{ color:var(--ink-soft); border-color:var(--line) }
.widget-cta-muted:hover{ background:var(--ink-soft); border-color:var(--ink-soft); color:#fff }
.widget-zip-form{ display:flex; gap:8px; width:100%; max-width:300px; margin-top:2px }
/* display:flex above out-specifies the UA's [hidden]{display:none} — without this
   the ZIP form shows on the offer card before "Enter ZIP" is ever pressed. */
.widget-zip-form[hidden]{ display:none }
.widget-zip-input{ flex:1 1 auto; min-width:0; font:inherit; font-family:var(--ui); font-size:14px; color:var(--ink);
  background:var(--tile); border:1px solid var(--line); border-radius:10px; padding:8px 11px; transition:border-color .15s ease }
.widget-zip-input:focus{ outline:none; border-color:var(--crimson) }
.widget-msg{ margin:0; font-size:12px; color:var(--ink-soft) }
.widget-msg-err{ color:var(--crimson) }

/* ---- live weather readout ---- */
.widget-place{ font-family:var(--display); font-weight:600; font-size:17px; line-height:1.25; margin:0 0 8px; color:var(--ink) }
.widget-now{ display:flex; align-items:baseline; flex-wrap:wrap; gap:8px; margin:0 0 4px }
.widget-emoji{ font-size:22px; line-height:1 }
.widget-temp{ font-family:var(--display); font-weight:600; font-size:24px; line-height:1; color:var(--ink); font-variant-numeric:tabular-nums }
.widget-cond{ font-size:13.5px; color:var(--ink-soft) }
/* align-items:flex-start (not center) so when the line WRAPS at narrow widths the
   dot stays beside "Air Quality" on the first line instead of floating to the
   vertical centre of the 2-line block; the small margin-top drops it onto the
   text's cap-height so it reads as a leading bullet, not a superscript. */
.widget-aqi{ display:flex; align-items:flex-start; gap:8px; margin:6px 0 0; font-size:13px; line-height:1.4; color:var(--ink) }
.widget-dot{ flex:0 0 auto; width:10px; height:10px; border-radius:50%; margin-top:4px }
/* ---- 7-day forecast strip ---- */
.widget-week{ display:grid; grid-template-columns:repeat(7, 1fr); gap:3px; margin:12px 0 0;
  padding-top:11px; border-top:1px solid var(--line-2) }
.widget-day{ display:flex; flex-direction:column; align-items:center; gap:2px; padding:7px 1px;
  border-radius:10px; min-width:0 }
.widget-day-today{ background:color-mix(in srgb, var(--crimson) 8%, transparent) }
.widget-day-dow{ font-family:var(--ui); font-size:10px; font-weight:600; letter-spacing:.02em;
  text-transform:uppercase; color:var(--ink-soft) }
.widget-day-today .widget-day-dow{ color:var(--crimson) }
.widget-day-emoji{ font-size:18px; line-height:1; margin:1px 0 }
.widget-day-hi{ font-size:12.5px; font-weight:700; color:var(--ink); font-variant-numeric:tabular-nums; line-height:1.15 }
.widget-day-lo{ font-size:11px; color:var(--ink-soft); font-variant-numeric:tabular-nums; line-height:1.15 }
.widget-day-aqi{ display:inline-flex; align-items:center; gap:3px; min-height:13px; margin-top:2px;
  font-size:9.5px; color:var(--ink-soft); font-variant-numeric:tabular-nums }
.widget-day-aqi .widget-dot{ width:7px; height:7px; margin-top:0 }   /* reset hero-dot offset — this one is center-aligned beside its number */
.widget-loading{ margin:0; font-size:13px; color:var(--ink-soft); font-style:italic }
.widget-err{ margin:0; font-size:13px; color:var(--crimson) }
.widget-link{ font:inherit; font-size:12.5px; font-weight:600; color:var(--crimson); cursor:pointer;
  background:none; border:0; padding:2px 2px; text-decoration:underline; text-underline-offset:2px }
.widget-link:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }
.widget-foot{ margin-top:9px; padding-top:9px; border-top:1px solid var(--line-2) }
.widget-refresh{ display:inline-flex; align-items:center; gap:7px; font-size:12px; font-weight:500;
  letter-spacing:normal; text-transform:none; color:var(--ink-soft); margin:0 }
.widget-refresh-sel{ font:inherit; font-family:var(--ui); font-size:12.5px; color:var(--ink); cursor:pointer;
  background:var(--tile); border:1px solid var(--line); border-radius:8px; padding:5px 9px; transition:border-color .15s ease }
.widget-refresh-sel:focus{ outline:none; border-color:var(--crimson) }
.widget-refresh-sel:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px }

/* ---- Hacker News card ---- */
.widget-hn-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:11px }
.widget-hn-item{ display:flex; flex-direction:column; gap:2px }
.widget-hn-item + .widget-hn-item{ padding-top:11px; border-top:1px solid var(--line-2) }
.widget-hn-link{ font-weight:600; font-size:14.5px; line-height:1.35; color:var(--ink); text-decoration:none;
  text-underline-offset:2px; transition:color .15s ease }
.widget-hn-link:hover{ color:var(--crimson); text-decoration:underline }
.widget-hn-link:focus-visible{ outline:2px solid var(--crimson); outline-offset:2px; border-radius:3px }
.widget-hn-meta{ font-size:12px; color:var(--ink-soft); font-variant-numeric:tabular-nums }

/* ---- poetry card ---- */
.widget-poem-title{ font-family:var(--display); font-weight:600; font-style:italic; font-size:19px; line-height:1.25; margin:0 0 2px; color:var(--ink) }
.widget-poem-author{ margin:0 0 10px; font-size:12.5px; color:var(--ink-soft) }
.widget-poem-lines{ font-family:var(--display); font-size:15px; line-height:1.55; color:var(--ink); margin:0; white-space:normal }
.widget-poetry .widget-link{ margin-top:8px }

@media (prefers-reduced-motion: reduce){
  .widget-x:hover{ transform:none }
}

/* "Continue reading" series card (client-injected by feed/series-feed.js) */
.feed-card.series-continue{ grid-template-columns:1fr; border-color:color-mix(in srgb, var(--gold) 45%, var(--line));
  max-width:560px; margin:14px auto; width:100% }
.feed-card.series-continue .feed-type{ color:var(--gold) }
.feed-card.series-done h3{ font-style:italic }

/* readable cards are whole-card tap targets (feed-ambient.js delegated nav) */
.feed-card.poem, .feed-card.text, .feed-card.ping-daily{ cursor:pointer }

/* ---- archive calendar (series views: /feed/?series=daily-chess|daily-word) ---- */
.arch-cal{ display:flex; flex-wrap:wrap; gap:18px; margin:0 0 22px }
.ac-month h3{ font-family:"EB Garamond",serif; font-size:17px; margin:0 0 6px; color:var(--ink) }
.ac-grid{ display:grid; grid-template-columns:repeat(7,30px); gap:3px }
.ac-dow{ font-size:10px; text-align:center; color:var(--ink-soft); line-height:18px }
.ac-day{ height:30px; display:grid; place-items:center; font-size:12px; border-radius:8px;
  color:var(--ink-soft); border:1px solid transparent; font-variant-numeric:tabular-nums }
.ac-day.pad{ visibility:hidden }
a.ac-day.has{ color:var(--ink); border-color:var(--line); background:var(--tile);
  text-decoration:none; font-weight:600; transition:border-color .15s ease, transform .12s ease }
a.ac-day.has:hover{ border-color:var(--crimson); transform:translateY(-1px) }
a.ac-day.done{ border-color:color-mix(in srgb, var(--gold) 55%, var(--line));
  background:color-mix(in srgb, var(--gold) 14%, var(--tile)) }
.ac-day.today{ box-shadow:0 0 0 2px var(--crimson) inset }

/* ---- series chapter-1 anchor: the book cover rides the card ---- */
.feed-body .series-cover{ float:left; width:84px; height:auto; aspect-ratio:2/3; object-fit:cover;
  border-radius:8px; border:1px solid var(--line); margin:2px 14px 6px 0;
  box-shadow:0 4px 14px -8px rgba(0,0,0,.45) }
.feed-card:hover .series-cover{ filter:brightness(1.04) }
@media (max-width:480px){ .feed-body .series-cover{ width:68px } }

/* answered-event sign-off (events.js adds it with the outcome reveal) */
.fb-done{ margin:9px 0 0; font-size:12.5px; color:var(--ink-soft) }

/* "next puzzle in…" return hook beside the share button */
.dc-nextin{ display:inline-block; margin-left:12px; font-size:12.5px; color:var(--ink-soft);
  font-variant-numeric:tabular-nums }

/* ---- FEED CHANNEL cards + series chapter-1 pills — the daily-game pop-in
   family: 54px tile/cover left, Garamond title, crimson meta, the same
   crimson action button (.dc-play) on the right. ---- */
.feed-card.feed-promo, .feed-card.series-pill{ display:flex; align-items:center; padding:12px 16px }
.fp-mini, .sp-mini{ display:flex; align-items:center; gap:12px; width:100%; min-width:0 }
.fp-ico{ flex:0 0 auto; width:54px; height:54px; display:grid; place-items:center; font-size:26px;
  background:color-mix(in srgb, #1185fe 9%, var(--paper-2)); border:2px solid color-mix(in srgb, #1185fe 35%, var(--line));
  border-radius:5px }
.sp-cover{ flex:0 0 auto; width:44px; aspect-ratio:2/3; object-fit:cover; border-radius:5px;
  border:2px solid var(--line); box-shadow:0 3px 10px -6px rgba(0,0,0,.5) }
.fp-text, .sp-text{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:1px; line-height:1.25 }
.fp-title, .sp-title{ font-family:var(--display,serif); font-weight:700; font-size:15px; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.fp-meta, .sp-kicker{ font-family:var(--ui); font-size:12px; font-weight:600; color:var(--crimson);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.fp-sub, .sp-meta{ font-family:var(--ui); font-size:12px; color:var(--ink-soft);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.fp-sub a, .sp-meta a{ color:var(--crimson) }
a.dc-play{ text-decoration:none; display:inline-flex; align-items:center }
.fp-x{ flex:0 0 auto; font:inherit; font-size:17px; line-height:1; color:var(--ink-soft); background:none;
  border:0; cursor:pointer; padding:4px }
.fp-x:hover{ color:var(--crimson) }
@media (max-width:480px){
  .fp-mini, .sp-mini{ flex-wrap:wrap }
  /* the Follow button + dismiss drop to a second row. Align Follow under the
     text column and pin × hard-right so the row reads as a deliberate footer,
     not a button floating mid-card. */
  .fp-mini .dc-play, .sp-mini .dc-play{ margin-left:66px }
  .fp-mini .fp-x{ margin-left:auto }
  /* let the friendly promo invite wrap instead of truncating to "…Fe…" —
     there's clearly room once the pill becomes a stacked card. */
  .fp-meta, .sp-kicker{ white-space:normal; overflow:visible; text-overflow:clip }
}

/* ===================================================================
   ANAGRAM LADDER (feed/anagram-daily.js) — date-seeded daily word puzzle
   =================================================================== */
.ag-card .ag-kicker{ color:var(--crimson) }
/* loading beat: a calm, on-brand "one sec" line in the card's quote voice
   (italic serif, gold rule) — NOT the tiny uppercase .ag-progress label, which
   read as a system status. A gentle pulse signals "working", reduced-motion safe. */
.ag-loading{ font-family:var(--display); font-style:italic; font-size:16px; line-height:1.5;
  color:var(--ink-soft); margin:10px 0 2px; padding:2px 0 2px 14px;
  border-left:3px solid color-mix(in srgb, var(--gold) 55%, var(--line));
  animation:agLoadPulse 1.4s ease-in-out infinite }
@keyframes agLoadPulse{ 0%,100%{ opacity:.55 } 50%{ opacity:1 } }
@media (prefers-reduced-motion:reduce){ .ag-loading{ animation:none; opacity:.8 } }
.ag-h{ font-family:var(--display); font-weight:600; font-size:18px; line-height:1.25; margin:2px 0 10px }
.ag-verse{ font-family:var(--display); font-size:20px; line-height:1.5; margin:0; color:var(--ink); border-left:3px solid var(--gold); padding:2px 0 2px 14px }
.ag-word{ font-style:normal }
.ag-word.blank{ letter-spacing:.12em; color:var(--ink-soft) }
.ag-word.blank.cur{ color:var(--crimson); font-weight:600 }
.ag-word.done{ color:var(--gold); font-weight:600 }
.ag-author{ font-size:12.5px; color:var(--ink-soft); margin:8px 0 0; text-align:right }
.ag-play{ margin:16px 0 0 }
.ag-progress{ font-size:11px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin:0 0 8px }
.ag-slots{ display:flex; flex-wrap:wrap; gap:6px; margin:0 0 12px; min-height:42px }
.ag-slot{ width:38px; height:42px; border-radius:9px; display:grid; place-items:center;
  border:2px dashed var(--line); background:var(--paper-2); font-family:var(--display); font-weight:600; font-size:22px; color:var(--ink); text-transform:uppercase }
.ag-slot.filled{ border-style:solid; border-color:var(--crimson); cursor:pointer; transition:.12s ease }
/* a filled slot is tappable — tap (or Backspace) pulls the letter back. Hover/focus
   hints "remove" with a strike + softer fill so it reads as interactive, not fixed. */
.ag-slot.filled:hover,.ag-slot.filled:focus-visible{ outline:none;
  background:color-mix(in srgb, var(--crimson) 14%, var(--paper-2)); text-decoration:line-through;
  text-decoration-thickness:2px; box-shadow:0 0 0 2px color-mix(in srgb, var(--crimson) 45%, transparent) }
.ag-rack{ display:flex; flex-wrap:wrap; gap:7px; margin:0 0 12px }
.ag-tile{ width:40px; height:46px; border-radius:10px; cursor:pointer; font:inherit; font-family:var(--display); font-weight:600; font-size:22px; text-transform:uppercase;
  border:1px solid var(--line); background:var(--tile); color:var(--ink); box-shadow:0 2px 0 var(--line); transition:transform .1s ease, background .12s ease }
.ag-tile:hover{ border-color:var(--crimson); color:var(--crimson) }
.ag-tile:active{ transform:translateY(2px); box-shadow:none }
.ag-tile.used{ opacity:.28; cursor:default; box-shadow:none }
.ag-actions{ display:flex; gap:8px }
.ag-btn{ font:inherit; font-family:var(--ui); font-size:13px; font-weight:600; cursor:pointer; border-radius:999px;
  padding:7px 16px; border:1px solid var(--line); color:var(--ink-soft); background:transparent; transition:.15s ease }
.ag-btn:hover{ color:var(--crimson); border-color:var(--crimson) }
.ag-msg{ margin:10px 0 0; font-size:13.5px; font-weight:500 }
.ag-msg.ok{ color:var(--gold) }
.ag-msg.warn{ color:var(--crimson) }
.ag-done{ font-family:var(--display); font-size:17px; line-height:1.45; margin:6px 0 12px; color:var(--ink) }
.ag-done .ag-xp{ color:var(--gold); font-weight:600 }
.ag-again{ font:inherit; font-family:var(--ui); font-size:13px; font-weight:600; cursor:pointer; border-radius:999px;
  padding:8px 18px; border:1px solid var(--crimson); color:var(--crimson); background:transparent }
.ag-again:hover{ background:var(--crimson); color:#fff }
@media (prefers-reduced-motion:reduce){ .ag-tile{ transition:none } }
/* phones: a 7-letter word at 40px tiles wrapped 6+1, leaving a lone orphan tile
   on its own line (reads broken). Shrink a touch so 7 fit on one row at ~390px,
   and CENTER the rack/slots so any 8-letter wrap balances instead of going
   left-ragged. Tiles stay ≥36px — comfortable thumb targets. */
@media (max-width:480px){
  .ag-slots{ gap:4px; justify-content:center }
  .ag-slot{ width:34px; height:40px; font-size:20px }
  .ag-rack{ gap:4px; justify-content:center }
  .ag-tile{ width:36px; height:44px; font-size:20px }
}

/* ===================================================================
   DAILY DECODE (feed/decode-daily.js) — date-seeded emoji cryptogram
   =================================================================== */
.dd-card .dd-kicker{ color:var(--crimson) }
.dd-h{ font-family:var(--display); font-weight:600; font-size:18px; line-height:1.25; margin:2px 0 12px }
.dd-cipher{ display:flex; flex-wrap:wrap; align-items:flex-end; gap:4px 0;
  border-left:3px solid var(--gold); padding:4px 0 4px 14px; margin:0 }
.dd-word{ display:inline-flex; flex-wrap:wrap }
.dd-gap{ width:14px }
.dd-punct{ align-self:flex-end; font-family:var(--display); font-size:20px; color:var(--ink); padding:0 1px 4px }
.dd-cell{ display:inline-flex; flex-direction:column; align-items:center; gap:2px; padding:0 1px }
.dd-glyph{ font-size:21px; line-height:1.1 }
.dd-letter{ font-family:var(--display); font-weight:600; font-size:16px; line-height:1;
  min-width:1ch; height:18px; color:var(--ink-soft); border-bottom:2px solid var(--line); text-transform:uppercase }
.dd-cell.done .dd-letter{ color:var(--gold); border-bottom-color:color-mix(in srgb, var(--gold) 60%, var(--line)) }
/* the cell the prompt is asking about: a calm crimson-tinted pill so the eye can
   connect the big prompt glyph to its spot in the phrase. The thin crimson rule
   alone (on an empty slot) was nearly invisible among the gray blanks. */
.dd-cell.cur{ background:color-mix(in srgb, var(--crimson) 14%, transparent); border-radius:7px }
.dd-cell.cur .dd-glyph{ transform:translateY(-1px) }
.dd-cell.cur .dd-letter{ border-bottom-color:var(--crimson) }
/* any UNSOLVED symbol is tappable to answer it next (jump around the phrase) —
   ONLY in the in-feed daily. The duel reuses the same cipher markup (so cells also
   carry .pick) but has no cell click handler, so scope the clickable affordance to
   .dd-card to avoid a pointer cursor + hover that does nothing in /duel/. */
.dd-card .dd-cell.pick{ cursor:pointer; border-radius:7px }
@media (hover:hover){ .dd-card .dd-cell.pick:not(.cur):hover{ background:color-mix(in srgb, var(--ink) 7%, transparent) } }
.dd-author{ font-size:12.5px; color:var(--ink-soft); margin:10px 0 0; text-align:right }
.dd-play{ margin:16px 0 0 }
.dd-progress{ font-size:11px; font-weight:600; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin:0 0 10px }
.dd-prompt{ display:flex; align-items:center; flex-wrap:wrap; gap:4px 10px; margin:0 0 12px }
.dd-prompt-glyph{ font-size:30px; line-height:1; filter:drop-shadow(0 2px 0 var(--line)) }
.dd-prompt-q{ font-family:var(--display); font-size:16px; color:var(--ink) }
/* quiet, persistent helper so tap-to-jump is discoverable on touch (no hover) and
   the picked vs. auto-next symbol is never ambiguous; full-width so it tucks on its
   own line under the prompt row rather than crowding the glyph + question. */
.dd-hop{ flex:1 0 100%; font-size:12px; line-height:1.3; color:var(--ink-soft) }
.dd-rack{ display:flex; flex-wrap:wrap; gap:6px; margin:0 0 12px }
.dd-key{ width:34px; height:40px; border-radius:9px; cursor:pointer; font:inherit; font-family:var(--display);
  font-weight:600; font-size:17px; border:1px solid var(--line); background:var(--tile); color:var(--ink);
  box-shadow:0 2px 0 var(--line); transition:transform .1s ease, background .12s ease, color .12s ease }
.dd-key:hover{ border-color:var(--crimson); color:var(--crimson) }
.dd-key:active{ transform:translateY(2px); box-shadow:none }
.dd-key.used{ opacity:.26; cursor:default; box-shadow:none }
.dd-actions{ display:flex; gap:8px; flex-wrap:wrap }
.dd-btn{ font:inherit; font-family:var(--ui); font-size:13px; font-weight:600; cursor:pointer; border-radius:999px;
  padding:7px 16px; border:1px solid var(--line); color:var(--ink-soft); background:transparent; transition:.15s ease }
.dd-btn:hover{ color:var(--crimson); border-color:var(--crimson) }
.dd-msg{ margin:10px 0 0; font-size:13.5px; font-weight:500 }
.dd-msg.ok{ color:var(--gold) }
.dd-msg.warn{ color:var(--crimson) }
.dd-done{ font-family:var(--display); font-size:17px; line-height:1.45; margin:6px 0 8px; color:var(--ink) }
.dd-done .dd-xp{ color:var(--gold); font-weight:600 }
.dd-reveal{ font-family:var(--display); font-style:italic; font-size:16px; line-height:1.5; color:var(--ink-soft); margin:0 0 12px }
.dd-again{ font:inherit; font-family:var(--ui); font-size:13px; font-weight:600; cursor:pointer; border-radius:999px;
  padding:8px 18px; border:1px solid var(--crimson); color:var(--crimson); background:transparent }
.dd-again:hover{ background:var(--crimson); color:#fff }
/* "Challenge a friend" is an <a> wearing .dd-btn — keep it inline + un-underlined,
   with a soft crimson tint so the duel invite reads as the fun next step */
a.dd-btn{ text-decoration:none; display:inline-flex; align-items:center }
.dd-duel{ border-color:color-mix(in srgb, var(--crimson) 45%, var(--line)); color:var(--crimson) }
.dd-duel:hover{ background:color-mix(in srgb, var(--crimson) 10%, transparent); border-color:var(--crimson) }
@media (prefers-reduced-motion:reduce){ .dd-key{ transition:none } }
@media (max-width:480px){
  .dd-cipher{ justify-content:center }
  .dd-glyph{ font-size:19px }
  .dd-rack{ gap:4px; justify-content:center }
  /* keep the A–Z keys at least as tappable as the anagram tiles (36×44) — a
     full alphabet still fits 8/row in a ~320px card, so don't shrink the thumb
     target below the sibling just to save a row. */
  .dd-key{ width:36px; height:44px; font-size:17px }
}

/* ===================================================================
   DAILY QUEST (feed/quest-daily.js) — a wellness prompt + shared answers
   =================================================================== */
.quest-card .quest-kicker{ color:var(--gold) }
.quest-prompt{ font-family:var(--display); font-weight:600; font-size:21px; line-height:1.3; margin:4px 0 6px; color:var(--ink) }
.quest-sub{ font-size:13px; color:var(--ink-soft); margin:0 0 14px }
.quest-sub .quest-xp{ color:var(--gold); font-weight:600 }
.quest-sub .quest-count{ font-variant-numeric:tabular-nums }
.quest-answers{ list-style:none; margin:0 0 12px; padding:0; display:flex; flex-direction:column; gap:10px }
.quest-answers .quest-answer{ background:var(--paper-2); border:1px solid var(--line-2); border-radius:12px; padding:10px 13px }
.quest-answers .comment-head{ display:flex; align-items:center; gap:8px; margin:0 0 4px }
.quest-av{ width:24px; height:24px; border-radius:50%; object-fit:cover; background:var(--tile); border:1px solid var(--line); flex:0 0 auto }
.quest-answers .comment-name{ font-weight:600; font-size:13.5px; color:var(--ink) }
.quest-answers .comment-text{ margin:0; font-size:14.5px; line-height:1.5; color:var(--ink); white-space:pre-wrap; overflow-wrap:anywhere }
.quest-answer.is-pending .comment-text{ opacity:.66 }
/* a little air between the warm "chorus" of answers and the composer, so the
   textarea reads as its own gentle invitation rather than another answer row. */
.quest-compose{ margin-top:4px }
.quest-mine-note{ font-size:12.5px; color:var(--gold); font-weight:600; margin:8px 0 0 }
.quest-msg{ margin:10px 0 0; font-size:13.5px; font-weight:500 }
/* a comment / quest-answer attached image (our own /media/ paths only) — shared by
   the bsky-modal comment thread + quest answers (both load feed.css, not post.css). */
.comment-img{ display:inline-block; margin:7px 0 0 }
.comment-img img{ max-width:240px; max-height:240px; border-radius:8px; border:1px solid var(--line); display:block }
/* The composer reuses the .comment-* class NAMES, but post.css (where the bare
   .comment-* rules live) is NOT loaded on the homepage / /feed/ — only feed.css is,
   and its .comment-* rules are scoped to .bskm-comments. So style the quest card's
   composer + answer states here, scoped under .quest-card so nothing leaks into other
   feed cards (bare .comment-* selectors have leaked before — see homepage-feed-parity). */
.quest-card .comment-empty{ color:var(--ink-soft); font-size:13px; list-style:none; padding:6px 2px }
/* gold (not crimson) "Pending" — matches the site's canonical comment-pending
   badge (post.css) and keeps this warm wellness card from reading alarmed/transactional. */
.quest-card .comment-pending{ margin-left:auto; font-size:10.5px; font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; color:var(--gold) }
.quest-card .comment-gate{ font-size:13px; color:var(--ink-soft); margin:0 }
.quest-card .comment-gate a{ color:var(--crimson); font-weight:600 }
.quest-card .comment-form{ display:flex; flex-direction:column; gap:8px; margin:0 }
.quest-card .comment-input{ width:100%; font:inherit; font-size:14px; padding:9px 11px; resize:vertical;
  border:1px solid var(--line); border-radius:10px; background:var(--tile); color:var(--ink) }
.quest-card .comment-input:focus{ outline:none; border-color:var(--crimson) }
.quest-card .comment-actions{ display:flex; align-items:center; gap:10px }
.quest-card .comment-err{ margin-right:auto; font-size:12.5px; color:var(--crimson); min-width:0 }   /* shrink before the submit wraps */
.quest-card .comment-err.ok{ color:var(--gold) }
.quest-card .comment-submit{ font:inherit; font-weight:600; cursor:pointer; border:0; border-radius:999px; white-space:nowrap;
  background:var(--crimson); color:#fff; padding:7px 16px }
.quest-card .comment-submit:disabled{ opacity:.6; cursor:default }

/* ===================================================================
   WORD OF THE DAY (feed/learn-word.js) — one word in five languages
   =================================================================== */
.lw-card .lw-kicker{ color:var(--crimson) }
.lw-word{ font-family:var(--display); font-weight:600; font-size:30px; line-height:1.1; margin:4px 0 14px; color:var(--ink) }
.lw-list{ list-style:none; margin:0 0 14px; padding:0; display:flex; flex-direction:column; gap:2px }
.lw-row{ display:flex; align-items:baseline; gap:10px; padding:8px 4px; border-bottom:1px solid var(--line-2) }
.lw-row:last-child{ border-bottom:0 }
.lw-flag{ flex:0 0 auto; font-size:18px; line-height:1 }
.lw-lang{ flex:0 0 84px; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--ink-soft) }
.lw-tr{ flex:1; font-family:var(--display); font-size:18px; color:var(--ink) }
.lw-said{ font:inherit; font-family:var(--ui); font-size:13.5px; font-weight:600; cursor:pointer; border-radius:999px;
  padding:9px 18px; border:1px solid var(--crimson); color:var(--crimson); background:transparent; transition:.15s ease }
.lw-said:hover:not(.done){ background:var(--crimson); color:#fff }
.lw-said .lw-xp{ color:var(--gold); font-weight:600 }
.lw-said:hover:not(.done) .lw-xp{ color:#ffe6a6 }
.lw-said.done{ border-color:var(--gold); color:var(--gold); cursor:default }
@media (max-width:480px){ .lw-lang{ flex-basis:72px; font-size:11px } .lw-tr{ font-size:16px } }

/* ===================================================================
   MOBILE COMPRESSION — flatter, more modular posts on phones (owner ask
   2026-06-11). Tighter padding/margins/line-height + a height cap on lone
   images so a single post never dominates the screen.
   =================================================================== */
@media (max-width:480px){
  .bsky-card{ padding:11px 13px; border-radius:12px }
  .bsky-avatar{ width:32px; height:32px }
  /* phones: trim the header→divider→content gaps a touch tighter than desktop */
  .bsky-head{ padding-bottom:8px }
  .bsky-head + .bsky-text,
  .bsky-head + .bsky-row,
  .bsky-head + .bsky-images,
  .bsky-head + .bsky-ext,
  .bsky-head + .bsky-video{ margin-top:8px }
  .bsky-text{ font-size:14.5px; line-height:1.42; margin-top:8px }
  .bsky-images, .bsky-ext{ margin-top:8px }
  .bsky-images img{ aspect-ratio:16/9 }                  /* a touch shorter than 16/10 */
  /* lone image: RESERVE a 4/3 box up front (was aspect-ratio:auto, which left the
     height unset until the image loaded → the card grew + bumped on-screen content
     on mobile = the reported CLS). Tall images are capped + cropped. */
  .bsky-images.n1 img{ aspect-ratio:4/3; max-height:240px; object-fit:cover }
  .bsky-ext-thumb{ width:74px; height:74px }
  .bsky-meta{ margin-top:9px; font-size:12px }
  /* native feed cards: trim the body spacing so text posts are flatter too */
  .feed-card .feed-body{ gap:6px }
  .feed-card .text{ line-height:1.45 }
}
