/* The members bar, and what a visitor sees instead of the numbers
   (2026-09-16, DESIGN_entitlement.md).

   A separate stylesheet on purpose. Kept inside app.css this had to be
   merged every time main changed a line near it, and the demo silently
   missed fixes that had shipped. Additive, so the branch only ever adds
   files and app.css is byte-identical to main's. */

/* The members bar, and what a visitor does not see (2026-09-16,
   DESIGN_entitlement.md). Presentation only: market.json is public, so this
   decides what is drawn, never what can be obtained. */
.gate-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 14px 0 4px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--violet) 45%, transparent); border-left: 3px solid var(--violet);
  border-radius: var(--radius); background: color-mix(in srgb, var(--violet) 8%, var(--surface)); }
.gate-bar[hidden] { display: none; }
.gate-bar.gate-owner-only { border-color: var(--border); border-left-color: var(--text-faint); background: var(--surface); }
.gate-txt { min-width: 0; flex: 1 1 280px; }
.gate-k { font-size: 12px; font-weight: 700; letter-spacing: 0; text-transform: none; color: var(--violet); }
.gate-d { margin-top: 2px; font-size: 13px; color: var(--text-dim); line-height: 1.45; }
.gate-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-left: auto; }
.gate-cta { font-size: 13px; color: var(--text-dim); }
.gate-owner { display: flex; align-items: center; gap: 6px; margin-left: auto; font-size: 12px; color: var(--text-faint); }
.gate-owner button { all: unset; cursor: pointer; padding: 5px 10px; border: 1px solid var(--border); border-radius: 999px; font-size: 12px; color: var(--text-dim); }
.gate-owner button[aria-pressed="true"] { color: var(--text); border-color: var(--sky); background: color-mix(in srgb, var(--sky) 12%, transparent); }
.gate-owner button:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }

/* What a visitor sees (2026-09-16, DESIGN_entitlement.md).

   The line is: today is public, time is the product. Anything true right
   now from a public source stays readable; anything that needed the price
   history we collected is the membership.

   HOW it is withheld changed on 2026-09-16, on the owner's note: "I want to
   understand what I would get if I paid without reading anything, just by
   seeing … blurred, not aggressively, but enough that I can't see the
   value." The first version replaced each number with a grey block. It read
   as something still loading, nearly vanished in dark mode, and made a
   membership look like it bought one faint line per card. Now every
   member-only value sits exactly where a member sees it, in its own colour,
   softly out of focus: you can tell there is a green 30-day move, a red
   drawdown, a price axis, five movers with their numbers. You cannot read
   them. 0.22em left prices half guessable; 0.3em was meant not to.
   On the live site it still did: "Steady", a 30-day move and "14% up · 30d"
   could be read, and a selection paints blurred text sharp. Since
   2026-09-22 the words are not in the page — gate.js draws a blurred
   bitmap and drops the text. 0.3em remains the sparkline's soft edge.
   That line is the teaser. It has no words to copy. .gate-veiled turns
   this filter off so the bitmap is not blurred a second time into nothing.

   Three things about the engine, each learned the slow way:

   1. A blurred text-shadow over transparent text draws nothing in WebKit.
      filter: blur() on the element is what works.
   2. filter: blur() is ignored on SVG <text> in WebKit even though the
      computed style reports it. An SVG <filter> referenced by url() is
      honoured, so gate.js injects one (#gateBlur) and marks the body
      gate-svgblur. Without that mark the labels fall back to hidden, so a
      failure never turns into a leak.
   3. A filter is a compositing layer. 515 cards carry about 1,500 gated
      values, so a card's values are blurred only while the card is near the
      screen (gate.js adds gate-near) and simply not painted while it is
      far. Nothing is ever readable in between.

   MSRP, the shelf price and the multiple are public (owner, 2026-09-18:
   "I don't think MSRP should be gated"): the table's Multiple column is
   left clear like Price, and so is the MSRP tooltip.

   None of this withholds anything technically: market.json is public. This
   is presentation, recorded as such in DESIGN_entitlement.md. No id may
   appear in these lists; one once outranked the rule beneath it. */
