/* giveaway.css — /p/giveaway ("Nova Craft") public list page. Design
   source: FAN_PROJECT_GIVEAWAY_REQUIREMENTS.md §9.1/§9.2. Structural/
   functional baseline (Phase 2) — visual polish pass is Phase 6, not this
   file's scope yet. Same tokens/grid conventions as market.css. */

* { box-sizing: border-box; }
body { margin: 0; background: var(--space-gradient), var(--bg-primary); background-repeat: no-repeat; background-attachment: fixed; color: var(--text-primary); font-family: var(--font-thai); }

.nt-page {
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px 32px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: calc(100vh - var(--navbar-h));
  min-height: calc(100dvh - var(--navbar-h));
  /* Lets .nt-toolbar below query its OWN rendered width via @container — an
     element can't be the container for its own query (spec forbids the
     self-reference), so this has to live on an ancestor instead. */
  container-type: inline-size;
}
.nt-page .lg-footer-link { margin-top: auto; padding-top: 4px; }

.nt-head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.nt-head-top { display: flex; align-items: center; gap: 8px; }
.nt-head-icon { font-size: 22px; color: var(--accent-gold); }
.nt-head-title { font-size: 26px; font-weight: 700; }
.nt-head-desc { font-size: 12px; color: var(--text-secondary); margin-top: -8px; }
/* "ข้อมูลอัปเดตล่าสุด (โดยแอดมิน)" (2026-07-29 ask) — quiet footnote right
   under the page description; empty and takes no space when there's no
   admin update yet (headUpdatedLine starts textContent-empty in JS). */
.nt-head-updated:not(:empty) { font-size: 10.5px; font-weight: 300; color: var(--text-tertiary, var(--text-secondary)); opacity: 0.85; margin-top: 2px; }

.nt-submit-wrap { position: relative; }
.nt-btn--submit {
  padding: 9px 18px;
  border-radius: 999px;
  border: none;
  background: var(--accent-gold);
  color: var(--gold-btn-text);
  font-family: var(--font-thai);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.15s ease;
}
.nt-btn--submit:hover { filter: brightness(1.08); transform: translateY(-1px); }
/* Keeps dark mode's vivid gold + dark text in light mode too (2026-08-08 ask)
   — solid fill + in-box text has self-contained contrast, independent of
   the page background, so light mode's muted gold isn't needed here. */
:root[data-theme="light"] .nt-btn--submit { background: #F4A140; color: #1a1300; }
.nt-submit-chooser {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
  min-width: 170px;
}
.nt-submit-chooser[hidden] { display: none; }
/* Dedicated class, not .nt-card__link (2026-07-25 fix) — that class is the
   card footer's small link-buttons, later shrunk (10.5px font, flex:1,
   ellipsis) for that context; reusing it here for a small link-buttons,
   which reused it here for a totally different context (a dropdown MENU
   item) bled that shrinking in too, making these read as "thin and tiny".
   Same "shared selector, unrelated consumer breaks" pattern as
   TROUBLESHOOTING_BIBLE.md §6.9/§6.13 — give unrelated UI its own class. */
.nt-submit-chooser__link {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 10px 12px;
  border-radius: 8px;
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-family: var(--font-thai);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease;
}
.nt-submit-chooser__link:hover { background: rgba(var(--surface-rgb),0.06); }

/* ── Search row — search field + saved-only toggle live together (the
   toggle used to be a text chip buried in the controls row; pulling it up
   here next to search declutters that row and gives "my saved items" its
   own fixed, always-visible spot). ─────────────────────────────────────── */
.nt-search-row { display: flex; gap: 8px; align-items: center; }
.nt-search-wrap {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  padding: 0 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 11px;
}
.nt-search-icon { font-size: 14px; color: var(--text-secondary); flex-shrink: 0; }
.nt-search { flex: 1; height: 100%; border: none; background: transparent; color: var(--text-primary); font-family: var(--font-thai); font-size: 13px; min-width: 0; }
.nt-search:focus { outline: none; }
.nt-search::placeholder { color: var(--text-secondary); }

/* ── Search dropdown (live suggestions: X handle / category) — same pattern
   as /p/find's .find-search-dropdown (find.css). ─────────────────────────── */
.nt-search-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 5;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 8px 20px rgba(var(--shadow-rgb), calc(0.4 * var(--shadow-mult)));
}
.nt-search-dropdown__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 13px;
}
.nt-search-dropdown__item:hover { background: rgba(var(--surface-rgb),0.06); }
.nt-search-dropdown__item + .nt-search-dropdown__item { border-top: 1px solid var(--border); }
.nt-search-dropdown__name { font-weight: 600; }
.nt-search-dropdown__type { font-size: 11px; color: var(--text-secondary); flex-shrink: 0; }
.nt-saved-toggle {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 11px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.nt-saved-toggle:hover { color: var(--text-primary); border-color: var(--text-primary); }
.nt-saved-toggle.is-active { color: var(--accent-gold); border-color: var(--accent-gold); background: rgba(var(--accent-gold-rgb),0.1); }

/* ── Toolbar — a flat sequence of control rows, NOT a boxed panel
   (2026-07-24 correction: an earlier pass wrapped this in its own
   bg+border "card" sitting directly under the search bar's own box, which
   just traded one kind of clutter for "boxes stacked on boxes". Matches
   Marketplace's own filter row (market.css .mk-filter-row), which has no
   background/border at all — filters live straight on the page). Each row
   still gets a short label for scannability, just without the box/divider
   treatment around it. ──────────────────────────────────────────────── */
.nt-toolbar { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 14px 20px; }
.nt-toolbar__row { display: flex; flex-direction: column; gap: 8px; }
.nt-toolbar__label { font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-secondary); }
.nt-toolbar__controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* Mobile-default order (2026-07-29 ask, 3rd/4th pass): ประเภท → รอบ →
   หมวดหมู่ → สถานะ&เรียงลำดับ. ประเภท/รอบ share a line when there's room
   and each fall back to their own line the moment their combined
   min-content doesn't fit — ordinary flex-wrap, no breakpoint needed for
   that part ("ตัดทั้งก้อนลงมา"). Content-sized (flex:0 1 auto, no grow) —
   an earlier flex:1 1 220px made them grow to fill the line, which left a
   big empty gap *inside* ประเภท's own box before รอบ started (2026-07-29
   ask, 4th pass: "รอบ ห่างจากประเภทมาก"); without grow they just sit
   snug next to each other with the row-gap between.
   หมวดหมู่/tail always get their own full-width line via flex-basis:100%. */