body[data-gate="visitor"] { --gate-blur: 0.3em; }

/* A. Values outside cards and table rows. Few, so always blurred. */
body[data-gate="visitor"] :is(
  .mk-pct:not(.s-card *):not(.mk-table tr *),
  [id="mkBrief"],
  .mk-pulse-sum,
  .mk-pulse-signals .mk-signal > b,
  .mk-mover-price,
  .mk-score .mk-breadth,
  .mk-score b,
  .family-heading .fam-traj,
  .family-heading .s-ctx:is(.ctx-near_high, .ctx-recovering, .ctx-reprint_wave),
  .mk-compare-sum,
  .mk-idx-game,
  .chart-stats .cs-m .cs-v,
  .chart-stats .cs-sig small,
  .chart-sub:not(.cmp-sub)
) {
  filter: blur(var(--gate-blur));
  user-select: none;
}

/* B. Values inside a card, and the gated cells of a table row: not painted
   while far from the screen, blurred while near it. */
body[data-gate="visitor"] .s-card :is(
  .mk-value,
  .mk-pct-lbl,
  .mk-pct,
  .s-ctx:is(.ctx-near_high, .ctx-recovering, .ctx-reprint_wave),
  .s-read-ctx:is(.ctx-near_high, .ctx-recovering, .ctx-reprint_wave)
),
body[data-gate="visitor"] .mk-table tbody td:not(.name, .cmp, .price, .mult):not(:has(.mk-signal)) {
  visibility: hidden;
  user-select: none;
}
body[data-gate="visitor"] :is(.s-card.gate-near, .gate-all-near .s-card) :is(
  .mk-value,
  .mk-pct-lbl,
  .mk-pct,
  .s-ctx:is(.ctx-near_high, .ctx-recovering, .ctx-reprint_wave),
  .s-read-ctx:is(.ctx-near_high, .ctx-recovering, .ctx-reprint_wave)
),
body[data-gate="visitor"] :is(.mk-table tbody tr.gate-near, .gate-all-near .mk-table tbody tr) td:not(.name, .cmp, .price, .mult):not(:has(.mk-signal)) {
  visibility: visible;
  filter: blur(var(--gate-blur));
}

/* C. Chart labels. The price axis and the "high" marker are member data;
   dates, "released", the MSRP line and event numbers are not. A sweep with
   exceptions, so a label added later is withheld by default. */