.nt-toolbar__row--type { order: 1; flex: 0 1 auto; }
.nt-toolbar__row--round { order: 2; flex: 0 1 auto; }
.nt-toolbar__row--category { order: 3; flex-basis: 100%; }
.nt-toolbar__row--tail { order: 4; flex-basis: 100%; }
/* Desktop: row 1 becomes ประเภท / รอบ / เรียงลำดับ (เรียงลำดับ pushed to the
   far right via margin-left:auto), หมวดหมู่ dropping to its own row 2 below
   (2026-07-24 ask). A container query on the .nt-page ancestor (not
   @media — an element can't query its own width, and raw viewport width
   doesn't reliably predict when this row's content actually runs out of
   room) so the exact same desktop layout also reverts to the mobile one
   above when a desktop *window* gets squeezed narrow, not just on phones
   (2026-07-29 ask, 2nd+3rd pass) — one breakpoint instead of maintaining a
   separate "squeezed desktop" patch that fights the mobile rules.
   Threshold measured empirically against mock data (5 categories, 3
   rounds): row 1 (type+round+tail together) overflows somewhere around a
   836–896px content-box, so 940px leaves a safety margin; re-check if the
   category/round counts grow a lot. */
@container (min-width: 940px) {
  .nt-toolbar { gap: 16px 28px; }
  .nt-toolbar__row { flex: none; } /* also covers type/round — already content-sized above */
  .nt-toolbar__row--tail { order: 3; margin-left: auto; }
  .nt-toolbar__row--category { order: 4; flex-basis: 100%; }
}

.nt-segment { display: flex; gap: 6px; flex-wrap: wrap; }
.nt-segment__btn {
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-thai);
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.nt-segment__btn:hover { color: var(--text-primary); }
/* ประเภท (type) → gold when selected (2026-07-24 ask). */
.nt-segment__btn.is-active { background: rgba(var(--accent-gold-rgb),0.15); border-color: var(--accent-gold); color: var(--accent-gold); }

.nt-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.nt-chip {
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-thai);
  font-size: 11px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.nt-chip:hover { color: var(--text-primary); }
/* หมวดหมู่ (category) → stays neutral/white when selected. */
.nt-chip.is-active { background: rgba(var(--surface-rgb),0.12); border-color: rgba(var(--surface-rgb),0.3); color: var(--text-primary); }
/* วัน/รอบ (round) → teal when selected (2026-07-24 ask) — .nt-chips--round
   is the modifier buildMultiChips adds only to the round-filter row, so
   category chips (sharing the same .nt-chip base) are unaffected. */
.nt-chips--round .nt-chip.is-active { background: rgba(var(--accent-teal-rgb),0.15); border-color: var(--accent-teal); color: var(--accent-teal); }
/* Entry-count suffix on หมวดหมู่ chips, e.g. "Sticker (20)" (2026-07-25 ask)
   — dimmed so the count reads as secondary info, not competing with the
   category name itself. */
.nt-chip__count { opacity: 0.55; }
.nt-chip-more {
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-thai);
  font-size: 11px;
  cursor: pointer;
}
.nt-chip-more:hover { color: var(--text-primary); border-color: var(--text-primary); }

/* Chevron indicator (2026-07-24 fix: nt-select had none — copied the input
   styling from market.css's .mk-select but dropped the wrapper+::after
   arrow that tells you it's actually a dropdown). Same pattern as
   .mk-select-wrap. */
.nt-select-wrap { position: relative; }
.nt-select-wrap::after {
  content: '▾';
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9px;
  color: var(--text-secondary);
  pointer-events: none;
}
.nt-select {
  width: 100%;
  height: 36px;
  padding: 0 26px 0 12px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 9px;
  color: var(--text-primary);
  font-family: var(--font-thai);
  font-size: 11px;
  font-weight: 600;
  appearance: none;
  cursor: pointer;
}
.nt-select:focus { outline: none; border-color: var(--accent-gold); }

/* "พบ N รายการ" left-set, with the hairline between the search/filter block
   and the results block continuing right after it (2026-07-29 ask, 5th
   pass) — text and line are separate flex children (text auto-width, line
   flex-growing to fill the rest), not one full-width line masked behind
   the text with a solid background patch: that patch didn't scroll with
   the page's fixed-attachment gradient background, so it read as an
   obvious solid block once the page was scrolled. Transparent, no masking
   needed since there's nothing under the text to hide. */
.nt-results-straddle { display: flex; align-items: center; gap: 10px; margin: 10px 0; }
.nt-results-line { flex: 1 1 auto; height: 0; border: none; border-top: 1px solid var(--border); margin: 0; }
.nt-results-count { flex: 0 0 auto; font-size: 11.5px; color: var(--text-secondary); }
.nt-results-count:empty { display: none; }
.nt-results-count:empty + .nt-results-line { display: none; }
/* Only the number itself is bold (2026-07-29 ask, 4th pass) — "พบ"/
   "รายการ" around it stay regular weight. */
.nt-results-count__num { font-weight: 700; }
/* "จากทั้งหมด (X รายการ)" suffix, only shown while a filter/search narrows
   the list (2026-07-29 ask) — dimmer than "พบ N รายการ" so it reads as
   secondary context, not a second fact of equal weight. */
.nt-results-count__total { opacity: 0.6; }

/* Data-provenance notice — a light teal tint (not the neutral
   .nt-detail__info gray used for other banners) so it reads as a little bit
   colorful/worth-noticing, without the stronger gold .nt-detail__warning
   treatment that would make it look urgent. */
.nt-provenance-notice {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  padding: 10px 12px;
  border-radius: 9px;
  background: rgba(var(--accent-teal-rgb),0.07);
  border: 1px solid rgba(var(--accent-teal-rgb),0.2);
}
.nt-provenance-notice .emoji-icon { flex-shrink: 0; margin-top: 1px; }

/* Grid — mobile-first (2026-07-24 rework, was desktop-first 3→2→1):
   2 columns by default (this page is a browse/discovery feed, meant to show
   plenty at once), 1 only once the viewport is genuinely narrow, scaling up
   to 4 on desktop. */
.nt-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
/* 1-col fallback only below 360px — narrower than a standard phone (375px+
   stays 2-col, per 2026-07-24 ask: this feed should show as much as
   possible even on mobile; only genuinely cramped widths drop to 1). */
@media (max-width: 359px) { .nt-grid { grid-template-columns: 1fr; gap: 14px; } }
@media (min-width: 768px) { .nt-grid { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
@media (min-width: 1100px) { .nt-grid { grid-template-columns: repeat(4, 1fr); gap: 20px; } }

.nt-panel-loading { font-size: 12px; color: var(--text-secondary); padding: 8px 0; }
.nt-empty {
  grid-column: 1 / -1;
  width: 100%;
  padding: 32px;
  text-align: center;
  color: var(--text-secondary);
  font-size: 12px;
  border: 1px dashed var(--border);
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.nt-empty__icon { font-size: 30px; opacity: 0.5; }
.nt-clear-link { background: none; border: none; color: var(--accent-teal); font-family: var(--font-thai); font-size: 11px; cursor: pointer; text-decoration: underline; }

.nt-loadmore-host { display: flex; justify-content: center; }
/* Bigger + more inviting (2026-07-25 ask) — up from 10px/24px padding and
   12px font, plus a hover lift/fill (2-3% scale + brightness, same "button
   feedback" micro-interaction as WEBSYS_MD/FEEL_LAB_REFERENCE.md's "Micro"
   category #1) instead of just a flat background tint on hover. */
.nt-btn--loadmore {
  padding: 14px 34px;
  border-radius: 999px;
  border: 1.5px solid var(--accent-gold);
  background: rgba(var(--accent-gold-rgb),0.08);
  color: var(--accent-gold);
  font-family: var(--font-thai);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.nt-btn--loadmore:hover {
  background: rgba(var(--accent-gold-rgb),0.18);
  transform: translateY(-1px) scale(1.02);
  box-shadow: 0 6px 18px rgba(var(--accent-gold-rgb),0.2);
}
.nt-btn--loadmore:active { transform: translateY(0) scale(0.98); }

/* Skeleton placeholder cards, shown while the initial fetch is in flight
   instead of a spinner (2026-07-25 ask, per WEBSYS_MD/FEEL_LAB_REFERENCE.md's
   "Waiting" category #1) — same rounded/bordered shape as .nt-card so the
   grid's eventual layout is visible immediately. */
@keyframes nt-skel-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }
.nt-skel-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.nt-skel-card__image { aspect-ratio: 3 / 2; background: var(--bg-elevated); animation: nt-skel-pulse 1.5s ease-in-out infinite; }
.nt-skel-card__body { padding: 12px; display: flex; flex-direction: column; gap: 8px; }
.nt-skel-line { height: 10px; border-radius: 4px; background: var(--bg-elevated); animation: nt-skel-pulse 1.5s ease-in-out infinite; }
.nt-skel-line--md { width: 85%; }
.nt-skel-line--sm { width: 55%; }

/* Staggered fade+rise on entry (2026-07-25 ask, Feel Lab-inspired) — each
   card's animation-delay is set inline per its index (see render() in
   giveaway.js). backwards fill keeps it at the "from" state during that
   delay instead of flashing full-opacity before the animation starts. */
@keyframes nt-card-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.nt-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0; /* grid items default to min-width:auto (2026-07-25 fix) — any
    unbreakable content inside (long organizer handle, footer button labels)
    was letting its own grid track grow past its fair 1fr share, making the
    2-column mobile grid uneven instead of a true 50/50 split. */
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 14px;
  animation: nt-card-enter 0.25s ease-out backwards;
  /* overflow:hidden removed (2026-07-24 fix, to let .nt-card__mine float
     above the card's own top edge like Go Home's badge does) — the rail and
     image below now round their own top corners directly instead of relying
     on the card clipping them, so nothing needs the card-level clip. */
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
/* Own-card highlight — border/tint/badge colored by the card's OWN entry
   type (2026-07-24 fix: was hardcoded gold regardless of type; giveaway.js
   now always adds a `.nt-card--giveaway`/`.nt-card--fan_project` type class
   too, not just on the rail, so this can target it), same convention as
   Go Home's own-row treatment (gohome.css .gh-route-card--own/__mine,
   floating above the card's own top-right edge, not an inline pill).
   Repeated at :hover explicitly (2026-07-24 fix) — the generic
   .nt-card:hover rule below sets a neutral border-color for every card,
   which was silently overwriting this own-card color on hover, making the
   "this is mine" cue disappear right when you're looking straight at it. */
.nt-card--own.nt-card--giveaway { border: 2px solid var(--accent-gold); background: rgba(var(--accent-gold-rgb),0.06); }
.nt-card--own.nt-card--fan_project { border: 2px solid var(--accent-teal); background: rgba(var(--accent-teal-rgb),0.06); }
.nt-card--own.nt-card--giveaway:hover { border-color: var(--accent-gold); }
.nt-card--own.nt-card--fan_project:hover { border-color: var(--accent-teal); }
.nt-card__mine {
  position: absolute;
  top: -8px;
  right: 10px;
  z-index: 2;
  font-size: 9px;
  font-weight: 700;
  color: var(--gold-btn-text);
  padding: 3px 9px;
  border-radius: 999px;
}
.nt-card--giveaway .nt-card__mine { background: var(--accent-gold); }
.nt-card--fan_project .nt-card__mine { background: var(--accent-teal); color: var(--teal-btn-text); }
/* Keeps dark mode's vivid accents + dark text in light mode too (2026-08-08
   ask) — solid fill + in-box text has self-contained contrast, independent
   of the page background, so light mode's muted accents aren't needed here. */
:root[data-theme="light"] .nt-card--giveaway .nt-card__mine { background: #F4A140; color: #1a1300; }
:root[data-theme="light"] .nt-card--fan_project .nt-card__mine { background: #13CDCB; color: #072626; }
.nt-card:hover { transform: translateY(-3px); border-color: rgba(var(--surface-rgb),0.2); box-shadow: 0 12px 28px rgba(var(--shadow-rgb), calc(0.35 * var(--shadow-mult))); }
/* Rail + image share ONE clipping wrapper now (2026-07-24 fix — the actual
   cause of the mismatched/kinked corner spotted on normal cards): the rail
   is only 4px tall, and a border-radius bigger than roughly half an
   element's own height gets clamped by the browser to fit — asking a 4px
   strip to render a 13px corner produces a much flatter, sharper curve than
   the 13px curve rendered correctly on the much-taller image right below
   it, so the two curves visibly didn't line up at the seam. Wrapping both
   in one tall-enough box and letting THAT single box clip+round means
   there's only one curve being rendered at that corner, not two mismatched
   ones. `.nt-card__mine` stays a direct child of `.nt-card` (outside this
   wrapper) so it can still float above without being clipped. */
.nt-card__top { overflow: hidden; border-radius: 13px 13px 0 0; }
.nt-card--own .nt-card__top { border-radius: 12px 12px 0 0; }

/* Type-identity rail — a 4px strip at the very top of every card so the grid
   reads type at a glance (gold=giveaway / teal=fan project) without having
   to read the badge on the photo (2026-07-24 hierarchy pass). No radius of
   its own now — .nt-card__top above handles the corner. */
.nt-card__rail { height: 4px; width: 100%; flex-shrink: 0; }
.nt-card__rail--giveaway { background: var(--accent-gold); }
.nt-card__rail--fan_project { background: var(--accent-teal); }
:root[data-theme="light"] .nt-card__rail--giveaway { background: #F4A140; }
:root[data-theme="light"] .nt-card__rail--fan_project { background: #13CDCB; }
/* Hidden on own-cards (2026-07-24 fix — this was the "extra colored line"
   spotted at the top edge): the 4px rail and the card's own 2px type-colored
   border sit flush against each other in the exact same color, doubling up
   the same signal right next to itself instead of reading as one clean
   edge. The border alone already says "this is a {type} card, and it's
   yours" — the rail underneath it is pure redundant clutter here. */
.nt-card--own .nt-card__rail { display: none; }

/* 3:2 landscape crop for every card image (2026-07-24 ask, replacing an
   earlier 2:3) — a fixed ratio so the grid stays perfectly aligned
   regardless of each source photo's own dimensions; object-fit:cover on the
   <img> below crops the DISPLAY only (non-destructive — the underlying
   file/upload is untouched), same as every other card image on the site
   (mk-card__image, md-card__image). The no-image placeholder sits in the
   exact same 3:2 box so empty cards still line up with populated ones.
   No radius/overflow of its own now — .nt-card__top handles the corner
   (see above). */
.nt-card__image {
  position: relative;
  aspect-ratio: 3 / 2;
  background: var(--bg-elevated);
}
/* Fade in once loaded instead of popping in abruptly (2026-07-24 ask) — the
   container's own --bg-elevated background (behind the still-transparent
   <img>) reads as a plain loading placeholder in the meantime. */
/* Dedicated class, NOT a bare ".nt-card__image img" descendant selector
   (2026-07-24 fix) — the type-badge emoji (🎁/⭐) also renders as an <img
   class="emoji-icon"> nested inside this same .nt-card__image wrapper (every
   emojiEl() output is an <img>, per emoji-icons.js), so the old selector
   caught it too and left it permanently opacity:0 since nothing ever called
   fadeInImage() on it — a real recurrence of the "shared selector, unrelated
   consumer breaks" pattern already logged in TROUBLESHOOTING_BIBLE.md §6.9. */
.nt-card__photo { width: 100%; height: 100%; object-fit: cover; display: block; opacity: 0; filter: blur(16px); transition: opacity 0.35s ease, filter 0.35s ease; }
.nt-card__photo.is-loaded { opacity: 1; filter: blur(0); }
.nt-card__image-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  font-size: 26px;
  opacity: 0.3;
}
.nt-card__type-badge {
  position: absolute;
  top: 10px;
  left: 10px;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(7, 7, 26, 0.75);
  /* Fixed light text — this badge overlays a photo card and stays
     permanently dark regardless of theme (like .nt-card__save next to it),
     so text must be pinned too, not left to var(--text-primary) which
     flips dark in light mode and disappears on this still-dark badge. */
  color: #fff;
  font-size: 10.5px;
  font-weight: 600;
}
.nt-card__type-badge .emoji-icon { width: 13px; height: 13px; }
.nt-card__save {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: rgba(7, 7, 26, 0.75);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  cursor: pointer;
  transition: color 0.15s ease, transform 0.1s ease;
}
.nt-card__save:hover { transform: scale(1.08); }
/* Saved color matches the entry's own type (2026-08-03 ask) — was always
   gold regardless of type; now gold/giveaway, teal/fan_project, same
   convention as .nt-card--giveaway/--fan_project and the type pills. */
.nt-card__save--giveaway.is-saved { color: var(--accent-gold); }
.nt-card__save--fan_project.is-saved { color: var(--accent-teal); }

.nt-card__body { flex: 1; padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; }
.nt-card__title {
  font-size: 17px;
  font-weight: 700;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Category/item pills — ONE shared treatment for both entry types now
   (2026-07-24 fix). Fan project categories and giveaway items are the exact
   same underlying shape (category_breakdown: [{category, qty}]) but used to
   render as two unrelated components — loose teal pills for one, a bordered
   box with table-ish rows for the other, different font sizes for each.
   Both now render as this one pill, qty picked out in gold within it. */
.nt-chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
/* -15% from the original 4px/11px padding, 11px font (2026-07-25 ask).
   Card only now — buildCategoryPills() is no longer called in the detail
   modal (2026-07-25 fix: it duplicated the modal's own "รายละเอียด Giveaway"
   box, which covers the same category_breakdown data in more detail). */
.nt-pill { padding: 3.4px 9.35px; border-radius: 999px; font-size: 9.35px; font-weight: 600; }
/* Regular weight, not bold (2026-07-25 ask) — was 700, heavier than the
   pill's own 600 label text, making the qty number stand out more than
   intended. */
.nt-pill__qty { color: var(--accent-gold); font-weight: 400; }
/* Category/item pills colored by entry type (2026-07-24 ask) — gold for
   giveaway, teal for fan project, instead of always-teal regardless of
   type. buildCategoryPills() adds one of these two modifiers to every pill
   it builds now. */
.nt-pill--giveaway { background: rgba(var(--accent-gold-rgb),0.12); color: var(--accent-gold); }
.nt-pill--fan_project { background: rgba(var(--accent-teal-rgb),0.12); color: var(--accent-teal); }
/* On a gold pill the qty span needs to contrast against the pill's own
   gold text instead of matching it (which would erase the emphasis) — swap
   to white there; teal pills keep the default gold qty for contrast. */
.nt-pill--giveaway .nt-pill__qty { color: var(--text-primary); }

/* "รายละเอียด Giveaway" box — bordered/legend-label frame matching sponsor's
   .sp-prize-box (p/sponsor.css), but holding a bullet list instead of pills:
   per-category variant detail (ลาย/วันที่/ขนาด/สุ่ม) that got stripped out of
   the short category pill name (2026-07-24 ask). Detail modal only. */
.nt-detail-box {
  position: relative;
  margin-top: 14px;
  padding: 14px 12px 10px;
  border: 1px solid var(--border); border-radius: 10px;
  background: rgba(var(--surface-rgb),0.02);
}
.nt-detail-box-label {
  position: absolute;
  top: -8px; left: 10px;
  padding: 0 6px;
  /* Was var(--bg-secondary) — a DIFFERENT token from .nt-detail-box's own
     background (a 2% surface tint over the modal card's real background,
     var(--modal-bg) from .auth-modal__card). The two tokens' dark-mode
     values (#141527 vs #0E0F1F) are close enough that the seam where this
     label crosses the box's border was never visible; light mode's more
     separated values (#DBD4E9 vs #F1EDF9) exposed it as a visible color
     patch (2026-08-08 fix — this bug predates light mode, dark mode just
     never showed it). Matching --modal-bg (not --bg-elevated — corrected
     same day once the modal shell's own dark-mode drift was found) makes
     the label actually blend into the border the way it was always meant
     to, in both themes. */
  background: var(--modal-bg);
  font-size: 10px;
  color: var(--text-secondary);
}
.nt-detail-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
.nt-detail-list__item { font-size: 12px; color: var(--text-secondary); line-height: 1.5; }
.nt-detail-list__cat { color: var(--accent-gold); font-weight: 600; }

.nt-card__meta { display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: var(--text-secondary); }
/* Base size/color declared explicitly (2026-07-24 fix) — this class used to
   rely entirely on inheriting 11px/secondary from .nt-card__meta's own
   context; used bare anywhere else (e.g. the submit form's old helper
   lines) it fell back to the page's default body text — large and white,
   not "explanatory text" at all. */
.nt-meta-line { font-size: 11px; color: var(--text-secondary); }
.nt-round-line { }
/* Multiple rounds now share one line, separator-joined (2026-07-25 ask) —
   is-active moved from the line itself to each round's own span, since a
   line can mix an active and an inactive round together. */
.nt-round-line__item.is-active { color: var(--accent-gold); font-weight: 700; }
.nt-round-line__sep { color: var(--text-secondary); opacity: 0.55; }

.nt-card__organizer { display: flex; align-items: center; gap: 8px; font-size: 12px; }
/* Absorbs the card's leftover flex space (moved here from
   .nt-card__organizer's own margin-top:auto, 2026-07-24 ask) — a visible
   hairline instead of just blank space above the organizer row. */
/* margin-top:auto still does the real work here (2026-07-24 fix — a prior
   pass dropped it entirely in favor of a flat 6px, which broke the
   organizer+footer block's pin-to-bottom-of-card behavior: cards in the
   same grid row are stretched to the tallest one, and without an auto
   margin absorbing that leftover height, the divider/organizer/footer
   group just sat wherever the content above it ended, floating in the
   middle of the card instead of settling at the bottom). auto still
   correctly pulls the divider AWAY from the content above (that's exactly
   what "leftover space" means — the gap between content and card bottom);
   the negative margin-bottom is what keeps it hugging the organizer row
   below it rather than centering itself between the two. */
/* margin-bottom eased from -6px to -2px (2026-07-24 ask) — net gap to the
   organizer row below goes from ~5px to ~9px (body's 11px flex gap minus
   2px), noticeably more breathing room than before while still sitting
   closer to the organizer than to the content above it. */
.nt-card__divider { height: 0; border-top: 1px solid var(--border); margin: auto 0 0; }
.nt-card__organizer-who { min-width: 0; }
.nt-card__organizer-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Real display name, shown only when it differs from the @handle above
   (2026-08-03 fix/ask) — bold like the handle line above it, not muted like
   .nt-card__organizer-credits (a real name, not incidental metadata). */
.nt-card__organizer-secondary { font-size: 10px; font-weight: 700; color: var(--text-secondary); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "ร่วมจัดโดย" (2026-07-29 ask) — same tiny/muted treatment as the
   "ประกาศเมื่อ" line convention (.nt-meta-line--posted below), truncated since
   the card column is narrow and credited-handle lists can run long. */
.nt-card__organizer-credits { font-size: 10px; color: var(--text-secondary); opacity: 0.75; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "ประกาศเมื่อ" line — same tiny/muted treatment as Marketplace's own
   seller-label line under the seller name (market.css .mk-card__seller-label). */
.nt-meta-line--posted { font-size: 10px; color: var(--text-secondary); opacity: 0.75; margin-top: 1px; }
.nt-card__avatar {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1.5px solid var(--border);
  font-size: 13px;
}
/* Organizer not yet a linked member (§ ask 2026-07-24) — transparent/outline
   circle with the account name's first uppercase letter, instead of a
   generic person icon, so it visually reads as "not in the system yet". */
.nt-card__avatar--fallback { background: transparent; font-weight: 700; font-size: 12px; color: var(--text-secondary); }

/* Footer — small capsule buttons, matching the site's own established card
   action-button convention (2026-07-24 fix: a prior pass made these bare
   underlined text links, which no other card on the site does — see
   market.css .mk-report-btn/.mk-card__dm and md.css .md-fav-btn, all
   bordered pills sized to their own content, never full-width boxes). */
/* No divider above this row anymore (2026-07-24 ask — was a border-top
   separating it from the organizer row right above); both link buttons
   always right-aligned instead of left-packed. */
/* nowrap, not wrap (2026-07-25 ask — "ปุ่มทั้ง 2 ปุ่มต้องอยู่แถวเดียวกัน") —
   each .nt-card__link gets flex:1 + ellipsis below so the pair always shares
   the row evenly and truncates instead of wrapping to a second line, even
   on a narrow 2-column mobile card. */
.nt-card__footer { display: flex; align-items: center; justify-content: flex-end; gap: 6px; padding-top: 4px; flex-wrap: nowrap; }
.nt-card__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 6px 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  text-decoration: none;
  font-family: var(--font-thai);
  font-size: 10.5px;
  font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
  flex: 1 1 0;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nt-card__link svg { width: 9px; height: 9px; flex-shrink: 0; }
.nt-card__link:hover { color: var(--text-primary); border-color: var(--text-primary); background: rgba(var(--surface-rgb),0.05); }

@media (max-width: 640px) {
  .nt-page { padding: 16px 16px 16px; }
  .nt-toolbar__controls { display: flex; flex-wrap: wrap; }
  .nt-toolbar__controls .nt-select-wrap { flex: 1 1 130px; }
  /* Smaller footer buttons on mobile (2026-07-25 ask, shrunk further on a
     follow-up ask) — a 2-column card is only ~170px wide there; the desktop
     button size left almost no room before ellipsis kicked in. */
  .nt-card__link { padding: 4px 6px; font-size: 8.5px; gap: 3px; }
  .nt-card__link svg { width: 7px; height: 7px; }

  /* Card text scaled down for the narrower 2-column mobile card (2026-07-25
     ask) — tablet/desktop keep their existing sizes untouched, only this
     max-width:640px block changes. */
  .nt-card__title { font-size: 14.5px; }
  .nt-pill { padding: 3px 8px; font-size: 8.5px; }
  .nt-card__meta, .nt-meta-line { font-size: 9.5px; }
  .nt-card__organizer { font-size: 10.5px; }
  .nt-meta-line--posted { font-size: 9px; }

  /* Type badge (🎁 Giveaway / ⭐ Fan Project) -15% + both it and the
     bookmark button pulled closer to the card's edge (2026-07-25 ask —
     read as floating too loose on a narrow mobile card). */
  .nt-card__type-badge { top: 6px; left: 6px; padding: 3.4px 8.5px; font-size: 9px; }
  .nt-card__type-badge .emoji-icon { width: 11px; height: 11px; }
  .nt-card__save { top: 5px; right: 5px; }
}

/* ── Detail modal (§9.3) — .auth-modal__card's own overflow-y:auto (auth.css)
   is turned OFF for this variant; only .nt-detail__body scrolls, so the
   scrollbar/scroll region covers exactly the part that moves, not the fixed
   header above it too (2026-07-24 fix — position:sticky kept the header
   visually frozen, but it was still part of the same scrolling box, so the
   native scrollbar spanned the whole card including the space "behind" the
   frozen header). .auth-modal__head is a shared class used by every modal
   on the site, so all of this is scoped to .nt-detail only. */
/* gap:0 explicitly (2026-07-24 fix) — .auth-modal__card's own base rule
   (auth.css) sets gap:14px for every modal on the site; left un-reset here,
   that 14px stacked on top of .nt-detail__body's own 14px padding-top below,
   doubling the visual gap between the header and the first thing under it
   to ~28px. */
.nt-detail.auth-modal__card { padding: 0; gap: 0; overflow: hidden; display: flex; flex-direction: column; }
.nt-detail .auth-modal__head {
  flex-shrink: 0;
  /* Bottom padding eased 18px→12px (2026-07-24 fix) — this and
     .nt-detail__body's own top padding are two independent gaps that stack
     into the total space below the frozen header; both were sized
     generously on their own, adding up to ~32px combined even though
     neither was individually "wrong." The border-bottom above already
     marks the boundary, so the two paddings together only need to read as
     one comfortable gap, not two. */
  /* Top eased 24px→20px→16px across two follow-up asks (2026-07-25 — still
     read as too much air above the title) — the close button is
     position:absolute against .auth-modal__card, not this header, so
     shrinking this padding further doesn't risk it overlapping the title
     text. Bottom stays at 12px, still deliberately less per the
     stacking-gap note above; sides stay 20px. */
  padding: 16px 20px 12px;
  /* Was var(--bg-secondary) — originally this header and .auth-modal__card's
     own body were the exact same literal #0E0F1F (one flat-colored modal,
     no header/body distinction); an earlier conversion pass gave the header
     a DIFFERENT token than the body ended up with, so once each token got
     its own distinct light-mode value, the header started visibly
     mismatching the body right at this boundary (2026-08-08 fix — restores
     the original one-color modal by matching --modal-bg, same as the body). */
  background: var(--modal-bg);
  border-bottom: 1px solid var(--border);
}
.nt-detail__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 10px 20px 20px;
}
/* Type strip — its own line above the title, colored by entry type
   (2026-07-24 ask: was a pill mixed into the category row further down;
   now the very first thing you see, and stays visible while scrolling). */
.nt-detail__type-strip { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; margin-bottom: 4px; }
.nt-detail__type-strip--giveaway { color: var(--accent-gold); }
.nt-detail__type-strip--fan_project { color: var(--accent-teal); }

/* Wraps the detail image so the floating zoom button (below) can anchor to
   the image's own rendered edges, not the modal body — align-self:center
   lets the wrap shrink to the image's actual width instead of stretching
   full-width (2026-07-27 fix), which is what let a landscape/near-square
   image (object-fit:contain below) get letterboxed by max-height clamping
   its box narrower than width:100% would otherwise force. */
/* Sizing (width/height) and border/radius/overflow both live here, in
   explicit PIXEL values set by fitDetailImage() (giveaway.js) — not CSS
   aspect-ratio/width:auto/max-width percentages. Several earlier attempts
   this session tried various CSS-only combinations and each broke a
   different way once `overflow:hidden` was added here (for the blur-
   placeholder frame, see .nt-detail__image--placeholder below): aspect-ratio
   on a non-replaced box with no definite axis rendered tiny; auto-height
   under overflow:hidden mis-measured a dynamically-resized child (cropped
   ~half the photo); and finally, even with img sized correctly on its own,
   img's `max-width:100%` and this wrap's `max-width:100%` could each resolve
   against a different effective width depending on viewport/timing (a
   percentage-against-an-indefinite-ancestor case), so the two would quietly
   disagree — image overflowing its own frame on some entries but not others.
   fitDetailImage() sidesteps all of it by computing the target box from
   `.nt-detail__body`'s own definite content width directly, in JS, with
   nothing "auto" left for either element to disagree about.

   flex-shrink: 0 is a separate, equally real fix found along the way:
   `.nt-detail__body` (this wrap's flex-column parent) has a constrained
   height with its own `overflow-y: auto` for scrolling — but a flex item
   defaults to `flex-shrink: 1`, so the flex algorithm was squeezing this
   wrap SMALLER (varying per entry by how much other modal content —
   title/description/buttons — competed for the same space) to make
   everything fit, instead of ever letting the intended scrollbar kick in. */
.nt-detail__image-wrap {
  position: relative;
  display: block;
  align-self: center;
  flex-shrink: 0;
  max-width: 100%;
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
}

/* Fills the wrap exactly (wrap owns the real pixel size, see above) —
   object-fit set by fitDetailImage(): 'contain' for normal ratios (nothing
   cut off), 'cover' for anything beyond the 4:5 portrait cap (rounded
   corners come from the wrap's own clip). Click still opens the lightbox
   with the full, uncropped image regardless of which mode this picked. */
.nt-detail__image {
  display: block;
  width: 100%;
  height: 100%;
  cursor: zoom-in;
  transition: opacity 0.35s ease, filter 0.35s ease, transform 0.35s ease;
}
/* Thumbnail-as-placeholder state (2026-07-29) — faded + blurred while only
   the low-res thumbnail has loaded; removed once the full-res photo swaps
   in (same box/ratio throughout, see giveaway.js's openDetailModal).
   scale(1.15) pushes the blur's own soft/faded edge (filter blur samples
   past the image's real edge, which otherwise reads as a fuzzy border right
   inside the wrap's crisp one) outside the wrap's overflow:hidden clip —
   the wrap's border stays sharp, only the photo itself looks blurred. */
.nt-detail__image--placeholder { opacity: 0.55; filter: blur(20px); transform: scale(1.15); }

/* Faint corner zoom hint (2026-07-27 ask) — signals the image is clickable
   to zoom without competing visually with the photo itself; brightens only
   on hover/focus. */
.nt-detail__zoom-btn {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(0,0,0,0.35);
  color: #fff;
  opacity: 0.55;
  cursor: zoom-in;
  transition: opacity 0.2s ease;
}
.nt-detail__zoom-btn svg { width: 14px; height: 14px; }
.nt-detail__zoom-btn:hover, .nt-detail__zoom-btn:focus-visible { opacity: 1; }
/* Quick-actions row — แก้ไข (owner only) + บันทึกไว้ side by side, right
   under the image (2026-07-24 reorder ask). Each button shares the row
   equally; with only one (no edit access) it just takes the full width. */
.nt-detail__actions { display: flex; gap: 10px; }
.nt-detail__actions > * { flex: 1; }
/* Section dividers — a plain hairline between each logical group (actions /
   organizer / what+when / description), instead of everything running
   together at the same 14px flex gap with nothing to break it up
   (2026-07-24 ask — "เรียงเป็นพรืด อ่านยาก"). */
/* border-top on a height:0 box, not height:1px + background (2026-07-24
   fix — the height:1px + background approach can round away to 0 rendered
   pixels at fractional display-scaling/zoom, e.g. Windows' common 125%/150%
   scaling, making the line vanish entirely on some machines while looking
   fine at 100% zoom. A 1px border is snapped to a visible hairline by the
   browser far more reliably across zoom/DPI than a computed box height.
   Same faint opacity as var(--border) — this only fixes reliability, not
   visibility strength. */
.nt-detail__divider { height: 0; border-top: 1px solid rgba(var(--surface-rgb),0.08); margin: -3px 0; }
.nt-detail__info, .nt-detail__warning {
  font-size: 11.5px;
  padding: 10px 12px;
  border-radius: 9px;
  color: var(--text-secondary);
}
.nt-detail__info { background: rgba(var(--surface-rgb),0.04); border: 1px solid var(--border); }
.nt-detail__warning { background: rgba(var(--accent-gold-rgb),0.1); border: 1px solid rgba(var(--accent-gold-rgb),0.35); color: var(--text-primary); }

/* Ownership-claim notice at the very bottom of the detail modal (2026-07-24
   ask) — deliberately faded/quiet, not a banner, so it doesn't compete with
   the actual content above. Distinct from .nt-detail__info/__warning (still
   used at normal weight elsewhere, e.g. the submit form's gate messages). */
.nt-detail__notice {
  font-size: 10px;
  color: var(--text-secondary);
  opacity: 0.6;
  text-align: center;
  padding-top: 4px;
}
.nt-detail__notice--warning { opacity: 0.75; }
.nt-detail__notice .nt-detail__link-btn { font-size: 10px; opacity: 1; }
.nt-detail__link-btn {
  background: none;
  border: none;
  color: var(--accent-teal);
  font-family: var(--font-thai);
  font-size: 11.5px;
  text-decoration: underline;
  cursor: pointer;
  padding: 0;
}
/* รหัสร่วมกิจกรรม box (2026-08-03 redesign) — reuses .nt-detail-box's
   border/label shell (position:relative, radius, padding) but swaps in a
   gold-tinted dashed border + soft gradient fill so it reads as a distinct
   "special" box rather than the neutral gray ones above it, and stretches
   full-width like every other block in the detail body instead of shrinking
   to content (the old .nt-detail__code pill used align-self:flex-start). */
.nt-detail__code {
  display: block;
  width: 100%;
  text-align: left;
  align-self: stretch;
  border-color: rgba(var(--accent-gold-rgb),0.5);
  border-style: dashed;
  background: linear-gradient(135deg, rgba(var(--accent-gold-rgb),0.14), rgba(var(--accent-gold-rgb),0.03));
  /* <button> doesn't inherit the page's font by default (browser UA
     stylesheet quirk) — without this, every child (label/value/hint) that
     doesn't set its own font-family falls back to the system default
     (Arial), which is exactly how .nt-detail__code-hint went unstyled
     (2026-08-03 found: its color/size rules were applied, but font-family
     never was). Setting it once here covers every current and future child. */
  font-family: var(--font-thai);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.08s ease;
}
.nt-detail__code:hover { background: linear-gradient(135deg, rgba(var(--accent-gold-rgb),0.2), rgba(var(--accent-gold-rgb),0.05)); border-color: var(--accent-gold); }
.nt-detail__code:active { transform: scale(0.99); }
.nt-detail__code-label { color: var(--accent-gold); font-weight: 400; letter-spacing: 0.4px; }
.nt-detail__code-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 14px;
}
.nt-detail__code-value {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-thai);
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: var(--accent-gold);
}
.nt-detail__code-key { width: 1.05em; height: 1.05em; }
.nt-detail__code-hint {
  font-family: var(--font-thai);
  font-size: 10.5px;
  font-weight: 400;
  color: var(--text-secondary);
  opacity: 0.75;
  white-space: nowrap;
}
.nt-detail__save-btn { display: flex; align-items: center; justify-content: center; gap: 6px; }
/* White by default (2026-08-03 ask — this button had no color/background at
   all before, falling through to the browser's unstyled default; `.auth-btn`
   alone sets no color/background, only a modifier variant does and none was
   ever applied here), switching to the entry's own type color (gold/
   giveaway, teal/fan_project — same convention as .nt-card--giveaway/
   --fan_project and the type pills) on hover or once actually saved
   (`.is-saved`, toggled in JS same as the card's bookmark button). */
.nt-detail__save-btn--giveaway,
.nt-detail__save-btn--fan_project { background: #fff; color: #12121A; }
.nt-detail__save-btn--giveaway:hover:not(:disabled),
.nt-detail__save-btn--giveaway.is-saved { background: var(--accent-gold); color: var(--gold-btn-text); }
.nt-detail__save-btn--fan_project:hover:not(:disabled),
.nt-detail__save-btn--fan_project.is-saved {
  background: var(--accent-teal);
  /* Was fixed #04302f — sibling to the --giveaway rule above which
     correctly uses var(--gold-btn-text); this one drifted to a literal
     instead of var(--teal-btn-text), measuring 3.5:1 in light mode
     (2026-08-08 fix). */
  color: var(--teal-btn-text);
}
/* Keeps dark mode's vivid accents + dark text in light mode too (2026-08-08
   ask) — solid fill + in-box text has self-contained contrast, independent
   of the page background, so light mode's muted accents aren't needed here. */
:root[data-theme="light"] .nt-detail__save-btn--giveaway:hover:not(:disabled),
:root[data-theme="light"] .nt-detail__save-btn--giveaway.is-saved { background: #F4A140; color: #1a1300; }
:root[data-theme="light"] .nt-detail__save-btn--fan_project:hover:not(:disabled),
:root[data-theme="light"] .nt-detail__save-btn--fan_project.is-saved { background: #13CDCB; color: #072626; }

/* วิธีรับของ (2026-07-27) — border-label sits on the box edge same as
   "รายละเอียด Giveaway"'s .nt-detail-box-label just above it, but keeps its
   own teal-tinted frame (2026-07-27 follow-up ask — match the label
   placement only, not the neutral .nt-detail-box background/border) and
   stacked text lines instead of a bullet list. */
.nt-detail__redemption {
  background: rgba(var(--accent-teal-rgb),0.06);
  border-color: rgba(var(--accent-teal-rgb),0.2);
}
.nt-detail__redemption-label { color: var(--accent-teal); opacity: 0.85; }
.nt-detail__redemption-method { font-size: 12px; font-weight: 700; color: var(--accent-teal); }
/* Bullet marker added 2026-07-30 — redemption_notes is now stored as a
   curated bullet list (one real redemption instruction per line), each
   rendered as its own .nt-detail__redemption-notes div; a leading "•" makes
   that list structure visible instead of reading as unrelated stacked
   sentences with no visual grouping. */
.nt-detail__redemption-notes { font-size: 11.5px; color: var(--text-secondary); margin-top: 4px; padding-left: 14px; position: relative; }
.nt-detail__redemption-notes::before { content: '•'; position: absolute; left: 2px; color: var(--accent-teal); }
.nt-detail__redemption-notes:first-of-type { margin-top: 6px; }

/* ร่วมจัดโดย (credits, 2026-07-27) — only rendered for credits NOT already
   named in the free-text description (see buildCreditsRow), so this stays a
   quiet inline row rather than its own boxed section. */
.nt-detail__credits { display: flex; flex-wrap: wrap; align-items: center; row-gap: 2px; column-gap: 6px; font-size: 11px; margin-top: -6px; }
.nt-detail__credits-label { color: var(--text-secondary); opacity: 0.75; }
.nt-detail__credit-item { color: var(--text-secondary); text-decoration: none; }
.nt-detail__credit-item:hover { color: var(--text-primary); }
.nt-detail__credit-item:not(:last-child)::after { content: ","; color: var(--text-secondary); }

/* related_links (2026-07-27) — inline text link(s) right under the
   description, not a footer button pill (footer stays reserved for the two
   primary source/organizer links). */
.nt-detail__related-links { font-size: 11px; margin-top: -4px; }
.nt-detail__related-link { display: inline-flex; align-items: center; gap: 4px; color: var(--accent-teal); text-decoration: underline; }
.nt-detail__related-link svg { width: 9px; height: 9px; flex-shrink: 0; }
.nt-detail__related-link:hover { color: var(--text-primary); }

/* ── Submit/edit form, WHEN rendered inside the popup (§ giveaway-form.js's
   openSubmitModal, which wraps its content in .nt-detail same as the detail
   modal above) — 2026-07-24 ask. Scoped to .nt-detail only: the standalone
   /p/giveaway/edit/ page renders the exact same form directly on the page
   (not inside another box), where .adm-form's own bg/border IS the only
   frame and stays correct as-is; giveaway-form.js/fanproject-form-builders.js
   are also shared with the Admin Console's own fan-project editor
   (admin-fanproject-edit.js), which must stay untouched — so these
   overrides target .nt-detail specifically, never the base classes. */
.nt-detail .adm-form {
  background: none;
  border: none;
  padding: 0;
  margin-bottom: 0;
  max-width: none;
}
/* Lighten the giveaway item-builder rows to the same visual weight as the
   fan-project category picker's chip list right above it in the same form
   (2026-07-24 ask — was a heavy bordered "card" per row while the category
   picker was a light inline chip row, two unrelated-looking pickers back to
   back). Plain hairline-separated rows instead of individual boxes. */
.nt-detail .adm-prize-row {
  background: none;
  border: none;
  border-radius: 0;
  padding: 12px 0;
  margin-bottom: 0;
  border-bottom: 1px solid var(--border);
}
.nt-detail .adm-prize-row:first-child { padding-top: 0; }
.nt-detail .adm-prize-row:last-of-type { border-bottom: none; padding-bottom: 4px; }

/* ── Guest→account save-merge popup (§5) ─────────────────────────────── */
.nt-merge-box { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin-bottom: 14px; max-height: 140px; overflow-y: auto; }
.nt-merge-box__label { font-size: 10.5px; color: var(--text-secondary); margin-bottom: 6px; }
.nt-merge-box__item { font-size: 12px; padding: 2px 0; }
.nt-merge-option {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  margin-bottom: 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(var(--surface-rgb),0.03);
  color: var(--text-primary);
  font-family: var(--font-thai);
  font-size: 12.5px;
  cursor: pointer;
}
.nt-merge-option.is-reco { border-color: var(--accent-gold); }
.nt-merge-badge { padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight: 700; background: var(--accent-gold); color: var(--gold-btn-text); }
/* Keeps dark mode's vivid gold + dark text in light mode too (2026-08-08 ask)
   — solid fill + in-box text has self-contained contrast, independent of
   the page background, so light mode's muted gold isn't needed here. */
:root[data-theme="light"] .nt-merge-badge { background: #F4A140; color: #1a1300; }
.nt-merge-footer { width: 100%; background: none; border: none; color: var(--text-secondary); font-family: var(--font-thai); font-size: 11px; cursor: pointer; padding: 6px; text-decoration: underline; }

/* Delete action's own zone at the bottom of the submit form (2026-07-24 fix)
   — a hairline divider + top margin so it never sits flush against the
   primary submit button above it. */
.nt-danger-zone { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }

/* Limited-markdown output (markdown.js sp-markdown classes) inside the modal */
.nt-detail .sp-markdown { font-size: 13px; color: var(--text-primary); line-height: 1.7; }
.nt-detail .sp-markdown__p { margin: 4px 0; }
.nt-detail .sp-markdown__list { margin: 4px 0; padding-left: 18px; }
.nt-detail .sp-markdown a { color: var(--accent-teal); }
.nt-detail .sp-markdown__p--gap, .nt-detail .sp-markdown__list--gap { margin-top: 14px; }