body[data-gate="visitor"] .chart text:not(.cx):not(.crel-l):not(.cretail-l):not(.cev-n) { display: none; }
body[data-gate="visitor"].gate-svgblur .chart text:not(.cx):not(.crel-l):not(.cretail-l):not(.cev-n) { display: initial; filter: url(#gateBlur); }

/* The chart panel for a visitor (2026-09-17): the gated groups are not drawn
   at all, and one line names them, instead of eight blurred tiles. The price
   axis is simply absent rather than smudged. */
.cs-members { display: none; }
body[data-gate="visitor"] .chart-stats.cs-moves,
body[data-gate="visitor"] .chart-stats.cs-state,
body[data-gate="visitor"] .chart-stats-note { display: none; }
body[data-gate="visitor"] .cs-members { display: block; margin: 4px 0 10px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); font-size: 13px; color: var(--text-dim); }
body[data-gate="visitor"].gate-svgblur .chart text.cy { display: none; }

/* D. Stand-ins (decoy.js, 2026-09-18). A signed-out page now carries a
   made-up sparkline, 30-day move, range and signal where a member's real
   ones go, so there is something to blur instead of "no history yet". The
   old rules above were written when the numbers were in the page and left
   the sparkline and the card's signal chip crisp; with stand-ins those would
   be readable and fake, so they join the blur here — along with the names in
   Biggest movers, which would otherwise rank real products by made-up moves.

   Colour is neutral for every gated value: a blurred green arrow still says
   "rising", and a stand-in must not say anything. */
body[data-gate="visitor"] :is(
  .s-spark:not(.none), .s-spark-read, .s-spark-label,
  .s-badges-row .mk-signal, .mk-card .mk-signal, .mk-table td .mk-signal,
  .mk-pct, .mk-value
) {
  color: var(--text-faint) !important;
  border-color: var(--border) !important;
  background: transparent !important;
}
body[data-gate="visitor"] .s-spark:not(.none) :is(polyline, path, circle, line) { stroke: var(--text-faint) !important; fill: none; }
body[data-gate="visitor"] .s-spark:not(.none) circle { fill: var(--text-faint) !important; }
body[data-gate="visitor"] .s-card :is(.s-spark:not(.none), .s-spark-read, .s-badges-row .mk-signal),
body[data-gate="visitor"] .mk-card .mk-signal {
  visibility: hidden;
  user-select: none;
}
body[data-gate="visitor"] :is(.s-card.gate-near, .gate-all-near .s-card) :is(.s-spark:not(.none), .s-spark-read, .s-badges-row .mk-signal),
body[data-gate="visitor"] :is(.mk-card.gate-near, .gate-all-near .mk-card) .mk-signal,
body[data-gate="visitor"] .mk-table td .mk-signal,
body[data-gate="visitor"] .mk-mover-name {
  visibility: visible;
  filter: blur(var(--gate-blur));
  user-select: none;
}
/* Blurred things are the way in: the pointer says so. */
body[data-gate="visitor"] :is(.s-spark:not(.none), .s-spark-read, .mk-mover, [id="mkBrief"]) { cursor: pointer; }

/* The prompt a tap on anything blurred opens (gate.js signInNudge). */
.gate-nudge { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 9998;
  width: min(440px, calc(100vw - 32px)); box-sizing: border-box; display: flex; align-items: center; gap: 12px;
  padding: 12px 12px 12px 16px; border-radius: 14px; background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); box-shadow: 0 12px 32px rgba(0,0,0,.3); font-size: 13.5px; line-height: 1.45; }
.gate-nudge p { margin: 0; flex: 1 1 auto; color: var(--text-dim); }
.gate-nudge p b { color: var(--text); }
.gate-nudge .discord-btn { flex: none; }
.gate-nudge .gn-x { all: unset; cursor: pointer; flex: none; width: 36px; height: 36px; display: grid; place-items: center; border-radius: 999px; color: var(--text-faint); }
.gate-nudge .gn-x:hover { background: var(--surface-2); color: var(--text); }

/* The set scorecard names a best and a worst product. With stand-ins those
   would be real products ranked by made-up moves, readable even with the
   numbers blurred — so the whole row is blurred, in neutral colour. Pulse's
   signal chips keep their labels (a category is not a claim) but lose their
   colours, and their counts stay blurred as before. */
body[data-gate="visitor"] .mk-score { filter: blur(var(--gate-blur)); user-select: none; }
body[data-gate="visitor"] .mk-score * { color: var(--text-faint) !important; background-color: transparent; }
body[data-gate="visitor"] .mk-score :is(.mk-breadth, .mk-breadth *) { background: var(--border) !important; }
body[data-gate="visitor"] .mk-pulse-signals .mk-signal:not(.mk-pulse-more) { color: var(--text-dim) !important; border-color: var(--border) !important; background: transparent !important; }

/* The stand-in bitmap (gate.js, 2026-09-22). Already blurred. A second
   filter, or a text selection, would either wipe it out or hand the words
   back. Pulse chip labels stay real text: a category is not a move. */
body[data-gate="visitor"] .gate-veiled {
  filter: none !important;
  user-select: none !important;
  -webkit-user-select: none !important;
}
body[data-gate="visitor"] .gate-veil {
  display: inline-block;
  vertical-align: middle;
  max-width: 100%;
  height: auto;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}
/* Natural width. Stretching the bitmap to the row turned a sentence into a bar
   (2026-09-22). */
body[data-gate="visitor"] :is(#mkBrief, .mk-score, .mk-compare-sum, .mk-idx-game, .chart-sub).gate-veiled > .gate-veil {
  display: block;
  width: auto;
  max-width: 100%;
}
body[data-gate="visitor"] .chart text { user-select: none; -webkit-user-select: none; }
