@nooticr/mcp 1.26.20 → 1.26.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
18
18
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
19
19
  <title>Nooticr</title>
20
20
  <style>
21
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--shadow-sm:0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{overflow:hidden auto}[hidden]{display:none!important}a{color:inherit;text-decoration:none}body{color:var(--fg);background:0 0;max-width:100vw;padding:10px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;line-height:1.5;overflow-x:hidden}}@layer components{.load-bar{background:var(--tag);border-radius:3.40282e38px;height:3px;margin-bottom:14px;position:relative;overflow:hidden}.load-bar:after{content:"";background:var(--brand);border-radius:3.40282e38px;width:42%;animation:1.35s cubic-bezier(.45,0,.55,1) infinite sweep;position:absolute;top:0;bottom:0}@keyframes sweep{0%{left:-45%}50%{left:58%}to{left:-45%}}.load-head{align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);margin-bottom:11px;font-size:12.5px;font-weight:600;display:flex}.load-head .dot{background:var(--brand);border-radius:50%;flex-shrink:0;width:7px;height:7px;animation:1.2s ease-in-out infinite pulse}@keyframes pulse{0%,to{opacity:.35}50%{opacity:1}}.sk{border-radius:var(--radius-md);background:var(--tag);animation:1.5s ease-in-out infinite skpulse;position:relative;overflow:hidden}@keyframes skpulse{0%,to{opacity:1}50%{opacity:.55}}.sk-media{aspect-ratio:9/16;width:100%;max-height:var(--stage-max,420px);border-radius:0}.sk-media.h{aspect-ratio:16/9}.sk-line{height:11px;margin-top:8px}.sk-line.w40{width:40%}.sk-line.w60{width:60%}.sk-line.w80{width:80%}.sk-pill{border-radius:999px;width:76px;height:22px;margin:0 5px 5px 0;display:inline-block}.sk-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);flex-shrink:0;width:min(340px,86vw);min-width:min(340px,86vw);overflow:hidden}.sk-body{padding:13px 14px}.sk-row{align-items:center;gap:calc(var(--spacing) * 3);border-bottom:1px solid var(--border);padding:11px 0;display:flex}.sk-avatar{border-radius:10px;flex-shrink:0;width:44px;height:44px}.sk-strip{gap:calc(var(--spacing) * 3);padding:2px 0 8px;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.load-bar:after,.sk,.load-head .dot{animation:none}.load-bar:after{opacity:.5;width:100%;left:0}}.fade-in{animation:.4s ease-out fadeIn}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.stagger>*{opacity:0;animation:.4s ease-out forwards fadeIn}.stagger>:first-child{animation-delay:50ms}.stagger>:nth-child(2){animation-delay:.1s}.stagger>:nth-child(3){animation-delay:.15s}.stagger>:nth-child(4){animation-delay:.2s}.stagger>:nth-child(5){animation-delay:.25s}.stagger>:nth-child(6){animation-delay:.3s}.stagger>:nth-child(7){animation-delay:.35s}.stagger>:nth-child(8){animation-delay:.4s}.gallery-wrap{max-width:100%;position:relative;overflow:hidden}.gallery{gap:calc(var(--spacing) * 3);scroll-snap-type:x mandatory;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:none;width:100%;max-width:100%;padding:2px 0 8px;display:flex;overflow-x:auto}.gallery::-webkit-scrollbar{display:none}.gallery-dots{justify-content:center;gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:10px;display:flex}.gallery-dots .dot{background:var(--border);cursor:pointer;width:8px;height:8px;transition:var(--transition);border:none;border-radius:50%;padding:0}.gallery-dots .dot.active{background:var(--accent);border-radius:999px;width:20px}.gallery-nav{z-index:2;cursor:pointer;background:var(--card);border:1px solid var(--border);width:32px;height:32px;color:var(--fg);transition:var(--transition);opacity:0;border-radius:50%;justify-content:center;align-items:center;font-size:14px;display:flex;position:absolute;top:50%;transform:translateY(-50%)}.gallery-wrap:hover .gallery-nav{opacity:1}.gallery-nav:hover{background:var(--accent);color:var(--card)}.gallery-nav.prev{left:2px}.gallery-nav.next{right:2px}.card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(380px,100%);min-width:min(380px,100%);max-width:100%;transition:var(--transition);cursor:default;scroll-snap-align:center;flex-shrink:0;display:block}.card.card-media{--ar-w:9;--ar-h:16;width:min(100%, calc(var(--stage-max,520px) * var(--ar-w) / var(--ar-h)));background:0 0;border-color:#0000;min-width:0;margin:0 auto}.card.card-media.h{--ar-w:16;--ar-h:9}.card.card-media.sq{--ar-w:1;--ar-h:1}.card-media .card-foot{background:0 0;justify-content:flex-end;padding:8px 4px 0;display:flex}.card-media .btn-sm{margin-top:0}.card:hover{border-color:var(--muted)}.card-media:hover{box-shadow:var(--shadow-md);transform:translateY(-2px)}.card-wide{width:100%}.card img:not(.mp-slide){object-fit:cover;width:100%;height:260px;display:block}.card-wide img:not(.mp-slide){max-height:340px}.card video{object-fit:contain;background:#000;border:none;outline:none;width:100%;min-height:220px;max-height:520px;display:block}.card-wide video{max-height:520px}.card-body{padding:14px 16px}.badge{align-items:center;gap:var(--spacing);text-transform:capitalize;transition:var(--transition);border-radius:3.40282e38px;padding:3px 10px;font-size:11px;font-weight:700;display:inline-flex}.handle{color:var(--muted);margin-left:auto;font-size:12px}.tag{align-items:center;gap:var(--spacing);transition:var(--transition);cursor:default;border-radius:3.40282e38px;margin:2px;padding:4px 10px;font-size:12px;display:inline-flex}.tag.clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.tag.clickable:hover{background:var(--tag)}.tag.copied{background:var(--green)!important;color:#fff!important}.title{margin:6px 0;font-size:14px;font-weight:600;line-height:1.35}.card-wide .title{font-size:16px;font-weight:700}.caption{color:var(--muted);margin:6px 0 0;font-size:13px;line-height:1.5}.stats{align-items:center;gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:8px;display:flex}.stat-pill{align-items:center;gap:var(--spacing);color:var(--fg);background:var(--tag);transition:var(--transition);border-radius:3.40282e38px;padding:5px 12px;font-size:12px;display:inline-flex}.stat-pill:hover{background:var(--border)}.btn{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-lg);color:#fff;background:var(--brand);transition:var(--transition);border:none;margin-top:12px;padding:8px 18px;font-size:13px;font-weight:600;text-decoration-line:none;display:inline-flex}.btn:hover{background:var(--brand-2)}.btn:active{opacity:.9}.btn-sm{margin-top:8px;padding:5px 12px;font-size:11px}.section{border-bottom:1px solid var(--border);margin-bottom:6px;overflow:hidden}.section-header{cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--transition);justify-content:space-between;align-items:center;padding:10px 2px;display:flex}.section-header:hover .section-label{color:var(--fg)}.section-label{color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-size:11px;font-weight:700}.section-chevron{color:var(--muted);font-size:12px;transition:transform .2s}.section-header.open .section-chevron{transform:rotate(180deg)}.section-content{max-height:0;padding:0 2px;transition:max-height .28s,padding .28s;overflow:hidden}.section-content.open{max-height:2400px;padding:4px 2px 14px}.section-text{color:var(--fg);font-size:13px;line-height:1.6}.ai-actions{gap:calc(var(--spacing) * 2);flex-wrap:wrap;margin-top:14px;display:flex}.ai-btn{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border:1px solid var(--border);background:var(--card);color:var(--fg);font:inherit;transition:var(--transition);border-radius:3.40282e38px;padding:8px 13px;font-size:13px;font-weight:600;display:inline-flex}.ai-btn:hover:not(:disabled){border-color:var(--brand);background:var(--brand-soft)}.ai-btn:disabled{opacity:.55;cursor:not-allowed}.ai-btn .spark{color:var(--brand);flex-shrink:0;display:inline-flex}.ai-btn .price{white-space:nowrap;color:var(--brand);background:var(--brand-soft);border-radius:3.40282e38px;align-items:center;padding:2px 7px;font-size:11px;font-weight:700;display:inline-flex}.ai-btn.busy .spark{animation:1.1s linear infinite spin}.ai-note{color:var(--muted);margin-top:8px;font-size:11.5px}.an-title{align-items:center;gap:calc(var(--spacing) * 2);margin-bottom:14px;font-size:16px;font-weight:700;display:flex}.verdict{gap:calc(var(--spacing) * 2.5);grid-template-columns:repeat(1,minmax(0,1fr));margin-bottom:12px;display:grid}@media (min-width:420px){.verdict{grid-template-columns:1fr 1fr}}.meter{border-radius:var(--radius-md);background:var(--tag);padding:9px 11px}.meter-top{justify-content:space-between;align-items:baseline;gap:calc(var(--spacing) * 2);letter-spacing:.05em;text-transform:uppercase;color:var(--muted);font-size:11px;font-weight:700;display:flex}.meter-top b{letter-spacing:0;font-size:14px}.meter-track{background:var(--border);border-radius:3.40282e38px;height:5px;margin-top:6px;overflow:hidden}.meter-track>i{border-radius:3.40282e38px;height:100%;display:block}.chiprow{gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;display:flex}.mhead{z-index:3;background:var(--bg);border-bottom:1px solid var(--border);padding:2px 0 10px;position:sticky;top:0}.mhead-top{justify-content:space-between;align-items:flex-end;gap:calc(var(--spacing) * 2.5);flex-wrap:wrap;display:flex}.mention-term{color:var(--muted);margin-bottom:2px;font-size:13px}.mention-term b{color:var(--fg)}.mention-total{color:var(--fg);letter-spacing:-.01em;font-size:20px;font-weight:650}.mention-down{color:var(--muted);margin-top:8px;font-size:12px}.mention-window{color:var(--muted);border:1px solid var(--border);border-radius:3.40282e38px;margin-left:6px;padding:1px 8px;font-size:11px;display:inline-block}.mention-summary{color:var(--muted);max-width:60ch;margin-top:6px;font-size:13px;line-height:1.45}.msort{background:var(--tag);border-radius:3.40282e38px;flex:none;align-items:center;gap:2px;padding:3px;display:inline-flex}.msort-btn{color:var(--muted);cursor:pointer;transition:var(--transition);white-space:nowrap;background:0 0;border:0;border-radius:999px;padding:5px 14px;font-size:11.5px;font-weight:600}.msort-btn:hover{color:var(--fg)}.msort-btn.on{color:var(--card);background:var(--accent)}.mchips{gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:10px;display:flex}.mchip{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);border:1px solid var(--border);transition:var(--transition);white-space:nowrap;background:0 0;border-radius:3.40282e38px;padding:5px 12px;font-size:12px;font-weight:600;display:inline-flex}.mchip:hover{color:var(--fg);border-color:var(--muted)}.mchip b{color:var(--fg);background:var(--tag);font-variant-numeric:tabular-nums;border-radius:3.40282e38px;padding:1px 7px;font-weight:700}.mchip.on{color:var(--card);background:var(--accent);border-color:#0000}.mchip.on b{color:inherit;background:0 0;padding:1px 0}.mchip.on .mchip-mark{color:inherit}.mchip.quiet{opacity:.5;cursor:default}.mchip.quiet:hover{color:var(--muted);border-color:var(--border)}.mchip-mark{color:var(--brand);display:inline-flex}.mchip-mark svg{fill:currentColor;width:13px;height:13px}.mgroups{gap:calc(var(--spacing) * 2.5);flex-direction:column;margin-top:12px;display:flex}.mgroup{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);padding:12px 14px 6px}.mgroup-head{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;margin-bottom:6px;display:flex}.mgroup-plat{align-items:center;gap:calc(var(--spacing) * 1.5);color:var(--brand);font-size:11px;font-weight:600;display:inline-flex}.mgroup-plat svg{fill:currentColor;width:12px;height:12px}.mgroup-about{text-transform:uppercase;letter-spacing:.04em;color:var(--muted);border:1px solid var(--border);border-radius:3.40282e38px;padding:1px 7px;font-size:10.5px}.mgroup-reach{color:var(--muted);margin-left:auto;font-size:11px}.mgroup-title{color:var(--fg);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}.mgroup-rest{color:var(--muted);cursor:pointer;transition:var(--transition);background:0 0;border:0;align-self:flex-start;margin-top:2px;padding:2px 0;font-size:11.5px;font-weight:600}.mgroup-rest:hover{color:var(--fg)}.mgroup-actions{align-items:center;gap:calc(var(--spacing) * 3);margin-top:5px;display:flex}.mgroup-link{color:var(--muted);font-size:11.5px;text-decoration:none}.mgroup-link:hover{color:var(--fg);text-decoration:underline}.mgroup-all{color:var(--muted);cursor:pointer;transition:var(--transition);background:0 0;border:0;padding:0;font-size:11.5px;font-weight:600}.mgroup-all:hover{color:var(--fg)}.mgroup-none{color:var(--muted);margin-top:8px;font-size:12px}.mgroup-media{margin-top:10px}.mentions{flex-direction:column;margin-top:8px;display:flex}.mention{cursor:pointer;align-items:flex-start;gap:calc(var(--spacing) * 3);transition:var(--transition);padding:11px 2px;display:flex}.mention+.mention{border-top:1px solid var(--border)}.mention:hover{background:var(--card-hover)}.mention.picked{background:var(--tag)}.mention-pick{width:15px;height:15px;accent-color:var(--accent);cursor:pointer;flex:none;margin-top:11px}.mention-av{background:var(--tag);border:1px solid var(--border);border-radius:3.40282e38px;flex:none;width:36px;height:36px;position:relative;overflow:visible}.mention-av-init{color:var(--muted);border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:14px;font-weight:700;display:flex;position:absolute;inset:0}.mention-av-img{object-fit:cover;border-radius:3.40282e38px;width:100%;height:100%;position:absolute;inset:0}.mention-av-badge{background:var(--card);width:16px;height:16px;color:var(--brand);border:1px solid var(--border);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:-3px;right:-3px}.mention-av-badge svg{fill:currentColor;width:9.5px;height:9.5px}.mention-body{gap:calc(var(--spacing) * .5);flex-direction:column;flex:1;min-width:0;display:flex}.mention-head{align-items:baseline;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.mention-who{color:var(--fg);font-size:13px;font-weight:650}.mention-when{color:var(--muted);font-size:11.5px}.mention-when:before{content:"·";color:var(--border);margin-right:7px}.mention-hits{color:var(--fg);background:var(--tag);border:1px solid var(--border);border-radius:3.40282e38px;margin-left:auto;padding:1px 7px;font-size:10.5px;font-weight:700}.mention-text{color:var(--fg);word-break:break-word;margin-top:4px;font-size:13.5px;line-height:1.5}.mention-text mark{color:inherit;background:#f43f5e26;border-radius:4px;padding:1px 3px;font-weight:600}.mention-note{border-left:2px solid var(--brand,#f43f5e);background:var(--tag);color:var(--fg);white-space:pre-wrap;word-break:break-word;border-radius:0 6px 6px 0;margin-top:6px;padding:6px 9px;font-size:12.5px;line-height:1.5;display:block}.mention-meta{align-items:center;gap:calc(var(--spacing) * 3.5);margin-top:6px;display:flex}.mention-stat{align-items:center;gap:var(--spacing);color:var(--muted);font-size:11px;display:inline-flex}.mention-stat svg{fill:none;stroke:currentColor;stroke-width:1.8px;stroke-linecap:round;stroke-linejoin:round;width:12px;height:12px}.chip{text-transform:capitalize;letter-spacing:.01em;border:1px solid var(--border);color:var(--muted);border-radius:3.40282e38px;padding:1px 7px;font-size:10px;font-weight:600}.chip-positive{color:#15803d;background:#15803d14;border-color:#15803d55}.chip-negative{color:#b91c1c;background:#b91c1c14;border-color:#b91c1c55}.chip-mixed{color:#a16207;background:#a1620714;border-color:#a1620755}.chip-neutral{color:var(--muted)}.chip-cat{background:var(--tag)}.mention-more{cursor:pointer;border-radius:var(--radius-xl);width:100%;color:var(--fg);background:var(--tag);border:1px solid var(--border);transition:var(--transition);margin-top:12px;padding:10px;font-size:13px;font-weight:600;display:block}.mention-more:hover:not(:disabled){border-color:var(--muted)}.mention-more:disabled{opacity:.6;cursor:default}.sm-thumb{border-radius:var(--radius-lg);object-fit:cover;background:var(--tag);border:1px solid var(--border);flex:none;width:56px;height:56px}.tag.copyable{cursor:pointer;border:1px solid var(--border);font:inherit;align-items:center;gap:5px;font-size:12px;display:inline-flex}.tag.copyable:hover{border-color:var(--accent)}.tag.warn{background:var(--red)}@supports (color:color-mix(in lab, red, red)){.tag.warn{background:color-mix(in srgb, var(--red) 12%, transparent)}}.tag.warn{color:var(--red);border:1px solid var(--red)}@supports (color:color-mix(in lab, red, red)){.tag.warn{border:1px solid color-mix(in srgb, var(--red) 35%, transparent)}}.lede-box{background:var(--tag);border-radius:var(--radius-sm);margin-bottom:12px;padding:11px 13px;font-size:13.5px;line-height:1.6}.steps-list{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.step-row{align-items:flex-start;gap:calc(var(--spacing) * 2.5);grid-template-columns:78px 1fr;display:grid}.step-k{letter-spacing:.05em;text-transform:uppercase;color:var(--muted);padding-top:2px;font-size:10.5px;font-weight:700}.step-v{font-size:13px;line-height:1.5}.an-list{margin:0;padding-left:18px;font-size:13px;line-height:1.55}.an-list li{margin:4px 0}.clamp6{-webkit-line-clamp:6;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hook-bar-track{align-items:center;gap:calc(var(--spacing) * 2.5);margin-top:6px;display:flex}.hook-bar{background:var(--tag);border-radius:3.40282e38px;flex:1;height:10px;overflow:hidden}.hook-bar-fill{border-radius:3.40282e38px;height:100%;transition:width .8s cubic-bezier(.4,0,.2,1)}.hook-score{text-align:right;min-width:40px;font-size:15px;font-weight:800}.quote-box{border-radius:var(--radius-md);background:var(--tag);border-left:3px solid var(--blue);margin:6px 0;padding:10px 14px;font-size:13px;font-weight:600;line-height:1.45;position:relative}.quote-box .copy-btn{cursor:pointer;border-radius:var(--radius-md);background:var(--card);border:1px solid var(--border);color:var(--muted);transition:var(--transition);opacity:0;padding:3px 8px;font-size:10px;position:absolute;top:8px;right:8px}.quote-box:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:var(--card)}.theme-chip{align-items:center;gap:calc(var(--spacing) * 1.5);background:var(--tag);border:1px solid var(--border);color:var(--fg);border-radius:3.40282e38px;margin:0 4px 5px 0;padding:4px 10px;font-size:12px;display:inline-flex}.theme-chip b{color:var(--brand,var(--blue));font-variant-numeric:tabular-nums}.sec-label{letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:7px;font-size:11px;font-weight:700}.cbadge{white-space:nowrap;border-radius:4px;padding:1px 5px;font-size:10px;font-weight:700}.cbadge.pin{color:#b45309;background:#fef3c7}.cbadge.liked{color:#be185d;background:#fce7f3}.cmeta{align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);flex-wrap:wrap;margin-top:5px;font-size:11px;display:flex}.ctext{color:var(--fg);font-size:13px;line-height:1.45}.ctext.clamp{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.cmore{color:var(--blue);cursor:pointer;background:0 0;border:none;padding:3px 0 0;font-family:inherit;font-size:11.5px;font-weight:600}.comment-row{justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-bottom:1px solid var(--border);transition:var(--transition);padding:10px 0;display:flex}.comment-row:last-child{border-bottom:none}.comment-row:hover{background:var(--tag)}.creator-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(240px,80vw);min-width:min(240px,80vw);transition:var(--transition);scroll-snap-align:start;flex-shrink:0;padding:16px;display:block}.creator-card:hover{border-color:var(--muted)}.avatar{object-fit:cover;border:2px solid var(--border);border-radius:3.40282e38px;width:52px;height:52px}.avatar-placeholder{border-radius:3.40282e38px;justify-content:center;align-items:center;width:52px;height:52px;font-size:22px;display:flex}.verified{color:#fff;border-radius:3.40282e38px;justify-content:center;align-items:center;width:18px;height:18px;margin-left:4px;font-size:10px;display:inline-flex}.sound-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(300px,88vw);min-width:min(300px,88vw);transition:var(--transition);scroll-snap-align:start;flex-shrink:0;padding:14px;display:block}.sound-card:hover{border-color:var(--muted)}.sound-cover{border-radius:var(--radius-md);object-fit:cover;width:68px;height:68px}.mp{--ar-w:9;--ar-h:16;width:min(100%, calc(var(--stage-max,520px) * var(--ar-w) / var(--ar-h)));background:#000;border:2px solid #0000;border-radius:18px;margin-inline:auto;position:relative;overflow:hidden}.mp.playing{border-color:var(--brand)}.card-media>.mp{width:100%}.mp.h{--ar-w:16;--ar-h:9}.mp.sq{--ar-w:1;--ar-h:1}.mp-stage{width:100%;aspect-ratio:var(--ar-w) / var(--ar-h);max-height:var(--stage-max,520px);background:#000;position:relative;overflow:hidden}.mp .mp-stage>video{object-fit:contain;background:#000;width:100%;height:100%;max-height:none;display:block}.mp .mp-slide{object-fit:contain;opacity:0;background:#000;width:100%;height:100%;max-height:none;transition:opacity .45s;position:absolute;inset:0;transform:none}.mp-slide.active{opacity:1}.mp-ov{pointer-events:none;z-index:2;position:absolute;inset:0}.mp-ov:after{content:"";pointer-events:none;background:linear-gradient(#0000,#00000047 38%,#000000b8);height:46%;position:absolute;bottom:0;left:0;right:0}.mp-ov:before{content:"";pointer-events:none;background:linear-gradient(#00000073,#0000);height:16%;position:absolute;top:0;left:0;right:0}.mp-badge,.mp-chip,.mp-rail,.mp-meta,.mp-tap,.mp-nav{z-index:1}.mp-ov>*{pointer-events:auto}.mp-badge{text-transform:capitalize;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;background:#0000008c;border-radius:3.40282e38px;align-items:center;gap:5px;padding:4px 9px;font-size:11px;font-weight:700;display:inline-flex;position:absolute;top:10px;left:10px}.mp-pick{z-index:3;cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#0000;width:26px;height:26px;transition:var(--transition);background:#0000006b;border:2px solid #ffffffd9;border-radius:3.40282e38px;justify-content:center;align-items:center;padding:0;display:flex;position:absolute;top:9px;right:9px}.mp-pick:hover{background:#000000a8;transform:scale(1.08)}.mp-open{z-index:3;cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;width:26px;height:26px;transition:var(--transition);background:#0000006b;border:2px solid #ffffffd9;border-radius:3.40282e38px;justify-content:center;align-items:center;padding:0;text-decoration-line:none;display:flex;position:absolute;top:9px;right:9px}.mp-open:hover{background:#000000a8;transform:scale(1.08)}.mp-open.with-pick{right:43px}.mp-pick[aria-pressed=true]{background:var(--brand,#ff4d23);color:#fff;border-color:#fff}.mp.picked{outline:2px solid var(--brand,#ff4d23);outline-offset:-2px}.mp-pick~.mp-chip,.mp[data-mp=slides] .mp-chip{top:42px}.pickbar{z-index:20;align-items:center;gap:calc(var(--spacing) * 2.5);background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow-lg);border-radius:3.40282e38px;margin:12px 0 0;padding:10px 12px;animation:.2s ease-out fadeIn;display:flex;position:sticky;bottom:0}.pickbar .n{white-space:nowrap;font-size:13px;font-weight:700}.pickbar .sp{flex:1}.pickbar button{cursor:pointer;white-space:nowrap;border:1px solid var(--border);background:var(--card);color:var(--fg);font:inherit;transition:var(--transition);border-radius:3.40282e38px;padding:6px 13px;font-size:12.5px;font-weight:600}.pickbar button.primary{background:var(--brand,#ff4d23);color:#fff;border-color:#0000}.pickbar button:hover{filter:brightness(1.08)}.pickbar button:disabled{opacity:.5;cursor:not-allowed}.pickbar .hint{color:var(--muted);font-size:11.5px}.mp-chip{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;font-variant-numeric:tabular-nums;background:#0000008c;border-radius:3.40282e38px;padding:3px 9px;font-size:11px;font-weight:700;position:absolute;top:10px;right:10px}.mp-rail{align-items:center;gap:calc(var(--spacing) * 3.5);flex-direction:column;display:flex;position:absolute;bottom:76px;right:8px}.mp-act{cursor:default;background:0 0;border:none;flex-direction:column;align-items:center;gap:3px;padding:0;display:flex}.mp-act .ic{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;width:38px;height:38px;transition:var(--transition);background:#0006;border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.mp-act:hover .ic{background:#0000009e;transform:scale(1.06)}.mp-act .n{color:#fff;text-shadow:0 1px 3px #000c;font-variant-numeric:tabular-nums;font-size:11px;font-weight:700}.mp-meta{color:#fff;text-shadow:0 1px 3px #000000d9;position:absolute;bottom:60px;left:12px;right:64px}.mp-handle{margin-bottom:3px;font-size:13px;font-weight:800}.mp-cap{-webkit-line-clamp:2;opacity:.95;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}.mp-song{white-space:nowrap;text-overflow:ellipsis;align-items:center;gap:5px;margin-top:6px;font-size:11px;font-weight:600;display:flex;overflow:hidden}.mp-song .note{flex:none;display:inline-flex}.mp.playing .mp-song .note{animation:3s linear infinite spin}.mp-song-t{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}@keyframes spin{to{transform:rotate(360deg)}}.mp-tap{cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.mp-tap span{-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;width:64px;height:64px;transition:var(--transition);opacity:1;background:#00000073;border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:26px;display:flex}.mp.playing .mp-tap span{opacity:0}.mp.playing:hover .mp-tap span{opacity:.65}.mp-nav{cursor:pointer;color:#fff;opacity:0;width:34px;height:34px;transition:var(--transition);background:#0000008c;border:none;border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:18px;display:flex;position:absolute;top:50%;transform:translateY(-50%)}.mp:hover .mp-nav,.mp:focus-within .mp-nav{opacity:1}.mp-nav.prev{left:8px}.mp-nav.next{right:8px}.mp-nav:hover{background:#000c}.mp-ctl{z-index:3;background:linear-gradient(#0000,#000000d9);align-items:center;gap:9px;padding:9px 11px;display:flex;position:absolute;bottom:0;left:0;right:0}.mp-btn{cursor:pointer;color:#111;width:30px;height:30px;transition:var(--transition);background:#fffffff2;border:none;border-radius:3.40282e38px;flex:none;justify-content:center;align-items:center;font-size:12px;display:flex}.mp-btn:hover{transform:scale(1.09)}.mp-time{color:#fff;font-variant-numeric:tabular-nums;text-shadow:0 1px 2px #000c;flex-shrink:0;font-size:10px;font-weight:700}.mp-seek{cursor:pointer;flex:1;align-items:center;min-width:0;height:14px;display:flex;position:relative}.mp-segs{gap:calc(var(--spacing) * .5);border-radius:3.40282e38px;width:100%;height:3px;transition:height .15s;display:flex;overflow:hidden}.mp-seek:hover .mp-segs{height:5px}.mp-seg{background:#ffffff52;border-radius:3.40282e38px;flex:1;height:100%;overflow:hidden}.mp-seg>i{background:#fff;border-radius:3.40282e38px;width:0;height:100%;display:block}.mp-seg.done>i{width:100%}.mp-icon{color:#fff;cursor:pointer;opacity:.9;background:0 0;border:none;flex-shrink:0;padding:0;font-size:14px;line-height:1}.mp-icon:hover{opacity:1}.mp-spin{pointer-events:none;z-index:2;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.mp-spin>i{border:3px solid #ffffff40;border-top-color:#fff;border-radius:3.40282e38px;width:34px;height:34px;animation:.8s linear infinite spin}.mp-err{pointer-events:none;z-index:4;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);text-align:center;color:#fff;background:#000000d1;flex-direction:column;padding:16px;font-size:12px;font-weight:600;display:flex;position:absolute;inset:0}.mp:focus-visible{outline-offset:-2px;outline:2px solid #fff}.mp-btn:focus-visible,.mp-icon:focus-visible,.mp-nav:focus-visible,.mp-tap:focus-visible,.au-play:focus-visible,.gallery-nav:focus-visible,.dot:focus-visible{outline-offset:2px;outline:2px solid #fff}@media (prefers-reduced-motion:reduce){.mp-song .note{animation:none}.mp-slide{transition:none}.mp-spin>i{animation-duration:2s}*{scroll-behavior:auto!important}}.au{align-items:center;gap:calc(var(--spacing) * 2.5);margin-top:10px;display:flex}.au-play{cursor:pointer;color:#fff;background:var(--brand);width:34px;height:34px;transition:var(--transition);border:none;border-radius:3.40282e38px;flex:none;justify-content:center;align-items:center;display:flex}.au-body{flex:1;min-width:0}.au-seek{cursor:pointer;background:var(--fg);border-radius:3.40282e38px;width:100%;height:5px;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.au-seek{background:color-mix(in srgb, var(--fg) 22%, transparent)}}.au-seek>i{background:var(--accent);border-radius:3.40282e38px;width:0;height:100%;display:block}.au-time{color:var(--muted);font-variant-numeric:tabular-nums;justify-content:space-between;margin-top:3px;font-size:10px;display:flex}.au-missing{align-items:center;gap:calc(var(--spacing) * 1.5);color:var(--muted);margin-top:8px;font-size:11px;display:flex}.credits-card{border-radius:var(--radius-2xl);border:1px solid var(--border);background:var(--card);max-width:380px;margin:8px 0;padding:24px}.pack{cursor:pointer;border-radius:var(--radius-xl);text-align:center;border:1px solid var(--border);transition:var(--transition);padding:16px}.pack:hover{border-color:var(--muted)}.pack-featured{border-radius:var(--radius-xl);text-align:center;border:2px solid var(--accent);padding:16px}.tier-badge{color:#1e40af;background:#dbeafe;border-radius:3.40282e38px;align-items:center;padding:4px 12px;font-size:12px;font-weight:700;display:inline-flex}.free-tag{align-items:center;gap:var(--spacing);color:#065f46;transition:var(--transition);background:#d1fae5;border-radius:3.40282e38px;margin:2px;padding:4px 10px;font-size:11px;font-weight:600;display:inline-flex}.json-block{border-radius:var(--radius-md);background:var(--tag);border:1px solid var(--border);white-space:pre-wrap;word-break:break-all;max-height:320px;margin-top:10px;padding:14px;font-family:SF Mono,Monaco,monospace;font-size:12px;overflow-y:auto}.empty-state{text-align:center;color:var(--muted);padding:48px 24px}.empty-state .icon{margin-bottom:12px;font-size:48px}.empty-state .text{font-size:14px}}@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-3{left:calc(var(--spacing) * 3)}.isolate{isolation:isolate}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.w-full{width:100%}.max-w-sm{max-width:var(--container-sm)}.shrink{flex-shrink:1}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-line{border-color:var(--border)}.bg-black{background-color:var(--color-black)}.bg-soft{background-color:var(--tag)}.bg-surface{background-color:var(--card)}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:var(--spacing)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-wrap{text-wrap:wrap}.text-ink{color:var(--fg)}.text-muted{color:var(--muted)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-all{-webkit-user-select:all;user-select:all}}:root{color-scheme:light dark;--bg:#fafbfc;--fg:#111827;--muted:#5f6773;--border:#e5e7eb;--card:#fff;--card-hover:#f3f4f6;--tag:#f3f4f6;--accent:#14151a;--brand:#ff4d23;--brand-2:#ff8a3d;--brand-soft:#ff4d2321;--radius:10px;--radius-sm:6px;--transition:all .25s cubic-bezier(.4, 0, .2, 1);--shadow-md:0 4px 12px #00000014;--shadow-lg:0 8px 24px #0000001f;--green:#047857;--amber:#b45309;--red:#dc2626;--blue:#2563eb}@media (prefers-color-scheme:dark){:root{--bg:#0f172a;--fg:#f1f5f9;--muted:#a8b8cc;--border:#334155;--green:#34d399;--amber:#fbbf24;--red:#f87171;--blue:#60a5fa;--card:#1e293b;--card-hover:#273548;--tag:#1e293b;--accent:#e2e8f0;--brand:#ff5c33;--brand-2:#ff9351;--brand-soft:#ff5c3329;--shadow-md:0 4px 12px #0000004d;--shadow-lg:0 8px 24px #0006}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
21
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--shadow-sm:0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}html{overflow:hidden auto}[hidden]{display:none!important}a{color:inherit;text-decoration:none}body{color:var(--fg);background:0 0;max-width:100vw;padding:10px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;line-height:1.5;overflow-x:hidden}}@layer components{.load-bar{background:var(--tag);border-radius:3.40282e38px;height:3px;margin-bottom:14px;position:relative;overflow:hidden}.load-bar:after{content:"";background:var(--brand);border-radius:3.40282e38px;width:42%;animation:1.35s cubic-bezier(.45,0,.55,1) infinite sweep;position:absolute;top:0;bottom:0}@keyframes sweep{0%{left:-45%}50%{left:58%}to{left:-45%}}.load-head{align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);margin-bottom:11px;font-size:12.5px;font-weight:600;display:flex}.load-price{color:var(--brand);white-space:nowrap;margin-left:auto;font-weight:700}.mk{color:var(--fg);flex-shrink:0;display:block}.mk .iris{animation:3.6s cubic-bezier(.5,0,.5,1) infinite mkScan}.mk .lid{transform-box:fill-box;transform-origin:50%;animation:5.4s ease-in-out infinite mkBlink}@keyframes mkScan{0%{transform:translate(0)}18%{transform:translate(-15px,7px)}34%{transform:translate(11px,-5px)}52%{transform:translate(-8px,12px)}70%{transform:translate(14px,3px)}86%{transform:translate(-4px,-6px)}to{transform:translate(0)}}@keyframes mkBlink{0%,88%,to{transform:scaleY(1)}93%{transform:scaleY(.08)}}.load-known{gap:calc(var(--spacing) * 2);flex-wrap:wrap;margin-bottom:11px;display:flex}.load-fact{align-items:center;gap:calc(var(--spacing) * 2);border:1px solid var(--border);background:var(--card);color:var(--fg);border-radius:3.40282e38px;padding:4px 11px;font-size:12px;font-weight:600;display:inline-flex}.load-fact-mark{color:var(--brand);display:inline-flex}.load-fact-mark svg{fill:currentColor;width:13px;height:13px}.led .card-body{padding:4px 14px 12px}.led-row{align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom:1px solid var(--border);padding:10px 0;display:flex}.led-row:last-of-type{border-bottom:none}.led-body{flex:1;min-width:0}.led-label{color:var(--fg);font-size:12.5px;font-weight:600}.led-detail{color:var(--muted);margin-top:1px;font-size:11px}.led-cost{background:var(--tag);color:var(--fg);font-variant-numeric:tabular-nums;border-radius:3.40282e38px;flex:none;align-items:center;padding:2px 9px;font-size:11.5px;font-weight:700;display:inline-flex}.led-mark{width:16px;color:var(--brand);flex:none;justify-content:center;align-items:center;margin-top:1px;display:inline-flex}.led-mark svg{fill:currentColor;width:13px;height:13px}.led-dot{border:1px solid var(--border);border-radius:50%;flex:none;width:13px;height:13px;margin-top:2px}.led-foot{color:var(--muted);margin-top:10px;font-size:11.5px;line-height:1.5}.load-centre{text-align:center;flex-direction:column;align-items:center;padding:30px 0 22px;display:flex}.load-centre .load-what{color:var(--fg);margin-top:20px;font-size:14px;font-weight:700}.load-centre .load-sub{color:var(--muted);margin-top:5px;font-size:12.5px}.load-centre .load-bar{width:210px;margin:16px 0 0}.sk{border-radius:var(--radius-md);background:var(--tag);position:relative;overflow:hidden}.sk-media{aspect-ratio:9/16;width:100%;max-height:var(--stage-max,420px);border-radius:0}.sk-media.h{aspect-ratio:16/9}.sk-line{height:11px;margin-top:8px}.sk-line.w40{width:40%}.sk-line.w60{width:60%}.sk-line.w80{width:80%}.sk-pill{border-radius:999px;width:76px;height:22px;margin:0 5px 5px 0;display:inline-block}.sk-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);flex-shrink:0;width:min(340px,86vw);min-width:min(340px,86vw);overflow:hidden}.sk-body{padding:13px 14px}.sk-row{align-items:center;gap:calc(var(--spacing) * 3);border-bottom:1px solid var(--border);padding:11px 0;display:flex}.sk-avatar{border-radius:10px;flex-shrink:0;width:44px;height:44px}.sk-strip{gap:calc(var(--spacing) * 3);padding:2px 0 8px;display:flex;overflow:hidden}@media (prefers-reduced-motion:reduce){.load-bar:after,.mk .iris,.mk .lid{animation:none}.load-bar:after{opacity:.5;width:100%;left:0}}.fade-in{animation:.4s ease-out fadeIn}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.stagger>*{opacity:0;animation:.4s ease-out forwards fadeIn}.stagger>:first-child{animation-delay:50ms}.stagger>:nth-child(2){animation-delay:.1s}.stagger>:nth-child(3){animation-delay:.15s}.stagger>:nth-child(4){animation-delay:.2s}.stagger>:nth-child(5){animation-delay:.25s}.stagger>:nth-child(6){animation-delay:.3s}.stagger>:nth-child(7){animation-delay:.35s}.stagger>:nth-child(8){animation-delay:.4s}.gallery-wrap{max-width:100%;position:relative;overflow:hidden}.gallery{gap:calc(var(--spacing) * 3);scroll-snap-type:x mandatory;scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:none;width:100%;max-width:100%;padding:2px 0 8px;display:flex;overflow-x:auto}.gallery::-webkit-scrollbar{display:none}.gallery-dots{justify-content:center;gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:10px;display:flex}.gallery-dots .dot{background:var(--border);cursor:pointer;width:8px;height:8px;transition:var(--transition);border:none;border-radius:50%;padding:0}.gallery-dots .dot.active{background:var(--accent);border-radius:999px;width:20px}.gallery-nav{z-index:2;cursor:pointer;background:var(--card);border:1px solid var(--border);width:32px;height:32px;color:var(--fg);transition:var(--transition);opacity:0;border-radius:50%;justify-content:center;align-items:center;font-size:14px;display:flex;position:absolute;top:50%;transform:translateY(-50%)}.gallery-wrap:hover .gallery-nav{opacity:1}.gallery-nav:hover{background:var(--accent);color:var(--card)}.gallery-nav.prev{left:2px}.gallery-nav.next{right:2px}.card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(380px,100%);min-width:min(380px,100%);max-width:100%;transition:var(--transition);cursor:default;scroll-snap-align:center;flex-shrink:0;display:block}.card.card-media{--ar-w:9;--ar-h:16;width:min(100%, calc(var(--stage-max,520px) * var(--ar-w) / var(--ar-h)));background:0 0;border-color:#0000;min-width:0;margin:0 auto}.card.card-media.h{--ar-w:16;--ar-h:9}.card.card-media.sq{--ar-w:1;--ar-h:1}.card-media .card-foot{background:0 0;justify-content:flex-end;padding:8px 4px 0;display:flex}.card-media .btn-sm{margin-top:0}.card:hover{border-color:var(--muted)}.card-media:hover{box-shadow:var(--shadow-md);transform:translateY(-2px)}.card-wide{width:100%}.card img:not(.mp-slide){object-fit:cover;width:100%;height:260px;display:block}.card-wide img:not(.mp-slide){max-height:340px}.card video{object-fit:contain;background:#000;border:none;outline:none;width:100%;min-height:220px;max-height:520px;display:block}.card-wide video{max-height:520px}.card-body{padding:14px 16px}.badge{align-items:center;gap:var(--spacing);text-transform:capitalize;transition:var(--transition);border-radius:3.40282e38px;padding:3px 10px;font-size:11px;font-weight:700;display:inline-flex}.handle{color:var(--muted);margin-left:auto;font-size:12px}.tag{align-items:center;gap:var(--spacing);transition:var(--transition);cursor:default;border-radius:3.40282e38px;margin:2px;padding:4px 10px;font-size:12px;display:inline-flex}.tag.clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.tag.clickable:hover{background:var(--tag)}.tag.copied{background:var(--green)!important;color:#fff!important}.title{margin:6px 0;font-size:14px;font-weight:600;line-height:1.35}.card-wide .title{font-size:16px;font-weight:700}.caption{color:var(--muted);margin:6px 0 0;font-size:13px;line-height:1.5}.stats{align-items:center;gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:8px;display:flex}.stat-pill{align-items:center;gap:var(--spacing);color:var(--fg);background:var(--tag);transition:var(--transition);border-radius:3.40282e38px;padding:5px 12px;font-size:12px;display:inline-flex}.stat-pill:hover{background:var(--border)}.btn{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-lg);color:#fff;background:var(--brand);transition:var(--transition);border:none;margin-top:12px;padding:8px 18px;font-size:13px;font-weight:600;text-decoration-line:none;display:inline-flex}.btn:hover{background:var(--brand-2)}.btn:active{opacity:.9}.btn-sm{margin-top:8px;padding:5px 12px;font-size:11px}.section{border-bottom:1px solid var(--border);margin-bottom:6px;overflow:hidden}.section-header{cursor:pointer;-webkit-user-select:none;user-select:none;transition:var(--transition);justify-content:space-between;align-items:center;padding:10px 2px;display:flex}.section-header:hover .section-label{color:var(--fg)}.section-label{color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-size:11px;font-weight:700}.section-chevron{color:var(--muted);font-size:12px;transition:transform .2s}.section-header.open .section-chevron{transform:rotate(180deg)}.section-content{max-height:0;padding:0 2px;transition:max-height .28s,padding .28s;overflow:hidden}.section-content.open{max-height:2400px;padding:4px 2px 14px}.section-text{color:var(--fg);font-size:13px;line-height:1.6}.ai-actions{gap:calc(var(--spacing) * 2);flex-wrap:wrap;margin-top:14px;display:flex}.ai-btn{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border:1px solid var(--border);background:var(--card);color:var(--fg);font:inherit;transition:var(--transition);border-radius:3.40282e38px;padding:8px 13px;font-size:13px;font-weight:600;display:inline-flex}.ai-btn:hover:not(:disabled){border-color:var(--brand);background:var(--brand-soft)}.ai-btn:disabled{opacity:.55;cursor:not-allowed}.ai-btn .spark{color:var(--brand);flex-shrink:0;display:inline-flex}.ai-btn .price{white-space:nowrap;color:var(--brand);background:var(--brand-soft);border-radius:3.40282e38px;align-items:center;padding:2px 7px;font-size:11px;font-weight:700;display:inline-flex}.ai-btn.busy .spark{animation:1.1s linear infinite spin}.ai-note{color:var(--muted);margin-top:8px;font-size:11.5px}.an-title{align-items:center;gap:calc(var(--spacing) * 2);margin-bottom:14px;font-size:16px;font-weight:700;display:flex}.verdict{gap:calc(var(--spacing) * 2.5);grid-template-columns:repeat(1,minmax(0,1fr));margin-bottom:12px;display:grid}@media (min-width:420px){.verdict{grid-template-columns:1fr 1fr}}.meter{border-radius:var(--radius-md);background:var(--tag);padding:9px 11px}.meter-top{justify-content:space-between;align-items:baseline;gap:calc(var(--spacing) * 2);letter-spacing:.05em;text-transform:uppercase;color:var(--muted);font-size:11px;font-weight:700;display:flex}.meter-top b{letter-spacing:0;font-size:14px}.meter-track{background:var(--border);border-radius:3.40282e38px;height:5px;margin-top:6px;overflow:hidden}.meter-track>i{border-radius:3.40282e38px;height:100%;display:block}.chiprow{gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;display:flex}.mhead{z-index:3;background:var(--bg);border-bottom:1px solid var(--border);padding:2px 0 10px;position:sticky;top:0}.mhead-top{justify-content:space-between;align-items:flex-end;gap:calc(var(--spacing) * 2.5);flex-wrap:wrap;display:flex}.mention-term{color:var(--muted);margin-bottom:2px;font-size:13px}.mention-term b{color:var(--fg)}.mention-total{color:var(--fg);letter-spacing:-.01em;font-size:20px;font-weight:650}.mention-down{color:var(--muted);margin-top:8px;font-size:12px}.mention-window{color:var(--muted);border:1px solid var(--border);border-radius:3.40282e38px;margin-left:6px;padding:1px 8px;font-size:11px;display:inline-block}.mention-summary{color:var(--muted);max-width:60ch;margin-top:6px;font-size:13px;line-height:1.45}.msort{background:var(--tag);border-radius:3.40282e38px;flex:none;align-items:center;gap:2px;padding:3px;display:inline-flex}.msort-btn{color:var(--muted);cursor:pointer;transition:var(--transition);white-space:nowrap;background:0 0;border:0;border-radius:999px;padding:5px 14px;font-size:11.5px;font-weight:600}.msort-btn:hover{color:var(--fg)}.msort-btn.on{color:var(--card);background:var(--accent)}.mchips{gap:calc(var(--spacing) * 1.5);flex-wrap:wrap;margin-top:10px;display:flex}.mchip{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);border:1px solid var(--border);transition:var(--transition);white-space:nowrap;background:0 0;border-radius:3.40282e38px;padding:5px 12px;font-size:12px;font-weight:600;display:inline-flex}.mchip:hover{color:var(--fg);border-color:var(--muted)}.mchip b{color:var(--fg);background:var(--tag);font-variant-numeric:tabular-nums;border-radius:3.40282e38px;padding:1px 7px;font-weight:700}.mchip.on{color:var(--card);background:var(--accent);border-color:#0000}.mchip.on b{color:inherit;background:0 0;padding:1px 0}.mchip.on .mchip-mark{color:inherit}.mchip.quiet{opacity:.5;cursor:default}.mchip.quiet:hover{color:var(--muted);border-color:var(--border)}.mchip-mark{color:var(--brand);display:inline-flex}.mchip-mark svg{fill:currentColor;width:13px;height:13px}.mgroups{gap:calc(var(--spacing) * 2.5);flex-direction:column;margin-top:12px;display:flex}.mgroup{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);padding:12px 14px 6px}.mgroup-head{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;margin-bottom:6px;display:flex}.mgroup-plat{align-items:center;gap:calc(var(--spacing) * 1.5);color:var(--brand);font-size:11px;font-weight:600;display:inline-flex}.mgroup-plat svg{fill:currentColor;width:12px;height:12px}.mgroup-about{text-transform:uppercase;letter-spacing:.04em;color:var(--muted);border:1px solid var(--border);border-radius:3.40282e38px;padding:1px 7px;font-size:10.5px}.mgroup-reach{color:var(--muted);margin-left:auto;font-size:11px}.mgroup-title{color:var(--fg);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;font-weight:600;line-height:1.35;display:-webkit-box;overflow:hidden}.mgroup-rest{color:var(--muted);cursor:pointer;transition:var(--transition);background:0 0;border:0;align-self:flex-start;margin-top:2px;padding:2px 0;font-size:11.5px;font-weight:600}.mgroup-rest:hover{color:var(--fg)}.mgroup-actions{align-items:center;gap:calc(var(--spacing) * 3);margin-top:5px;display:flex}.mgroup-link{color:var(--muted);font-size:11.5px;text-decoration:none}.mgroup-link:hover{color:var(--fg);text-decoration:underline}.mgroup-all{color:var(--muted);cursor:pointer;transition:var(--transition);background:0 0;border:0;padding:0;font-size:11.5px;font-weight:600}.mgroup-all:hover{color:var(--fg)}.mgroup-none{color:var(--muted);margin-top:8px;font-size:12px}.mgroup-stage{--stage-max:300px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;width:calc(100% + 28px);margin:-12px -14px 10px;overflow:hidden}.mgroup-stage .mp{border:0;border-radius:0;width:100%}.mgroup-media{background:var(--muted);border-radius:var(--radius-xl) var(--radius-xl) 0 0;cursor:default;border:0;width:calc(100% + 28px);height:132px;margin:-12px -14px 10px;padding:0;display:block;position:relative;overflow:hidden}.mgroup-media.is-playable{cursor:pointer}.mgroup-media-img{object-fit:cover;width:100%;height:100%;display:block}.mgroup-media-blank{opacity:.35;justify-content:center;align-items:center;width:100%;height:100%;display:flex}.mgroup-media-play{color:#fff;background:#00000094;border-radius:999px;justify-content:center;align-items:center;width:40px;height:40px;transition:transform .16s cubic-bezier(.16,1,.3,1);display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.mgroup-media.is-playable:hover .mgroup-media-play{transform:translate(-50%,-50%)scale(1.08)}.mgroup-stage{border-radius:var(--radius-xl) var(--radius-xl) 0 0;--stage-max:300px;width:calc(100% + 28px);margin:-12px -14px 10px;overflow:hidden}.mentions{flex-direction:column;margin-top:8px;display:flex}.mention{cursor:pointer;align-items:flex-start;gap:calc(var(--spacing) * 3);transition:var(--transition);padding:11px 2px;display:flex}.mention+.mention{border-top:1px solid var(--border)}.mention:hover{background:var(--card-hover)}.mention.picked{background:var(--tag)}.mention-pick{width:15px;height:15px;accent-color:var(--accent);cursor:pointer;flex:none;margin-top:11px}.mention-av{background:var(--tag);border:1px solid var(--border);border-radius:3.40282e38px;flex:none;width:36px;height:36px;position:relative;overflow:visible}.mention-av-init{color:var(--muted);border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:14px;font-weight:700;display:flex;position:absolute;inset:0}.mention-av-img{object-fit:cover;border-radius:3.40282e38px;width:100%;height:100%;position:absolute;inset:0}.mention-av-badge{background:var(--card);width:16px;height:16px;color:var(--brand);border:1px solid var(--border);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:-3px;right:-3px}.mention-av-badge svg{fill:currentColor;width:9.5px;height:9.5px}.mention-body{gap:calc(var(--spacing) * .5);flex-direction:column;flex:1;min-width:0;display:flex}.mention-head{align-items:baseline;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.mention-who{color:var(--fg);font-size:13px;font-weight:650}.mention-when{color:var(--muted);font-size:11.5px}.mention-when:before{content:"·";color:var(--border);margin-right:7px}.mention-hits{color:var(--fg);background:var(--tag);border:1px solid var(--border);border-radius:3.40282e38px;margin-left:auto;padding:1px 7px;font-size:10.5px;font-weight:700}.mention-text{color:var(--fg);word-break:break-word;margin-top:4px;font-size:13.5px;line-height:1.5}.mention-text mark{color:inherit;background:#f43f5e26;border-radius:4px;padding:1px 3px;font-weight:600}.mention-note{border-left:2px solid var(--brand,#f43f5e);background:var(--tag);color:var(--fg);white-space:pre-wrap;word-break:break-word;border-radius:0 6px 6px 0;margin-top:6px;padding:6px 9px;font-size:12.5px;line-height:1.5;display:block}.mention-meta{align-items:center;gap:calc(var(--spacing) * 3.5);margin-top:6px;display:flex}.mention-stat{align-items:center;gap:var(--spacing);color:var(--muted);font-size:11px;display:inline-flex}.mention-stat svg{fill:none;stroke:currentColor;stroke-width:1.8px;stroke-linecap:round;stroke-linejoin:round;width:12px;height:12px}.chip{text-transform:capitalize;letter-spacing:.01em;border:1px solid var(--border);color:var(--muted);border-radius:3.40282e38px;padding:1px 7px;font-size:10px;font-weight:600}.chip-positive{color:#15803d;background:#15803d14;border-color:#15803d55}.chip-negative{color:#b91c1c;background:#b91c1c14;border-color:#b91c1c55}.chip-mixed{color:#a16207;background:#a1620714;border-color:#a1620755}.chip-neutral{color:var(--muted)}.chip-cat{background:var(--tag)}.mention-more{cursor:pointer;border-radius:var(--radius-xl);width:100%;color:var(--fg);background:var(--tag);border:1px solid var(--border);transition:var(--transition);margin-top:12px;padding:10px;font-size:13px;font-weight:600;display:block}.mention-more:hover:not(:disabled){border-color:var(--muted)}.mention-more:disabled{opacity:.6;cursor:default}.sm-thumb-badged{flex:none;position:relative;overflow:visible}.sm-thumb-img{object-fit:cover;border-radius:var(--radius-lg);width:100%;height:100%;display:block}.sm-thumb{border-radius:var(--radius-lg);object-fit:cover;background:var(--tag);border:1px solid var(--border);flex:none;width:56px;height:56px}.tag.copyable{cursor:pointer;border:1px solid var(--border);font:inherit;align-items:center;gap:5px;font-size:12px;display:inline-flex}.tag.copyable:hover{border-color:var(--accent)}.tag.warn{background:var(--red)}@supports (color:color-mix(in lab, red, red)){.tag.warn{background:color-mix(in srgb, var(--red) 12%, transparent)}}.tag.warn{color:var(--red);border:1px solid var(--red)}@supports (color:color-mix(in lab, red, red)){.tag.warn{border:1px solid color-mix(in srgb, var(--red) 35%, transparent)}}.lede-box{background:var(--tag);border-radius:var(--radius-sm);margin-bottom:12px;padding:11px 13px;font-size:13.5px;line-height:1.6}.steps-list{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.step-row{align-items:flex-start;gap:calc(var(--spacing) * 2.5);grid-template-columns:78px 1fr;display:grid}.step-k{letter-spacing:.05em;text-transform:uppercase;color:var(--muted);padding-top:2px;font-size:10.5px;font-weight:700}.step-v{font-size:13px;line-height:1.5}.an-list{margin:0;padding-left:18px;font-size:13px;line-height:1.55}.an-list li{margin:4px 0}.clamp6{-webkit-line-clamp:6;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hook-bar-track{align-items:center;gap:calc(var(--spacing) * 2.5);margin-top:6px;display:flex}.hook-bar{background:var(--tag);border-radius:3.40282e38px;flex:1;height:10px;overflow:hidden}.hook-bar-fill{border-radius:3.40282e38px;height:100%;transition:width .8s cubic-bezier(.4,0,.2,1)}.hook-score{text-align:right;min-width:40px;font-size:15px;font-weight:800}.quote-box{border-radius:var(--radius-md);background:var(--tag);border-left:3px solid var(--blue);margin:6px 0;padding:10px 14px;font-size:13px;font-weight:600;line-height:1.45;position:relative}.quote-at{font-family:var(--font-mono);font-variant-numeric:tabular-nums;color:var(--muted);margin-bottom:4px;font-size:11px;font-weight:700;display:block}.quote-box .copy-btn{cursor:pointer;border-radius:var(--radius-md);background:var(--card);border:1px solid var(--border);color:var(--muted);transition:var(--transition);opacity:0;padding:3px 8px;font-size:10px;position:absolute;top:8px;right:8px}.quote-box:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:var(--card)}.theme-chip{align-items:center;gap:calc(var(--spacing) * 1.5);background:var(--tag);border:1px solid var(--border);color:var(--fg);border-radius:3.40282e38px;margin:0 4px 5px 0;padding:4px 10px;font-size:12px;display:inline-flex}.theme-chip b{color:var(--brand,var(--blue));font-variant-numeric:tabular-nums}.sec-label{letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:7px;font-size:11px;font-weight:700}.cbadge{white-space:nowrap;border-radius:4px;padding:1px 5px;font-size:10px;font-weight:700}.cbadge.pin{color:#b45309;background:#fef3c7}.cbadge.liked{color:#be185d;background:#fce7f3}.cmeta{align-items:center;gap:calc(var(--spacing) * 2);color:var(--muted);flex-wrap:wrap;margin-top:5px;font-size:11px;display:flex}.ctext{color:var(--fg);font-size:13px;line-height:1.45}.ctext.clamp{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.cmore{color:var(--blue);cursor:pointer;background:0 0;border:none;padding:3px 0 0;font-family:inherit;font-size:11.5px;font-weight:600}.comment-row{justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 2.5);border-radius:var(--radius-md);border-bottom:1px solid var(--border);transition:var(--transition);padding:10px 0;display:flex}.comment-row:last-child{border-bottom:none}.comment-row:hover{background:var(--tag)}.creator-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(240px,80vw);min-width:min(240px,80vw);transition:var(--transition);scroll-snap-align:start;flex-shrink:0;padding:16px;display:block}.creator-card:hover{border-color:var(--muted)}.avatar{object-fit:cover;border:2px solid var(--border);border-radius:3.40282e38px;width:52px;height:52px}.avatar-placeholder{border-radius:3.40282e38px;justify-content:center;align-items:center;width:52px;height:52px;font-size:22px;display:flex}.verified{color:#fff;border-radius:3.40282e38px;justify-content:center;align-items:center;width:18px;height:18px;margin-left:4px;font-size:10px;display:inline-flex}.sound-card{border-radius:var(--radius-xl);border:1px solid var(--border);background:var(--card);width:min(300px,88vw);min-width:min(300px,88vw);transition:var(--transition);scroll-snap-align:start;flex-shrink:0;padding:14px;display:block}.sound-card:hover{border-color:var(--muted)}.sound-cover{border-radius:var(--radius-md);object-fit:cover;width:68px;height:68px}.mp{--ar-w:9;--ar-h:16;width:min(100%, calc(var(--stage-max,520px) * var(--ar-w) / var(--ar-h)));background:#000;border:2px solid #0000;border-radius:18px;margin-inline:auto;position:relative;overflow:hidden}.mp.playing{border-color:var(--brand)}.card-media>.mp{width:100%}.mp.h{--ar-w:16;--ar-h:9}.mp.sq{--ar-w:1;--ar-h:1}.mp-stage{width:100%;aspect-ratio:var(--ar-w) / var(--ar-h);max-height:var(--stage-max,520px);background:#000;position:relative;overflow:hidden}.mp .mp-stage>video{object-fit:contain;background:#000;width:100%;height:100%;max-height:none;display:block}.mp .mp-slide{object-fit:contain;opacity:0;background:#000;width:100%;height:100%;max-height:none;transition:opacity .45s;position:absolute;inset:0;transform:none}.mp-slide.active{opacity:1}.mp-ov{pointer-events:none;z-index:2;position:absolute;inset:0}.mp-ov:after{content:"";pointer-events:none;background:linear-gradient(#0000,#00000047 38%,#000000b8);height:46%;position:absolute;bottom:0;left:0;right:0}.mp-ov:before{content:"";pointer-events:none;background:linear-gradient(#00000073,#0000);height:16%;position:absolute;top:0;left:0;right:0}.mp-badge,.mp-chip,.mp-rail,.mp-meta,.mp-tap,.mp-nav{z-index:1}.mp-ov>*{pointer-events:auto}.mp-badge{text-transform:capitalize;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;background:#0000008c;border-radius:3.40282e38px;align-items:center;gap:5px;padding:4px 9px;font-size:11px;font-weight:700;display:inline-flex;position:absolute;top:10px;left:10px}.mp-pick{z-index:3;cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#0000;width:26px;height:26px;transition:var(--transition);background:#0000006b;border:2px solid #ffffffd9;border-radius:3.40282e38px;justify-content:center;align-items:center;padding:0;display:flex;position:absolute;top:9px;right:9px}.mp-pick:hover{background:#000000a8;transform:scale(1.08)}.mp-open{z-index:3;cursor:pointer;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;width:26px;height:26px;transition:var(--transition);background:#0000006b;border:2px solid #ffffffd9;border-radius:3.40282e38px;justify-content:center;align-items:center;padding:0;text-decoration-line:none;display:flex;position:absolute;top:9px;right:9px}.mp-open:hover{background:#000000a8;transform:scale(1.08)}.mp-open.with-pick{right:43px}.mp-pick[aria-pressed=true]{background:var(--brand,#ff4d23);color:#fff;border-color:#fff}.mp.picked{outline:2px solid var(--brand,#ff4d23);outline-offset:-2px}.mp-pick~.mp-chip,.mp[data-mp=slides] .mp-chip{top:42px}.pickbar{z-index:20;align-items:center;gap:calc(var(--spacing) * 2.5);background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow-lg);border-radius:3.40282e38px;margin:12px 0 0;padding:10px 12px;animation:.2s ease-out fadeIn;display:flex;position:sticky;bottom:0}.pickbar .n{white-space:nowrap;font-size:13px;font-weight:700}.pickbar .sp{flex:1}.pickbar button{cursor:pointer;white-space:nowrap;border:1px solid var(--border);background:var(--card);color:var(--fg);font:inherit;transition:var(--transition);border-radius:3.40282e38px;padding:6px 13px;font-size:12.5px;font-weight:600}.pickbar button.primary{background:var(--brand,#ff4d23);color:#fff;border-color:#0000}.pickbar button:hover{filter:brightness(1.08)}.pickbar button:disabled{opacity:.5;cursor:not-allowed}.pickbar .hint{color:var(--muted);font-size:11.5px}.mp-chip{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;font-variant-numeric:tabular-nums;background:#0000008c;border-radius:3.40282e38px;padding:3px 9px;font-size:11px;font-weight:700;position:absolute;top:10px;right:10px}.mp-rail{align-items:center;gap:calc(var(--spacing) * 3.5);flex-direction:column;display:flex;position:absolute;bottom:76px;right:8px}.mp-act{cursor:default;background:0 0;border:none;flex-direction:column;align-items:center;gap:3px;padding:0;display:flex}.mp-act .ic{-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:#fff;width:38px;height:38px;transition:var(--transition);background:#0006;border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.mp-act:hover .ic{background:#0000009e;transform:scale(1.06)}.mp-act .n{color:#fff;text-shadow:0 1px 3px #000c;font-variant-numeric:tabular-nums;font-size:11px;font-weight:700}.mp-meta{color:#fff;text-shadow:0 1px 3px #000000d9;position:absolute;bottom:60px;left:12px;right:64px}.mp-handle{margin-bottom:3px;font-size:13px;font-weight:800}.mp-cap{-webkit-line-clamp:2;opacity:.95;-webkit-box-orient:vertical;font-size:12px;line-height:1.4;display:-webkit-box;overflow:hidden}.mp-song{white-space:nowrap;text-overflow:ellipsis;align-items:center;gap:5px;margin-top:6px;font-size:11px;font-weight:600;display:flex;overflow:hidden}.mp-song .note{flex:none;display:inline-flex}.mp.playing .mp-song .note{animation:3s linear infinite spin}.mp-song-t{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}@keyframes spin{to{transform:rotate(360deg)}}.mp-tap{cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.mp-tap span{-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;width:64px;height:64px;transition:var(--transition);opacity:1;background:#00000073;border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:26px;display:flex}.mp.playing .mp-tap span{opacity:0}.mp.playing:hover .mp-tap span{opacity:.65}.mp-nav{cursor:pointer;color:#fff;opacity:0;width:34px;height:34px;transition:var(--transition);background:#0000008c;border:none;border-radius:3.40282e38px;justify-content:center;align-items:center;font-size:18px;display:flex;position:absolute;top:50%;transform:translateY(-50%)}.mp:hover .mp-nav,.mp:focus-within .mp-nav{opacity:1}.mp-nav.prev{left:8px}.mp-nav.next{right:8px}.mp-nav:hover{background:#000c}.mp-ctl{z-index:3;background:linear-gradient(#0000,#000000d9);align-items:center;gap:9px;padding:9px 11px;display:flex;position:absolute;bottom:0;left:0;right:0}.mp-btn{cursor:pointer;color:#111;width:30px;height:30px;transition:var(--transition);background:#fffffff2;border:none;border-radius:3.40282e38px;flex:none;justify-content:center;align-items:center;font-size:12px;display:flex}.mp-btn:hover{transform:scale(1.09)}.mp-time{color:#fff;font-variant-numeric:tabular-nums;text-shadow:0 1px 2px #000c;flex-shrink:0;font-size:10px;font-weight:700}.mp-seek{cursor:pointer;flex:1;align-items:center;min-width:0;height:14px;display:flex;position:relative}.mp-segs{gap:calc(var(--spacing) * .5);border-radius:3.40282e38px;width:100%;height:3px;transition:height .15s;display:flex;overflow:hidden}.mp-seek:hover .mp-segs{height:5px}.mp-seg{background:#ffffff52;border-radius:3.40282e38px;flex:1;height:100%;overflow:hidden}.mp-seg>i{background:#fff;border-radius:3.40282e38px;width:0;height:100%;display:block}.mp-seg.done>i{width:100%}.mp-icon{color:#fff;cursor:pointer;opacity:.9;background:0 0;border:none;flex-shrink:0;padding:0;font-size:14px;line-height:1}.mp-icon:hover{opacity:1}.mp-spin{pointer-events:none;z-index:2;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.mp-spin>i{border:3px solid #ffffff40;border-top-color:#fff;border-radius:3.40282e38px;width:34px;height:34px;animation:.8s linear infinite spin}.mp-err{pointer-events:none;z-index:4;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);text-align:center;color:#fff;background:#000000d1;flex-direction:column;padding:16px;font-size:12px;font-weight:600;display:flex;position:absolute;inset:0}.mp:focus-visible{outline-offset:-2px;outline:2px solid #fff}.mp-btn:focus-visible,.mp-icon:focus-visible,.mp-nav:focus-visible,.mp-tap:focus-visible,.au-play:focus-visible,.gallery-nav:focus-visible,.dot:focus-visible{outline-offset:2px;outline:2px solid #fff}@media (prefers-reduced-motion:reduce){.mp-song .note{animation:none}.mp-slide{transition:none}.mp-spin>i{animation-duration:2s}*{scroll-behavior:auto!important}}.au{align-items:center;gap:calc(var(--spacing) * 2.5);margin-top:10px;display:flex}.au-play{cursor:pointer;color:#fff;background:var(--brand);width:34px;height:34px;transition:var(--transition);border:none;border-radius:3.40282e38px;flex:none;justify-content:center;align-items:center;display:flex}.au-body{flex:1;min-width:0}.au-seek{cursor:pointer;background:var(--fg);border-radius:3.40282e38px;width:100%;height:5px;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.au-seek{background:color-mix(in srgb, var(--fg) 22%, transparent)}}.au-seek>i{background:var(--accent);border-radius:3.40282e38px;width:0;height:100%;display:block}.au-time{color:var(--muted);font-variant-numeric:tabular-nums;justify-content:space-between;margin-top:3px;font-size:10px;display:flex}.au-missing{align-items:center;gap:calc(var(--spacing) * 1.5);color:var(--muted);margin-top:8px;font-size:11px;display:flex}.own-title{color:var(--fg);font-size:16px;font-weight:700}.own-sub{color:var(--muted);margin:3px 0 12px;font-size:12.5px}.own-head{align-items:center;gap:calc(var(--spacing) * 2);color:var(--fg);font-size:16px;font-weight:700;display:flex}.own-tick{color:var(--green);display:inline-flex}.own-connect{text-align:center;flex-direction:column;align-items:center;padding:30px 20px 22px;display:flex}.own-connect .btn{background:var(--accent);color:var(--bg)}.mono{font-family:var(--font-mono)}.conn-row{align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom:1px solid var(--border);padding:12px 0;display:flex}.conn-row:last-child{border-bottom:none}.conn-body{flex:1;min-width:0}.conn-who{color:var(--fg);font-size:13px;font-weight:650}.conn-sub{color:var(--muted);margin-top:1px;font-size:11.5px}.conn-note{color:var(--muted);margin-top:5px;font-size:11px}.conn-status,.conn-connect{align-self:flex-start;margin-top:2px}.conn-av{background:var(--tag);border:1px solid var(--border);border-radius:3.40282e38px;flex-shrink:0;width:32px;height:32px;position:relative;overflow:visible}.conn-av-empty{background:0 0;border-style:dashed}.conn-av-mark{color:var(--muted);justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.credits-card{border-radius:var(--radius-2xl);border:1px solid var(--border);background:var(--card);max-width:380px;margin:8px 0;padding:24px}.pack{cursor:pointer;border-radius:var(--radius-xl);text-align:center;border:1px solid var(--border);transition:var(--transition);padding:16px}.pack:hover{border-color:var(--muted)}.pack-featured{border-radius:var(--radius-xl);text-align:center;border:2px solid var(--accent);padding:16px}.tier-badge{color:#1e40af;background:#dbeafe;border-radius:3.40282e38px;align-items:center;padding:4px 12px;font-size:12px;font-weight:700;display:inline-flex}.free-tag{align-items:center;gap:var(--spacing);color:#065f46;transition:var(--transition);background:#d1fae5;border-radius:3.40282e38px;margin:2px;padding:4px 10px;font-size:11px;font-weight:600;display:inline-flex}.json-block{border-radius:var(--radius-md);background:var(--tag);border:1px solid var(--border);white-space:pre-wrap;word-break:break-all;max-height:320px;margin-top:10px;padding:14px;font-family:SF Mono,Monaco,monospace;font-size:12px;overflow-y:auto}.empty-state{text-align:center;color:var(--muted);padding:48px 24px}.empty-state .icon{margin-bottom:12px;font-size:48px}.empty-state .text{font-size:14px}}@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-3{left:calc(var(--spacing) * 3)}.isolate{isolation:isolate}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.w-full{width:100%}.max-w-sm{max-width:var(--container-sm)}.shrink{flex-shrink:1}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.resize{resize:both}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-line{border-color:var(--border)}.bg-black{background-color:var(--color-black)}.bg-soft{background-color:var(--tag)}.bg-surface{background-color:var(--card)}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-3{padding:calc(var(--spacing) * 3)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:var(--spacing)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-10{padding-block:calc(var(--spacing) * 10)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-wrap{text-wrap:wrap}.text-ink{color:var(--fg)}.text-muted{color:var(--muted)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-all{-webkit-user-select:all;user-select:all}}:root{color-scheme:light dark;--bg:#fafbfc;--fg:#111827;--muted:#5f6773;--border:#e5e7eb;--card:#fff;--card-hover:#f3f4f6;--tag:#f3f4f6;--accent:#14151a;--brand:#ff4d23;--brand-2:#ff8a3d;--brand-soft:#ff4d2321;--radius:10px;--radius-sm:6px;--transition:all .25s cubic-bezier(.4, 0, .2, 1);--shadow-md:0 4px 12px #00000014;--shadow-lg:0 8px 24px #0000001f;--green:#047857;--amber:#b45309;--red:#dc2626;--blue:#2563eb}@media (prefers-color-scheme:dark){:root{--bg:#0f172a;--fg:#f1f5f9;--muted:#a8b8cc;--border:#334155;--green:#34d399;--amber:#fbbf24;--red:#f87171;--blue:#60a5fa;--card:#1e293b;--card-hover:#273548;--tag:#1e293b;--accent:#e2e8f0;--brand:#ff5c33;--brand-2:#ff9351;--brand-soft:#ff5c3329;--shadow-md:0 4px 12px #0000004d;--shadow-lg:0 8px 24px #0006}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
22
22
  </style>
23
23
  </head>
24
24
  <body>
@@ -48,7 +48,11 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
48
48
  spawn_variants:"Spawn Variants",
49
49
  bake_job_status:"Bake Job Status",
50
50
  enqueue_publish_job:"Enqueue Publish",
51
- schedule_post:"Schedule Post"
51
+ schedule_post:"Schedule Post",
52
+ list_social_connections:"Connected Accounts",
53
+ connect_social_account:"Connect an Account",
54
+ create_product:"Add a Product",
55
+ update_product:"Update a Product"
52
56
  };
53
57
  var TOOL_DESCS={
54
58
  analyze_post:"Frames from the post plus its transcript, for you to read yourself.",
@@ -68,29 +72,180 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
68
72
  spawn_variants:"Generate multiple content variants from a single seed.",
69
73
  bake_job_status:"Check the status of a rendering/baking job.",
70
74
  enqueue_publish_job:"Queue a post for publishing to a connected platform.",
71
- schedule_post:"Schedule a post for future publishing."
72
- };
73
- /* How many results a tool comes back with, and in what shape — so the
74
- skeleton matches the answer instead of being a generic grey box. */
75
- var LOADING_SHAPE={
76
- get_social_media: {kind:"post", n:1, label:"Fetching the post"},
77
- get_post_transcript: {kind:"text", n:1, label:"Reading the caption track"},
78
- analyze_post: {kind:"post", n:1, label:"Analysing the post"},
79
- understand_social_post: {kind:"post", n:1, label:"Watching the video"},
80
- analyze_creator_profile: {kind:"list", n:4, label:"Reading the profile"},
81
- compare_posts: {kind:"strip", n:2, label:"Comparing posts"},
82
- discover_social_posts: {kind:"strip", n:3, label:"Searching posts"},
83
- get_user_posts: {kind:"strip", n:3, label:"Loading their posts"},
84
- search_creators: {kind:"list", n:5, label:"Finding creators"},
85
- get_similar_creators: {kind:"list", n:5, label:"Finding similar creators"},
86
- discover_sounds: {kind:"list", n:4, label:"Finding trending sounds"},
87
- discover_hashtags: {kind:"list", n:6, label:"Reading the trend board"},
88
- get_post_comments: {kind:"list", n:6, label:"Loading comments"},
89
- analyze_comments: {kind:"text", n:1, label:"Reading the comment section"},
90
- check_nooticr_credits: {kind:"text", n:1, label:"Checking your balance"},
91
- buy_nooticr_credits: {kind:"text", n:1, label:"Opening checkout"}
75
+ schedule_post:"Schedule a post for future publishing.",
76
+ list_social_connections:"What you have connected, and what each connection is allowed to do.",
77
+ connect_social_account:"A link you open to connect one account. Nothing connects until you approve it.",
78
+ create_product:"Create a product in your own workspace — the row every other own-account tool needs.",
79
+ update_product:"Patch a product's fields. An omitted field keeps the value it had."
92
80
  };
93
81
 
82
+ // ─── The mark ───
83
+ // The idle illustration and the loading indicator are the same SVG; a call
84
+ // starting only adds two animations to it, so there is no cut between the
85
+ // placeholder and the wait.
86
+ //
87
+ // The two rings overlap - the left one's outer edge reaches x=200 and the
88
+ // right one begins at x=167 - so the logo cuts the right ring where the
89
+ // left crosses it. That cut is a mask rather than a stroke painted in the
90
+ // background colour: this view is mounted inside the host's transcript and
91
+ // deliberately paints no body background, so there is no colour it could
92
+ // paint the cut in that would be right on every host.
93
+ function mkSvg(w,h,scanning){
94
+ var lid=scanning?' class="lid"':"";
95
+ var iris=scanning?' class="iris"':"";
96
+ return '<svg class="mk" width="'+w+'" height="'+h+'" viewBox="-10 -10 387 220" aria-hidden="true">'
97
+ +'<mask id="mkcut" maskUnits="userSpaceOnUse" x="-10" y="-10" width="387" height="220">'
98
+ +'<rect x="-10" y="-10" width="387" height="220" fill="#fff"/>'
99
+ +'<circle cx="100" cy="100" r="84.75" fill="none" stroke="#000" stroke-width="46"/>'
100
+ +'</mask>'
101
+ +'<g'+lid+' fill="none" stroke="currentColor" stroke-width="30.5">'
102
+ +'<circle cx="267" cy="100" r="84.75" mask="url(#mkcut)"/>'
103
+ +'<circle cx="100" cy="100" r="84.75"/>'
104
+ +'<circle'+iris+' cx="116.5" cy="66.5" r="33.25"/>'
105
+ +'<circle'+iris+' cx="250.5" cy="66.5" r="33.25" style="animation-delay:.09s"/>'
106
+ +'</g></svg>';
107
+ }
108
+
109
+ // ─── What this call is about to do, and what it costs ───
110
+ // Substituted by templateFor() from src/shared/loading-plans.ts, which
111
+ // builds it from the same constants the tools bill against. The first
112
+ // version of this was a price list typed in by hand from nooticr-server's
113
+ // mcp_tool_cost, and it was wrong about six tools: that table prices a
114
+ // direct /mcp call, while every evidence tool here fans out to cheaper ones
115
+ // — analyze_post is 6 there and 3 here. A price on a loading screen is a
116
+ // promise, so it comes from the implementation or not at all.
117
+ var PLAN_DATA="__NOOTICR_PLANS__";
118
+ var PLANS={},ALL_PLATFORMS=[],PER_NETWORK=2,XHS_CREDITS=5;
119
+ if(PLAN_DATA&&typeof PLAN_DATA==="object"){
120
+ PLANS=PLAN_DATA.plans||{};
121
+ ALL_PLATFORMS=PLAN_DATA.platforms||[];
122
+ PER_NETWORK=PLAN_DATA.perNetwork||2;
123
+ XHS_CREDITS=PLAN_DATA.xiaohongshu||5;
124
+ }
125
+
126
+ /** How many distinct values of an array argument the tool will actually keep. */
127
+ function listCount(v,only,fallback){
128
+ if(!Array.isArray(v)||!v.length)return fallback;
129
+ var seen={},n=0;
130
+ for(var i=0;i<v.length;i++){
131
+ var k=String(v[i]).toLowerCase();
132
+ if(only&&only.indexOf(k)<0)continue;
133
+ if(seen[k])continue;
134
+ seen[k]=1;n++;
135
+ }
136
+ return n;
137
+ }
138
+
139
+ /**
140
+ * How many units an argument asks for, or what the tool does without it.
141
+ *
142
+ * Every branch here mirrors something the tool does to the same argument
143
+ * before it spends: the list it filters requested platforms through, the
144
+ * per-platform fan-out a named handle gets, the clamp a numeric ceiling is
145
+ * put through. A wait that counts the argument as typed instead quotes
146
+ * calls that never run - a maxTranscripts of 200 drawing 200 lines for a
147
+ * call the server clamps to 20 - and a price on a loading screen is a
148
+ * promise. The atMost flag is the one thing this cannot pin down: a
149
+ * watchlist adds however many creators it holds, and a sandboxed view
150
+ * cannot read it, so
151
+ * the answer is the ceiling drawn as a ceiling.
152
+ */
153
+ function unitCount(unit,args){
154
+ var a=args&&typeof args==="object"?args:{};
155
+ if(unit.onlyWith&&!a[unit.onlyWith])return {n:0,atMost:false};
156
+ var atMost=false,n;
157
+ if(!unit.arg){
158
+ n=unit.defaultCount;
159
+ }else{
160
+ var v=a[unit.arg];
161
+ if(Array.isArray(v))n=listCount(v,unit.only,unit.defaultCount);
162
+ else if(typeof v==="number"&&isFinite(v))n=Math.max(0,Math.floor(v));
163
+ else n=unit.defaultCount;
164
+ }
165
+ if(unit.per)n*=listCount(a[unit.per.arg],unit.per.only,unit.per.defaultCount);
166
+ if(unit.ceilingWith&&a[unit.ceilingWith]){
167
+ n=typeof unit.max==="number"?unit.max:n;
168
+ atMost=true;
169
+ }
170
+ if(typeof unit.min==="number"&&n>0&&n<unit.min)n=unit.min;
171
+ if(typeof unit.max==="number"&&n>unit.max)n=unit.max;
172
+ return {n:n,atMost:atMost};
173
+ }
174
+
175
+ /** The named platforms, or every one of them when none were named. */
176
+ function chosenPlatforms(args){
177
+ var a=args&&typeof args==="object"?args:{};
178
+ var list=Array.isArray(a.platforms)?a.platforms:null;
179
+ var out=[];
180
+ if(list&&list.length){
181
+ for(var i=0;i<list.length;i++){
182
+ var k=String(list[i]).toLowerCase();
183
+ if(ALL_PLATFORMS.indexOf(k)>=0)out.push(k);
184
+ }
185
+ }
186
+ return out.length?out:ALL_PLATFORMS.slice();
187
+ }
188
+
189
+ /**
190
+ * The itemised fan-out: every backend call this tool will make, priced.
191
+ *
192
+ * These are lines the call *will* run, not lines it has finished. MCP Apps
193
+ * hands a view four notifications - tool-input, tool-input-partial,
194
+ * tool-result, tool-cancelled - and none of them is progress, so nothing
195
+ * here can honestly tick a row off. The design board's ledger ticks green
196
+ * checks through its rows; that is a canvas loop, and copying it would
197
+ * assert completions this server has no way to know.
198
+ */
199
+ function ledgerFor(tool,args){
200
+ var plan=PLANS[tool];
201
+ if(!plan)return [];
202
+ var lines=[];
203
+ (plan.steps||[]).forEach(function(st){
204
+ lines.push({label:st.label,detail:st.detail||"",credits:st.credits});
205
+ });
206
+ (plan.perUnit||[]).forEach(function(unit){
207
+ if(unit.perPlatform){
208
+ chosenPlatforms(args).forEach(function(k){
209
+ lines.push({
210
+ label:pTitle(k),
211
+ detail:unit.detail||"",
212
+ credits:k==="xiaohongshu"?XHS_CREDITS:PER_NETWORK,
213
+ platform:k
214
+ });
215
+ });
216
+ return;
217
+ }
218
+ var u=unitCount(unit,args),n=u.n;
219
+ if(n<=0)return;
220
+ lines.push({
221
+ label:unit.label+(u.atMost?" ×"+n+" at most":(n>1?" ×"+n:"")),
222
+ detail:unit.detail||"",
223
+ credits:unit.credits*n
224
+ });
225
+ });
226
+ return lines;
227
+ }
228
+
229
+ /** What this call costs, in credits, or -1 when the price is not knowable. */
230
+ function creditsFor(tool,args){
231
+ var plan=PLANS[tool];
232
+ if(!plan)return -1;
233
+ if(plan.free)return 0;
234
+ var total=0;
235
+ ledgerFor(tool,args).forEach(function(l){total+=l.credits;});
236
+ return total;
237
+ }
238
+ function priceLabel(credits,plan){
239
+ // Three states, not two. A generation tool is not free, but what it
240
+ // spends is the workspace's plan AI credits — a different balance from
241
+ // the personal MCP ones check_nooticr_credits reports on. "Free" would be
242
+ // a lie a user only meets on their plan bill.
243
+ if(plan&&plan.planAi)return "Plan AI credits";
244
+ if(credits===0)return "Free";
245
+ if(credits<0)return "";
246
+ return credits+(credits===1?" credit":" credits");
247
+ }
248
+
94
249
  function skPostCard(){
95
250
  return '<div class="sk-card"><div class="sk sk-media"></div><div class="sk-body">'
96
251
  +'<span class="sk sk-pill" style="width:64px"></span>'
@@ -122,6 +277,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
122
277
  if(a.username)return {kind:"strip",n:3,label:"Loading their posts"};
123
278
  if(a.country||a.days)return {kind:"list",n:6,label:"Reading the trend board"};
124
279
  if(a.niche||a.keyword)return {kind:"strip",n:3,label:"Searching"};
280
+ if(a.term)return {kind:"list",n:6,label:"Sweeping for mentions"};
125
281
  if(a.url)return {kind:"post",n:1,label:"Working on the post"};
126
282
  if(a.topic)return {kind:"text",n:1,label:"Writing"};
127
283
  return null;
@@ -147,30 +303,165 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
147
303
  // only when the shape changes, or when it is no longer on screen because a
148
304
  // result replaced it - which is the second call in a reused view.
149
305
  var loadingSig=null;
306
+
307
+ /**
308
+ * What the arguments already tell us about the answer.
309
+ *
310
+ * Turn 2c asked for the real card to fill in instead of a skeleton. It
311
+ * cannot fill in from the server - there is no partial-result channel - but
312
+ * a call is never made with nothing known: a url names its platform, a
313
+ * username is the handle the posts will be under, a term is the word being
314
+ * searched for. Those are facts, already in hand, and showing them beats
315
+ * showing a grey rectangle where they will land.
316
+ */
317
+ function knownFrom(args){
318
+ var a=args&&typeof args==="object"?args:{};
319
+ var out=[];
320
+ var url=typeof a.url==="string"?a.url:(Array.isArray(a.urls)&&a.urls.length?String(a.urls[0]):"");
321
+ if(url){
322
+ var plat=platformOfUrl(url);
323
+ if(plat)out.push({icon:pSvg(plat,13),text:pTitle(plat),brand:pColor(plat)});
324
+ }
325
+ if(a.username)out.push({text:"@"+String(a.username).replace(/^@/,"")});
326
+ if(Array.isArray(a.usernames)&&a.usernames.length){
327
+ out.push({text:a.usernames.length===1
328
+ ?"@"+String(a.usernames[0]).replace(/^@/,"")
329
+ :a.usernames.length+" creators"});
330
+ }
331
+ if(a.term)out.push({text:'"'+String(a.term)+'"'});
332
+ if(a.niche)out.push({text:String(a.niche)});
333
+ if(a.keyword)out.push({text:String(a.keyword)});
334
+ if(a.topic)out.push({text:String(a.topic)});
335
+ if(a.country)out.push({text:String(a.country).toUpperCase()});
336
+ if(a.since)out.push({text:"since "+String(a.since)});
337
+ return out;
338
+ }
339
+
340
+ /** The platform a post URL belongs to, for the one fact every url carries. */
341
+ function platformOfUrl(url){
342
+ var u=String(url||"").toLowerCase();
343
+ var known=["tiktok","youtube","instagram","douyin","xiaohongshu","bilibili",
344
+ "reddit","weibo","linkedin"];
345
+ for(var i=0;i<known.length;i++)if(u.indexOf(known[i])>=0)return known[i];
346
+ if(u.indexOf("twitter.com")>=0||u.indexOf("x.com")>=0)return "twitter";
347
+ if(u.indexOf("youtu.be")>=0)return "youtube";
348
+ return "";
349
+ }
350
+
351
+ /** The facts already in hand, as the chips they will be replaced by. */
352
+ function knownChips(args,said){
353
+ var facts=knownFrom(args).filter(function(f){
354
+ return !said||String(said).indexOf(f.text)<0;
355
+ });
356
+ if(!facts.length)return "";
357
+ return '<div class="load-known">'+facts.map(function(f){
358
+ return '<span class="load-fact"'+(f.brand?' style="--brand:'+f.brand+'"':"")+">"
359
+ +(f.icon?'<span class="load-fact-mark">'+f.icon+"</span>":"")
360
+ +esc(f.text)+"</span>";
361
+ }).join("")+"</div>";
362
+ }
363
+
364
+ /**
365
+ * The fan-out, itemised — turn 2b.
366
+ *
367
+ * Every line is a call the tool will make and the credits it will cost.
368
+ * Deliberately no per-line state: see ledgerFor. The footer is the part a
369
+ * reader actually needs, which is that the number above is a ceiling rather
370
+ * than a charge already taken.
371
+ */
372
+ function ledgerHtml(lines,total){
373
+ if(!lines.length)return "";
374
+ var rows=lines.map(function(l){
375
+ return '<div class="led-row">'
376
+ +(l.platform?'<span class="led-mark" style="--brand:'+pColor(l.platform)+'">'
377
+ +pSvg(l.platform,13)+"</span>":'<span class="led-dot"></span>')
378
+ +'<div class="led-body"><div class="led-label">'+esc(l.label)+"</div>"
379
+ +(l.detail?'<div class="led-detail">'+esc(l.detail)+"</div>":"")+"</div>"
380
+ +'<span class="led-cost">'+esc(String(l.credits))+"</span>"
381
+ +"</div>";
382
+ }).join("");
383
+ return '<div class="card card-wide led"><div class="card-body">'+rows
384
+ +'<div class="led-foot">Billed at what it fetched — '+esc(String(total))
385
+ +" credit"+(total===1?"":"s")+" is the ceiling, not a charge already taken. "
386
+ +"A failed fetch is refunded.</div></div></div>";
387
+ }
388
+
389
+ // A ChatGPT-shaped host sends no tool-input notification, so the poll below
390
+ // is what drives the skeleton there - which means this runs over and over
391
+ // within one call. The wait is a pure function of tool and arguments, so
392
+ // paint it once and let the CSS run: repaint only when the signature moves.
150
393
  function renderLoading(tool,args){
151
394
  var app=document.getElementById("app");
152
395
  if(!app||toolResultReceived)return;
153
- var shape=LOADING_SHAPE[tool]||shapeFromArgs(args)||{kind:"text",n:1,label:"Working"};
154
- var sig=shape.kind+"|"+shape.n+"|"+shape.label;
396
+ var plan=PLANS[tool];
397
+ var shape=plan?{kind:plan.kind,n:plan.n,label:plan.label}
398
+ :shapeFromArgs(args)||{kind:"text",n:1,label:"Working"};
399
+ var credits=creditsFor(tool,args);
400
+ var lines=ledgerFor(tool,args);
401
+ var facts=knownFrom(args).map(function(f){return f.text;}).join("|");
402
+ var sig=tool+"|"+shape.kind+"|"+shape.n+"|"+shape.label+"|"+credits+"|"
403
+ +lines.length+"|"+facts;
155
404
  if(sig===loadingSig&&app.querySelector(".load-bar"))return;
156
405
  loadingSig=sig;
157
- var inner="";
406
+
407
+ var price=priceLabel(credits,plan);
408
+ var priceHtml=price?'<span class="load-price">'+esc(price)+"</span>":"";
409
+
410
+ // What is being worked on, said with the arguments rather than in general.
411
+ var what=shape.label;
412
+ if(tool==="search_mentions"&&args&&args.term){
413
+ var nets=chosenPlatforms(args).length;
414
+ what="Sweeping "+nets+" network"+(nets===1?"":"s")+' for "'+args.term+'"';
415
+ }else if(args&&args.term){
416
+ what=shape.label+' for "'+args.term+'"';
417
+ }else if(args&&args.niche){
418
+ what=shape.label+' for "'+args.niche+'"';
419
+ }
420
+
421
+ var skeleton="";
158
422
  if(shape.kind==="post"){
159
- inner='<div style="display:flex;justify-content:center">'+skPostCard()+"</div>";
423
+ skeleton='<div style="display:flex;justify-content:center">'+skPostCard()+"</div>";
160
424
  }else if(shape.kind==="strip"){
161
425
  var cards="";
162
426
  for(var i=0;i<shape.n;i++)cards+=skPostCard();
163
- inner='<div class="sk-strip">'+cards+"</div>";
427
+ skeleton='<div class="sk-strip">'+cards+"</div>";
164
428
  }else if(shape.kind==="list"){
165
429
  var rows="";
166
430
  for(var j=0;j<shape.n;j++)rows+=skRow();
167
- inner='<div class="card card-wide"><div class="card-body">'+rows+"</div></div>";
431
+ skeleton='<div class="card card-wide"><div class="card-body">'+rows+"</div></div>";
432
+ }else{
433
+ skeleton='<div class="card card-wide"><div class="card-body">'+skText()+"</div></div>";
434
+ }
435
+
436
+ // Six is the same line spend.ts draws for asking before spending: below
437
+ // it the number is small enough that stating it costs more attention than
438
+ // it is worth. Above it the wait is the screen, so it gets the centred
439
+ // mark, the committed spend and the itemised ledger underneath.
440
+ var big=credits>6;
441
+ var body;
442
+ if(big){
443
+ body='<div class="load-centre">'+mkSvg(158,90,true)
444
+ +'<div class="load-what">'+esc(what)+"</div>"
445
+ +'<div class="load-sub">'+esc(price)+" committed"
446
+ +(plan&&plan.note?" · "+esc(plan.note):"")+"</div>"
447
+ +'<div class="load-bar"></div></div>'
448
+ +knownChips(args,what)
449
+ +ledgerHtml(lines,credits);
168
450
  }else{
169
- inner='<div class="card card-wide"><div class="card-body">'+skText()+"</div></div>";
451
+ body='<div class="load-bar"></div><div class="load-head">'+mkSvg(32,18,true)
452
+ +"<span>"+esc(what)+"…</span>"+priceHtml+"</div>"
453
+ +knownChips(args,what)
454
+ // One line is not a fan-out: the head already named that call and
455
+ // its price, and repeating it underneath is noise. Two or more is
456
+ // the thing worth itemising — "analyze_post costs 3" says less than
457
+ // "frames 2, transcript 1".
458
+ //
459
+ // The ledger and the skeleton both reserve the space the answer will
460
+ // occupy, so only one of them is drawn: itemised where there is a
461
+ // fan-out to itemise, shaped where there is not.
462
+ +(lines.length>1?ledgerHtml(lines,credits):skeleton);
170
463
  }
171
- app.innerHTML='<div class="fade-in"><div class="load-bar"></div>'
172
- +'<div class="load-head"><span class="dot"></span><span>'
173
- +esc(shape.label)+"…</span></div>"+inner+"</div>";
464
+ app.innerHTML='<div class="fade-in">'+body+"</div>";
174
465
  setTimeout(reportSize,50);
175
466
  }
176
467
 
@@ -207,7 +498,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
207
498
  var app=document.getElementById("app");
208
499
  if(!app)return;
209
500
  app.innerHTML='<div class="flex flex-col items-center justify-center text-center px-6 py-10" style="min-height:220px">'
210
- +'<svg width="73" height="44" viewBox="0 0 334 200" fill="currentColor" class="text-ink"><path d="M167,25.764A100,100 0 1,1 167,174.236A100,100 0 0,0 186.29,150.537A69.5,69.5 0 1,0 186.29,49.463A100,100 0 0,0 167,25.764ZM298.427,73.934A69.5,69.5 0 0,0 215.393,33.037A48.5,48.5 0 1,0 298.427,73.934ZM232.5,66.5a18,18 0 1,1 36,0a18,18 0 1,1 -36,0ZM0,100a100,100 0 1,1 200,0a100,100 0 1,1 -200,0ZM30.5,100a69.5,69.5 0 1,0 139,0a69.5,69.5 0 1,0 -139,0ZM164.427,73.934A69.5,69.5 0 0,0 81.393,33.037A48.5,48.5 0 1,0 164.427,73.934ZM98.5,66.5a18,18 0 1,1 36,0a18,18 0 1,1 -36,0Z"/></svg>'
501
+ +mkSvg(77,44,false)
211
502
  +'<div class="mt-4 text-lg font-bold text-ink">'+label+'</div>'
212
503
  +'<div class="mt-2 text-sm leading-relaxed text-muted max-w-sm">'+desc+'</div>'
213
504
  +'</div>';
@@ -1019,6 +1310,24 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1019
1310
  t.on("pause",function(){if(!box.classList.contains("playing"))return;setPlaying(false);clearInterval(ticker);});
1020
1311
  t.on("play",function(){if(!box.classList.contains("playing"))start();});
1021
1312
  t.on("loadedmetadata",paintSeek);
1313
+ // A spoken hit opens at the moment the term was said rather than at 0:00 —
1314
+ // the whole claim of that row is that the words are in the video at that
1315
+ // timecode, and making a reader scrub for them is asking them to take it
1316
+ // on trust. Once only: a seek on every metadata event would drag the
1317
+ // playhead back each time the element re-reported itself.
1318
+ var startAt=Number(box.getAttribute("data-mp-start")||0);
1319
+ if(startAt>0){
1320
+ var sought=false;
1321
+ var seekOnce=function(){
1322
+ if(sought)return;
1323
+ sought=true;
1324
+ t.seek(startAt);
1325
+ paintSeek();
1326
+ };
1327
+ t.on("loadedmetadata",seekOnce);
1328
+ // Metadata may already be in by the time this runs.
1329
+ if(t.kind==="video"&&t.el&&t.el.readyState>=1)seekOnce();
1330
+ }
1022
1331
  if(t.kind==="slides")t.on("timeupdate",tick);
1023
1332
 
1024
1333
  // Buffering and failure are states the user can see, not a frozen frame.
@@ -1210,6 +1519,152 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1210
1519
  return '<svg viewBox="0 0 24 24" width="'+s+'" height="'+s+'" fill="currentColor" style="display:block;flex-shrink:0" aria-hidden="true" focusable="false"><path d="'+d+'"/></svg>';
1211
1520
  }
1212
1521
 
1522
+ /**
1523
+ * One typed comment, as a row.
1524
+ *
1525
+ * Pulled out of renderMonitor so the merged feed below draws exactly the
1526
+ * same row rather than a near-copy of it: the two would drift, and the
1527
+ * whole point of merging is that a reader stops having to notice which
1528
+ * screen they are on.
1529
+ *
1530
+ * "sourced" adds the chip naming where the row came from. It is off on the
1531
+ * comment-only screen, where every row has the same answer and the chip
1532
+ * would be noise, and on in the merged feed, where it is the point.
1533
+ */
1534
+ function typedRow(m,plat,term,sourced){
1535
+ // No invented handle. "@someone" reads as an account you could go and
1536
+ // look at, and there is none — the platform withheld it, or the
1537
+ // commenter is deleted. An empty handle renders as a stated absence
1538
+ // below instead of a fabricated name. (No backticks in here: this
1539
+ // whole view is one TypeScript template literal, and one would end it.)
1540
+ var hits=Number(m.hits||1),id=String(m.id||"");
1541
+ var who=m.username==null?"":String(m.username).trim();
1542
+ var pic=String(m.avatarProxyUrl||m.avatarUrl||"");
1543
+ var picAlt=String(m.avatarUrl||"");
1544
+ // A face and a platform mark, then the words. Reading a feed of
1545
+ // strangers is mostly working out who is talking; a column of handles
1546
+ // makes you read every one of them to find out.
1547
+ var avatar='<span class="mention-av" style="--brand:'+pColor(plat)+'">'
1548
+ +'<span class="mention-av-init">'+esc(initialOf(who))+"</span>"
1549
+ +(pic?'<img class="mention-av-img" data-avatar src="'+esc(pic)+'"'
1550
+ +(picAlt&&picAlt!==pic?' data-fallback="'+esc(picAlt)+'"':"")
1551
+ +' alt="" loading="lazy"/>':"")
1552
+ +'<span class="mention-av-badge">'+pSvg(plat,9)+"</span></span>";
1553
+ return '<label class="mention" data-mention-id="'+esc(id)+'">'
1554
+ +'<input type="checkbox" class="mention-pick" data-mid="'+esc(id)+'"/>'
1555
+ +avatar
1556
+ +'<span class="mention-body">'
1557
+ +'<span class="mention-head">'
1558
+ +(who?'<span class="mention-who">@'+esc(who)+"</span>"
1559
+ :'<span class="mention-who mention-who-unknown">no handle</span>')
1560
+ +(m.postedAt?'<span class="mention-when">'+esc(friendlyTime(m.postedAt))+"</span>":"")
1561
+ +(hits>1?'<span class="mention-hits" title="names it '+hits+' times">×'+hits+"</span>":"")
1562
+ +"</span>"
1563
+ +'<span class="mention-text">'+highlightTerm(String(m.text||""),term)+"</span>"
1564
+ +(m.note?'<span class="mention-note">'+esc(String(m.note))+"</span>":"")
1565
+ +'<span class="mention-meta">'
1566
+ +'<span class="mention-stat">'+heartIcon()+esc(fmtNum(m.likes||0))+"</span>"
1567
+ +'<span class="mention-stat">'+replyIcon()+esc(fmtNum(m.replies||0))+"</span>"
1568
+ +labelChips(m)
1569
+ +(sourced?'<span class="chip chip-cat">comment</span>':"")
1570
+ +"</span></span></label>";
1571
+ }
1572
+
1573
+ /**
1574
+ * A spoken hit's own stage: the post's video, opened at the line.
1575
+ *
1576
+ * The design's case for this is that a quote lifted out of a transcript is
1577
+ * an assertion, and the video is the evidence for it — putting the two a
1578
+ * click apart is what makes a reader either trust the row or ignore it. So
1579
+ * the top spoken hit in a group gets a real player seeked to its own
1580
+ * timecode, and the rows underneath stay the compact feed.
1581
+ *
1582
+ * Only when there is something to play and somewhere to start: a hit whose
1583
+ * post carries no video, or whose track had no timings, falls back to the
1584
+ * thumbnail the row already shows rather than a black rectangle.
1585
+ */
1586
+ function spokenStage(hit,startMs){
1587
+ var post=hit&&hit.post||{};
1588
+ var video=typeof post.videoUrl==="string"&&post.videoUrl?post.videoUrl
1589
+ :typeof post.videoProxyUrl==="string"&&post.videoProxyUrl?post.videoProxyUrl:"";
1590
+ if(!video||typeof startMs!=="number")return "";
1591
+ var html=mediaPlayerHtml(post,[],video,String(post.platform||""),false);
1592
+ // mediaPlayerHtml has no opinion about where to start; the attribute is
1593
+ // read by initMediaPlayer once the media reports its duration.
1594
+ var at=' data-mp-start="'+(Math.max(0,Math.floor(startMs/1000)))+'"';
1595
+ return '<div class="mgroup-stage">'+html.replace('<div class="mp ', '<div'+at+' class="mp ')+"</div>";
1596
+ }
1597
+
1598
+ /**
1599
+ * The same video, opened at the moment the term was said.
1600
+ *
1601
+ * Only YouTube is given a timestamp: it reads ?t= (and &t=) on a watch URL
1602
+ * and lands there. TikTok has no public deep link into a moment, so its row
1603
+ * shows the timecode and the link opens the post — a ?t= it ignores would
1604
+ * promise a jump that never happens.
1605
+ */
1606
+ function linkAtMoment(url,plat,startMs){
1607
+ var u=String(url||"");
1608
+ if(!u||plat!=="youtube"||typeof startMs!=="number")return u;
1609
+ var secs=Math.max(0,Math.floor(startMs/1000));
1610
+ // Before the fragment, not after it: everything past a # is the
1611
+ // fragment, so a t= appended there is part of that string and never
1612
+ // reaches the query the player reads.
1613
+ var hash=u.indexOf("#"),frag=hash>=0?u.slice(hash):"";
1614
+ if(hash>=0)u=u.slice(0,hash);
1615
+ return u+(u.indexOf("?")>=0?"&":"?")+"t="+secs+"s"+frag;
1616
+ }
1617
+
1618
+ /**
1619
+ * One spoken line, as the same row.
1620
+ *
1621
+ * Where a comment carries a face and its likes, a spoken line carries the
1622
+ * video's thumbnail and the reach of the video it was said in — the
1623
+ * commenter is the creator, and there is no separate account behind the
1624
+ * words. The note is not decoration: a line lifted off a caption track is
1625
+ * a different kind of evidence from a comment somebody typed, and a reader
1626
+ * scanning a merged feed has to be able to tell without checking.
1627
+ *
1628
+ * The timecode is the caption track's own, carried through from the cue the
1629
+ * match landed in — not estimated from how far through the transcript it
1630
+ * sits, which would be wrong by however long the speaker paused. A track
1631
+ * with no timings (the speech-to-text fallback, on the platforms that
1632
+ * publish no captions) yields no timecode rather than a plausible wrong
1633
+ * one, and the row simply reads "spoken".
1634
+ */
1635
+ function spokenRow(hit,ex,i,term){
1636
+ var post=hit.post||{},plat=String(post.platform||"");
1637
+ var id=spokenId(hit,ex,i);
1638
+ var thumb=String(post.thumbnailUrl||post.thumbnailProxyUrl||"");
1639
+ var thumbFb=String((post.thumbnailUrl&&(post.thumbnailFallbackUrl||post.thumbnailProxyUrl))||"");
1640
+ var text=String(ex.text||"");
1641
+ var at=ex.timecode?String(ex.timecode):"";
1642
+ var face=thumb
1643
+ ? '<span class="sm-thumb sm-thumb-badged">'
1644
+ +'<img class="sm-thumb-img" src="'+esc(thumb)+'"'
1645
+ +(thumbFb&&thumbFb!==thumb?' data-fallback="'+esc(thumbFb)+'"':"")
1646
+ +' alt="" loading="lazy"/>'
1647
+ +'<span class="mention-av-badge">'+pSvg(plat,9)+"</span></span>"
1648
+ : '<span class="mention-av" style="--brand:'+pColor(plat)+'">'
1649
+ +'<span class="mention-av-init">'+mpIcon("volume",16)+"</span>"
1650
+ +'<span class="mention-av-badge">'+pSvg(plat,9)+"</span></span>";
1651
+ return '<label class="mention" data-mention-id="'+esc(id)+'">'
1652
+ +'<input type="checkbox" class="mention-pick" data-mid="'+esc(id)+'"/>'
1653
+ +face
1654
+ +'<span class="mention-body">'
1655
+ +'<span class="mention-head">'
1656
+ +'<span class="mention-who">spoken'+(at?" · "+esc(at):"")+"</span>"
1657
+ +(post.postedAt?'<span class="mention-when">'+esc(friendlyTime(post.postedAt))+"</span>":"")
1658
+ +"</span>"
1659
+ +'<span class="mention-text">'+highlightTerm(text,term)+"</span>"
1660
+ +'<span class="mention-note">From the caption track, not inferred. The words are the '
1661
+ +"creator's, in the video"+(at?", at "+esc(at):"")+".</span>"
1662
+ +'<span class="mention-meta">'
1663
+ +(post.views?'<span class="mention-stat">'+mpIcon("eye",12)+esc(fmtNum(post.views))+"</span>":"")
1664
+ +'<span class="chip chip-cat">transcript</span>'
1665
+ +"</span></span></label>";
1666
+ }
1667
+
1213
1668
  /**
1214
1669
  * The monitoring screen, kept in a variable so filtering and sorting redraw
1215
1670
  * without another paid call. A sandboxed view cannot re-query; anything the
@@ -1292,6 +1747,8 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1292
1747
  var GROUP_PEEK=4;
1293
1748
  /** Groups the reader has opened, by post URL, kept across a filter or sort. */
1294
1749
  var expandedGroups={};
1750
+ // Posters the reader has asked to play, keyed like expandedGroups.
1751
+ var expandedMedia={};
1295
1752
 
1296
1753
  /** Reach of the post a comment sits under — how many people saw it. */
1297
1754
  function postReach(post){
@@ -1405,45 +1862,13 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1405
1862
  // not push everything else off the screen. Nothing is dropped: the rest
1406
1863
  // are one click away, and the click costs nothing.
1407
1864
  var key=groupKey(t);
1865
+ var playing=!!expandedMedia[key];
1408
1866
  var open=expandedGroups[key],cut=open?all.length:Math.min(all.length,GROUP_PEEK);
1409
1867
  var mentions=all.slice(0,cut).map(function(m){
1410
- // No invented handle. "@someone" reads as an account you could go and
1411
- // look at, and there is none — the platform withheld it, or the
1412
- // commenter is deleted. An empty handle renders as a stated absence
1413
- // below instead of a fabricated name. (No backticks in here: this
1414
- // whole view is one TypeScript template literal, and one would end it.)
1415
- var hits=Number(m.hits||1),id=String(m.id||"");
1416
- var who=m.username==null?"":String(m.username).trim();
1417
- var pic=String(m.avatarProxyUrl||m.avatarUrl||"");
1418
- var picAlt=String(m.avatarUrl||"");
1419
- // A face and a platform mark, then the words. Reading a feed of
1420
- // strangers is mostly working out who is talking; a column of handles
1421
- // makes you read every one of them to find out.
1422
- var avatar='<span class="mention-av" style="--brand:'+pColor(plat)+'">'
1423
- +'<span class="mention-av-init">'+esc(initialOf(who))+"</span>"
1424
- +(pic?'<img class="mention-av-img" data-avatar src="'+esc(pic)+'"'
1425
- +(picAlt&&picAlt!==pic?' data-fallback="'+esc(picAlt)+'"':"")
1426
- +' alt="" loading="lazy"/>':"")
1427
- +'<span class="mention-av-badge">'+pSvg(plat,9)+"</span></span>";
1428
- return '<label class="mention" data-mention-id="'+esc(id)+'">'
1429
- +'<input type="checkbox" class="mention-pick" data-mid="'+esc(id)+'"/>'
1430
- +avatar
1431
- +'<span class="mention-body">'
1432
- +'<span class="mention-head">'
1433
- +(who?'<span class="mention-who">@'+esc(who)+"</span>"
1434
- :'<span class="mention-who mention-who-unknown">no handle</span>')
1435
- +(m.postedAt?'<span class="mention-when">'+esc(friendlyTime(m.postedAt))+"</span>":"")
1436
- +(hits>1?'<span class="mention-hits" title="names it '+hits+' times">×'+hits+"</span>":"")
1437
- +"</span>"
1438
- +'<span class="mention-text">'+highlightTerm(String(m.text||""),st.term)+"</span>"
1439
- +(m.note?'<span class="mention-note">'+esc(String(m.note))+"</span>":"")
1440
- +'<span class="mention-meta">'
1441
- +'<span class="mention-stat">'+heartIcon()+esc(fmtNum(m.likes||0))+"</span>"
1442
- +'<span class="mention-stat">'+replyIcon()+esc(fmtNum(m.replies||0))+"</span>"
1443
- +labelChips(m)
1444
- +"</span></span></label>";
1868
+ return typedRow(m,plat,st.term,false);
1445
1869
  }).join("");
1446
1870
  return '<div class="mgroup" data-platform="'+esc(plat)+'">'
1871
+ +mgroupMediaHtml(post,key,playing)
1447
1872
  +'<div class="mgroup-head">'
1448
1873
  +'<span class="mgroup-plat" style="--brand:'+pColor(plat)+'">'+pSvg(plat,12)+" "+esc(pTitle(plat))+"</span>"
1449
1874
  +(about?'<span class="mgroup-about">about '+esc(st.term)+"</span>":"")
@@ -1480,6 +1905,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1480
1905
  // Filter and sort redraw from memory rather than re-querying — the view has
1481
1906
  // no way to call the tool again for free, and the user did not ask to spend.
1482
1907
  document.addEventListener("click",function(e){
1908
+ if(feedIsMerged())return;
1483
1909
  var chip=e.target.closest&&e.target.closest(".mchip");
1484
1910
  if(chip&&monitorState){
1485
1911
  monitorState.filter=chip.getAttribute("data-filter")||"";
@@ -1496,7 +1922,20 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1496
1922
  if(rest&&monitorState){
1497
1923
  var k=rest.getAttribute("data-expand")||"";
1498
1924
  expandedGroups[k]=!expandedGroups[k];
1499
- renderMonitor();
1925
+ renderMentions();
1926
+ return;
1927
+ }
1928
+ var mplay=e.target.closest&&e.target.closest("[data-mplay]");
1929
+ if(mplay&&monitorState){
1930
+ var mkey=mplay.getAttribute("data-mplay")||"";
1931
+ if(mkey&&mplay.className.indexOf("is-playable")>=0){
1932
+ expandedMedia[mkey]=true;
1933
+ renderMonitor();
1934
+ // renderMonitor draws outside the top-level render, so the player it
1935
+ // just wrote has nothing bound to it. initPlayers resets first, so
1936
+ // calling it again here is safe.
1937
+ initPlayers();
1938
+ }
1500
1939
  return;
1501
1940
  }
1502
1941
  var all=e.target.closest&&e.target.closest("[data-group-all]");
@@ -1613,10 +2052,16 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1613
2052
  var count=Number(h.matchCount||0);
1614
2053
  var excerpts=(h.excerpts||[]).map(function(ex){
1615
2054
  var text=String(ex.text||"");
2055
+ var exAt=ex.timecode?String(ex.timecode):"";
1616
2056
  return '<div class="quote-box" style="margin:0">'
2057
+ +(exAt?'<span class="quote-at">'+esc(exAt)+"</span>":"")
1617
2058
  +'<span class="mention-text" style="margin-top:0">'+highlightTerm(text,st.term)+"</span>"
1618
2059
  +'<button class="copy-btn" data-copy="'+esc(text)+'">Copy</button></div>';
1619
2060
  }).join("");
2061
+ var spokenAt=null;
2062
+ (h.excerpts||[]).forEach(function(ex){
2063
+ if(!spokenAt&&ex&&ex.timecode)spokenAt={timecode:String(ex.timecode),startMs:ex.startMs};
2064
+ });
1620
2065
  var shownExcerpts=(h.excerpts||[]).length;
1621
2066
  var moreMatches=count-shownExcerpts;
1622
2067
  var meta=[];
@@ -1624,6 +2069,7 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1624
2069
  if(h.language)meta.push('<span class="chip">'+esc(String(h.language))+"</span>");
1625
2070
  if(h.autoGenerated)meta.push('<span class="chip">auto-generated</span>');
1626
2071
  return '<div class="mgroup" data-platform="'+esc(plat)+'">'
2072
+ +(spokenAt?spokenStage(h,spokenAt.startMs):"")
1627
2073
  +'<div class="mgroup-head">'
1628
2074
  +'<span class="mgroup-plat" style="--brand:'+pColor(plat)+'">'+pSvg(plat,12)+" "+esc(pTitle(plat))+"</span>"
1629
2075
  +(reach?'<span class="mgroup-reach">'+esc(reach)+"</span>":"")
@@ -1636,7 +2082,11 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1636
2082
  +(handle?'<span style="color:var(--muted);font-weight:500">@'+esc(handle)+"</span> — ":"")
1637
2083
  +esc(title)+"</div></div></div>"
1638
2084
  +'<div class="mgroup-actions">'
1639
- +(link?'<a class="mgroup-link" href="'+esc(link)+'" target="_blank" rel="noopener">Open ↗</a>':"")
2085
+ +(link
2086
+ ?'<a class="mgroup-link" href="'+esc(linkAtMoment(link,plat,spokenAt&&spokenAt.startMs))
2087
+ +'" target="_blank" rel="noopener">'
2088
+ +(spokenAt?"Open at "+esc(spokenAt.timecode):"Open")+" ↗</a>"
2089
+ :"")
1640
2090
  +'<span class="mention-hits" title="names it '+esc(String(count))+' times">×'+esc(String(count))+"</span>"
1641
2091
  +meta.join("")
1642
2092
  +"</div>"
@@ -1676,6 +2126,288 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1676
2126
  }
1677
2127
  });
1678
2128
 
2129
+ // ─── One sweep, both kinds of mention ───
2130
+ // search_mentions reads what people typed; search_spoken_mentions reads
2131
+ // what was said out loud in the video. They used to draw through two
2132
+ // separate surfaces, and a person monitoring a name does not care which of
2133
+ // the two found it. When both have answered about the same term they merge
2134
+ // here: one feed, one term, one selection, with the source stated per row.
2135
+ //
2136
+ // Neither state is thrown away to build this — the merge is a view over
2137
+ // both, so switching the source back redraws from memory and never
2138
+ // re-queries. Only the transcript half is metered per hit, because only it
2139
+ // costs per transcript, which is why the spoken row says so.
2140
+ var feedSource="both";
2141
+ /** Which sweep answered last, so a lone result still draws its own screen. */
2142
+ var lastMentionKind="";
2143
+
2144
+ /** True once both sweeps have answered about the same name. */
2145
+ function feedIsMerged(){
2146
+ return !!(monitorState&&spokenState&&monitorState.term===spokenState.term
2147
+ &&!monitorState.byCategory);
2148
+ }
2149
+
2150
+ /** The id a spoken excerpt is selected by, namespaced so it cannot collide
2151
+ with a comment id — those are the platform's own. */
2152
+ function spokenId(hit,ex,i){
2153
+ return "spoken:"+String(hit.postId||groupKey(hit))+":"+String(ex.occurrence||i+1);
2154
+ }
2155
+
2156
+ /** Every spoken excerpt on screen, flattened, for selection and hand-off. */
2157
+ function spokenExcerpts(){
2158
+ var out=[];
2159
+ if(!spokenState)return out;
2160
+ spokenState.hits.forEach(function(h){
2161
+ (h.excerpts||[]).forEach(function(ex,i){
2162
+ out.push({id:spokenId(h,ex,i),text:String(ex.text||""),hit:h});
2163
+ });
2164
+ });
2165
+ return out;
2166
+ }
2167
+
2168
+ /** The one network the reader has narrowed to, or "" for all of them. */
2169
+ var feedFilter="";
2170
+
2171
+ /** Both sweeps' findings, keyed by the post they are about. */
2172
+ function mergedGroups(){
2173
+ var order=[],by={};
2174
+ function slot(post){
2175
+ var k=groupKey({post:post});
2176
+ if(!by[k]){
2177
+ by[k]={key:k,post:post||{},typed:[],spoken:[],typedCount:0,postIsAboutTerm:false};
2178
+ order.push(k);
2179
+ }
2180
+ return by[k];
2181
+ }
2182
+ if(feedSource!=="spoken"&&monitorState){
2183
+ monitorState.threads.forEach(function(t){
2184
+ // Same rule the comment-only screen uses: a post with no matching
2185
+ // comment earns its space only when the post itself names the term.
2186
+ if(!(Number(t.mentionCount||0)>0||t.postIsAboutTerm))return;
2187
+ var g=slot(t.post||{});
2188
+ g.typed=g.typed.concat(t.mentions||[]);
2189
+ g.typedCount+=Number(t.mentionCount||0);
2190
+ if(t.postIsAboutTerm)g.postIsAboutTerm=true;
2191
+ });
2192
+ }
2193
+ if(feedSource!=="typed"&&spokenState){
2194
+ spokenState.hits.forEach(function(h){slot(h.post||{}).spoken.push(h);});
2195
+ }
2196
+ var groups=order.map(function(k){return by[k];});
2197
+ if(feedFilter){
2198
+ groups=groups.filter(function(g){return String(g.post.platform||"")===feedFilter;});
2199
+ }
2200
+ // Spoken first: it is the scarcer signal and the reason the two were
2201
+ // merged at all — a name said on camera to 96K viewers is not the same
2202
+ // event as a comment under the video, and burying it under whichever
2203
+ // post happened to collect more replies loses that.
2204
+ return groups.sort(function(a,b){
2205
+ return (b.spoken.length?1:0)-(a.spoken.length?1:0)
2206
+ || (b.spoken.length+b.typedCount)-(a.spoken.length+a.typedCount);
2207
+ });
2208
+ }
2209
+
2210
+ /** Platform counts across both sweeps, for the shared chip row. */
2211
+ function feedCounts(){
2212
+ var counts={};
2213
+ function add(k,n){k=String(k||"");if(k)counts[k]=(counts[k]||0)+n;}
2214
+ if(feedSource!=="spoken"&&monitorState){
2215
+ Object.keys(monitorState.counts||{}).forEach(function(k){
2216
+ add(k,Number(monitorState.counts[k]||0));
2217
+ });
2218
+ }
2219
+ if(feedSource!=="typed"&&spokenState){
2220
+ spokenState.hits.forEach(function(h){add((h.post||{}).platform,1);});
2221
+ }
2222
+ return counts;
2223
+ }
2224
+
2225
+ function renderFeed(){
2226
+ var app=document.getElementById("app");
2227
+ if(!app||!feedIsMerged())return;
2228
+ var term=monitorState.term;
2229
+ var typedTotal=Number(monitorState.total||0);
2230
+ var spokenTotal=Number(spokenState.matched||0);
2231
+ var groups=mergedGroups();
2232
+
2233
+ var counts=feedCounts();
2234
+ var order=Object.keys(counts).sort(function(a,b){return counts[b]-counts[a];});
2235
+ var chips=['<button type="button" class="mchip'+(feedFilter?"":" on")
2236
+ +'" data-feed-filter="">All <b>'
2237
+ +esc(String(feedSource==="typed"?typedTotal:feedSource==="spoken"?spokenTotal:typedTotal+spokenTotal))
2238
+ +"</b></button>"]
2239
+ .concat(order.map(function(k){
2240
+ var n=counts[k];
2241
+ return '<button type="button" class="mchip'+(feedFilter===k?" on":"")+(n?"":" quiet")
2242
+ +'" data-feed-filter="'+esc(k)+'"'+(n?"":" disabled")+' style="--brand:'+pColor(k)+'">'
2243
+ +'<span class="mchip-mark">'+pSvg(k,13)+"</span>"
2244
+ +esc(pTitle(k))+" <b>"+esc(String(n))+"</b></button>";
2245
+ })).join("");
2246
+
2247
+ var total=feedSource==="typed"?esc(String(typedTotal))+" typed"
2248
+ :feedSource==="spoken"?esc(String(spokenTotal))+" spoken"
2249
+ :esc(String(typedTotal))+" typed · "+esc(String(spokenTotal))+" spoken";
2250
+
2251
+ // The two prices are two different things, and rolling them into one
2252
+ // number would hide the only one an argument moves. A comment sweep is
2253
+ // priced per network and always runs; a transcript is priced per video
2254
+ // and only runs on the candidates that survive the cut.
2255
+ var summary=esc(String(spokenState.considered))+" candidate"
2256
+ +(spokenState.considered===1?"":"s")+" considered, "
2257
+ +esc(String(spokenState.transcribed))+" transcribed, "
2258
+ +esc(String(spokenState.transcriptsAvailable))+" had captions, "
2259
+ +esc(String(spokenState.matched))+" matched.";
2260
+
2261
+ var head='<div class="mhead">'
2262
+ +'<div class="mhead-top">'
2263
+ +'<div><div class="mention-term">Mentions of <b>'+esc(term)+"</b> "
2264
+ +(monitorState.since?'<span class="mention-window">since '+esc(monitorState.since)
2265
+ +(monitorState.sinceApplied?"":" · not applied")+"</span>":"")+"</div>"
2266
+ +'<div class="mention-total">'+total+"</div>"
2267
+ +'<div class="mention-summary">'+summary+"</div></div>"
2268
+ +'<div class="msort">'
2269
+ +'<button type="button" class="msort-btn'+(feedSource==="both"?" on":"")+'" data-source="both">Both</button>'
2270
+ +'<button type="button" class="msort-btn'+(feedSource==="typed"?" on":"")+'" data-source="typed">Typed</button>'
2271
+ +'<button type="button" class="msort-btn'+(feedSource==="spoken"?" on":"")+'" data-source="spoken">Spoken</button>'
2272
+ +"</div></div>"
2273
+ +(order.length>1?'<div class="mchips">'+chips+"</div>":"")
2274
+ +(spokenState.ceilingReached
2275
+ ?'<div class="mention-down">Only the '+esc(String(spokenState.maxTranscripts))
2276
+ +' most-viewed candidates were transcribed — '
2277
+ +esc(String(spokenState.considered-spokenState.maxTranscripts))
2278
+ +" more were found and never checked.</div>":"")
2279
+ +"</div>";
2280
+
2281
+ if(!groups.length){
2282
+ // "Nothing was said" and "nothing happened" are different answers, so
2283
+ // the funnel is drawn even on an empty one. A spoken sweep that
2284
+ // transcribed fourteen videos and matched none of them is a real
2285
+ // result; a sweep that never found a candidate to transcribe is not.
2286
+ var icon=feedSource==="spoken"?"🔊":"🔍";
2287
+ var text=feedFilter?"Nothing from "+esc(pTitle(feedFilter))
2288
+ :feedSource==="spoken"?"Nobody said it out loud"
2289
+ :"Nobody has mentioned that yet";
2290
+ var why=feedSource==="spoken"&&typedTotal>0
2291
+ ? '<div class="card card-wide"><div class="card-body">'
2292
+ +'<div class="section-text" style="color:var(--muted)">'+summary
2293
+ +" The "+esc(String(typedTotal))+" typed mention"+(typedTotal===1?" is":"s are")
2294
+ +" still there — switch the source back to <b>Typed</b>.</div></div></div>"
2295
+ : "";
2296
+ app.innerHTML=head+'<div class="empty-state fade-in"><div class="icon">'+icon+"</div>"
2297
+ +'<div class="text">'+text+"</div></div>"+why;
2298
+ setTimeout(reportSize,50);
2299
+ return;
2300
+ }
2301
+
2302
+ var html=groups.map(function(g){
2303
+ var post=g.post,plat=String(post.platform||"");
2304
+ var reach=postReach(post),link=String(post.externalUrl||"");
2305
+ var open=expandedGroups[g.key];
2306
+ var cut=open?g.typed.length:Math.min(g.typed.length,GROUP_PEEK);
2307
+ // The first spoken excerpt is what the group's "open at" link jumps
2308
+ // to — the same moment its top row shows.
2309
+ var firstAt=null;
2310
+ g.spoken.forEach(function(h){
2311
+ (h.excerpts||[]).forEach(function(ex){
2312
+ if(!firstAt&&ex&&ex.timecode)firstAt={timecode:String(ex.timecode),startMs:ex.startMs};
2313
+ });
2314
+ });
2315
+ var stage=firstAt?spokenStage(g.spoken[0],firstAt.startMs):"";
2316
+ var rows=g.spoken.map(function(h){
2317
+ return (h.excerpts||[]).map(function(ex,i){return spokenRow(h,ex,i,term);}).join("");
2318
+ }).join("")
2319
+ +g.typed.slice(0,cut).map(function(m){return typedRow(m,plat,term,true);}).join("");
2320
+ var selectable=g.spoken.reduce(function(n,h){return n+(h.excerpts||[]).length;},0)+g.typed.length;
2321
+ return '<div class="mgroup" data-platform="'+esc(plat)+'">'
2322
+ +stage
2323
+ +'<div class="mgroup-head">'
2324
+ +'<span class="mgroup-plat" style="--brand:'+pColor(plat)+'">'+pSvg(plat,12)+" "+esc(pTitle(plat))+"</span>"
2325
+ +(g.spoken.length?'<span class="mgroup-about">said out loud</span>'
2326
+ :g.postIsAboutTerm?'<span class="mgroup-about">about '+esc(term)+"</span>":"")
2327
+ +(reach?'<span class="mgroup-reach">'+esc(reach)+"</span>":"")
2328
+ +"</div>"
2329
+ +'<div class="mgroup-title">'+esc(String(post.title||post.caption||"").slice(0,140))+"</div>"
2330
+ +'<div class="mgroup-actions">'
2331
+ +(link
2332
+ ?'<a class="mgroup-link" href="'+esc(linkAtMoment(link,plat,firstAt&&firstAt.startMs))
2333
+ +'" target="_blank" rel="noopener">'
2334
+ +(firstAt?"Open at "+esc(firstAt.timecode):"Open")+" ↗</a>"
2335
+ :"")
2336
+ +(selectable?'<button type="button" class="mgroup-all" data-feed-all="'+esc(g.key)
2337
+ +'">Select all '+esc(String(selectable))+"</button>":"")
2338
+ +"</div>"
2339
+ +(rows?'<div class="mentions">'+rows
2340
+ +(g.typed.length>cut
2341
+ ?'<button type="button" class="mgroup-rest" data-expand="'+esc(g.key)+'">'
2342
+ +(open?"Show fewer":"Show "+(g.typed.length-cut)+" more from this post")+"</button>"
2343
+ :"")
2344
+ +"</div>"
2345
+ :'<div class="mgroup-none">The post names '+esc(term)+", but none of its comments do.</div>")
2346
+ +"</div>";
2347
+ }).join("");
2348
+
2349
+ app.innerHTML=head+'<div class="mgroups fade-in">'+html+"</div>"+pickBarHtml();
2350
+ initMentionPicks();
2351
+ applyMentionPicks();
2352
+ setTimeout(reportSize,60);
2353
+ }
2354
+
2355
+ /** Whichever mention screen the data supports — merged when both answered. */
2356
+ function renderMentions(){
2357
+ if(feedIsMerged())return renderFeed();
2358
+ if(lastMentionKind==="spoken")return renderSpoken();
2359
+ return renderMonitor();
2360
+ }
2361
+
2362
+ // The source toggle, the merged feed's platform chips and its select-all.
2363
+ // Distinct data attributes from either single screen's, so a merged view
2364
+ // cannot be driven by a handler that reads the other's state.
2365
+ document.addEventListener("click",function(e){
2366
+ if(!e.target.closest)return;
2367
+ var src=e.target.closest("[data-source]");
2368
+ if(src&&feedIsMerged()){
2369
+ feedSource=src.getAttribute("data-source")||"both";
2370
+ // A filter that survives into a source with nothing on that network is
2371
+ // a dead end the reader did not ask for.
2372
+ if(feedFilter&&!feedCounts()[feedFilter])feedFilter="";
2373
+ renderFeed();
2374
+ return;
2375
+ }
2376
+ var chip=e.target.closest("[data-feed-filter]");
2377
+ if(chip&&feedIsMerged()){
2378
+ if(chip.hasAttribute("disabled"))return;
2379
+ feedFilter=chip.getAttribute("data-feed-filter")||"";
2380
+ renderFeed();
2381
+ return;
2382
+ }
2383
+ var rest=e.target.closest("[data-expand]");
2384
+ if(rest&&feedIsMerged()){
2385
+ var rk=rest.getAttribute("data-expand")||"";
2386
+ expandedGroups[rk]=!expandedGroups[rk];
2387
+ renderFeed();
2388
+ return;
2389
+ }
2390
+ var all=e.target.closest("[data-feed-all]");
2391
+ if(all&&feedIsMerged()){
2392
+ var key=all.getAttribute("data-feed-all")||"";
2393
+ var group=null;
2394
+ mergedGroups().forEach(function(g){if(g.key===key)group=g;});
2395
+ if(!group)return;
2396
+ var ids=[];
2397
+ group.spoken.forEach(function(h){
2398
+ (h.excerpts||[]).forEach(function(ex,i){ids.push(spokenId(h,ex,i));});
2399
+ });
2400
+ group.typed.forEach(function(m){if(m.id)ids.push(String(m.id));});
2401
+ var turnOn=ids.some(function(id){return pickedMentions.indexOf(id)<0;});
2402
+ ids.forEach(function(id){
2403
+ var i=pickedMentions.indexOf(id);
2404
+ if(turnOn&&i<0)pickedMentions.push(id);
2405
+ if(!turnOn&&i>=0)pickedMentions.splice(i,1);
2406
+ });
2407
+ applyMentionPicks();
2408
+ }
2409
+ });
2410
+
1679
2411
  /**
1680
2412
  * Make what is on screen agree with the selection — after a redraw, and
1681
2413
  * after a select-all that reached comments no row is showing.
@@ -1694,6 +2426,21 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1694
2426
  var allOn=ids.length&&ids.every(function(id){return pickedMentions.indexOf(id)>=0;});
1695
2427
  b.textContent=allOn?"Clear these":"Select all "+ids.length;
1696
2428
  });
2429
+ if(feedIsMerged()){
2430
+ var byKey={};
2431
+ mergedGroups().forEach(function(g){byKey[g.key]=g;});
2432
+ document.querySelectorAll("[data-feed-all]").forEach(function(b){
2433
+ var g=byKey[b.getAttribute("data-feed-all")||""];
2434
+ if(!g)return;
2435
+ var ids=[];
2436
+ g.spoken.forEach(function(h){
2437
+ (h.excerpts||[]).forEach(function(ex,i){ids.push(spokenId(h,ex,i));});
2438
+ });
2439
+ g.typed.forEach(function(m){if(m.id)ids.push(String(m.id));});
2440
+ var on=ids.length&&ids.every(function(id){return pickedMentions.indexOf(id)>=0;});
2441
+ b.textContent=on?"Clear these":"Select all "+ids.length;
2442
+ });
2443
+ }
1697
2444
  syncMentionBar();
1698
2445
  }
1699
2446
 
@@ -1733,8 +2480,13 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1733
2480
  var clock=d.toLocaleTimeString([],{hour:"numeric",minute:"2-digit"});
1734
2481
  if(d.toDateString()===now.toDateString())return "today at "+clock;
1735
2482
  if(d.toDateString()===new Date(now.getTime()-day).toDateString())return "yesterday at "+clock;
1736
- var days=Math.floor((now.getTime()-d.getTime())/day);
1737
- if(days>0&&days<7)return days+" days ago";
2483
+ // Whole calendar days, not elapsed milliseconds. Flooring the elapsed
2484
+ // time reads 36 hours as one day, so a comment from the day before
2485
+ // yesterday printed "1 days ago" — wrong by a day and ungrammatical,
2486
+ // because the one-day case is already spelled "yesterday" above.
2487
+ var midnight=function(x){return new Date(x.getFullYear(),x.getMonth(),x.getDate()).getTime();};
2488
+ var days=Math.round((midnight(now)-midnight(d))/day);
2489
+ if(days>1&&days<7)return days+" days ago";
1738
2490
  var date=d.toLocaleDateString([],{month:"short",day:"numeric"});
1739
2491
  return d.getFullYear()===now.getFullYear()?date:date+", "+d.getFullYear();
1740
2492
  }
@@ -1802,7 +2554,14 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1802
2554
  bar.hidden=false;
1803
2555
  if(n)n.textContent=pickedMentions.length;
1804
2556
  var hint=document.getElementById("pickhint");
1805
- if(hint)hint.textContent=pickedMentions.length===1?"1 comment selected":pickedMentions.length+" comments selected";
2557
+ if(hint){
2558
+ var spoken=0;
2559
+ pickedMentions.forEach(function(id){if(id.indexOf("spoken:")===0)spoken++;});
2560
+ var typed=pickedMentions.length-spoken;
2561
+ hint.textContent=spoken
2562
+ ? spoken+" spoken, "+typed+" typed"
2563
+ : pickedMentions.length===1?"1 comment selected":pickedMentions.length+" comments selected";
2564
+ }
1806
2565
  var go=document.getElementById("pickgo");
1807
2566
  if(go){go.disabled=false;go.textContent="Analyse these";}
1808
2567
  }else{
@@ -1834,10 +2593,17 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
1834
2593
  if(pickedMentions.indexOf(String(m.id||""))>=0)urls[u]=true;
1835
2594
  });
1836
2595
  });
2596
+ // A spoken pick is a line from a transcript, not a comment with an id
2597
+ // analyze_comments could re-read. Re-reading its post's comments would
2598
+ // answer a different question from the one that was asked, so say so
2599
+ // rather than quietly analysing something else.
2600
+ var spokenPicks=0;
2601
+ pickedMentions.forEach(function(id){if(id.indexOf("spoken:")===0)spokenPicks++;});
1837
2602
  var distinct=Object.keys(urls);
1838
- if(distinct.length!==1){
2603
+ if(spokenPicks||distinct.length!==1){
1839
2604
  var was=go.textContent;
1840
- go.textContent=distinct.length===0?"Couldn't find that post":"Pick comments from one post only";
2605
+ go.textContent=spokenPicks?"Spoken lines are not comments"
2606
+ :distinct.length===0?"Couldn't find that post":"Pick comments from one post only";
1841
2607
  setTimeout(function(){go.textContent=was;},2200);
1842
2608
  return;
1843
2609
  }
@@ -2041,6 +2807,51 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2041
2807
  +"</div>";
2042
2808
  }
2043
2809
 
2810
+ // What a post can actually show: the playable stream and the still images,
2811
+ // in the precedence postCard has always used. Extracted because the brand
2812
+ // sweep needs the same answer — its threads carried videoUrl, thumbnailUrl
2813
+ // and mediaItems all along and drew none of them, so a monitoring view of
2814
+ // TikTok and Reddit showed comments over a blank header.
2815
+ function resolvePostMedia(p){
2816
+ var video="",images=[];
2817
+ var rawVideo=typeof p.videoUrl==="string"?p.videoUrl:"";
2818
+ if(rawVideo)video=rawVideo;
2819
+ else if(typeof p.videoProxyUrl==="string"&&p.videoProxyUrl)video=p.videoProxyUrl;
2820
+ else if(typeof p.videoFallbackUrl==="string"&&p.videoFallbackUrl
2821
+ &&(p.contentType==="video"||Number(p.duration)>0))video=p.videoFallbackUrl;
2822
+ if(p.mediaItems&&p.mediaItems.length){
2823
+ for(var i=0;i<p.mediaItems.length;i++){
2824
+ var m=p.mediaItems[i];
2825
+ var mu=m.proxy_url||m.preview_url;
2826
+ if(m.kind==="video"&&mu){if(!video)video=mu;}
2827
+ else if(m.kind==="image"&&mu&&images.length<12)images.push(mu);
2828
+ }
2829
+ }
2830
+ return {video:video,images:images,rawVideo:rawVideo};
2831
+ }
2832
+
2833
+ // The poster for a thread in the brand sweep: small, because this is a
2834
+ // scanning feed, and only a picture until it is asked to play.
2835
+ function mgroupMediaHtml(post,key,playing){
2836
+ var m=resolvePostMedia(post);
2837
+ var thumb=String(post.thumbnailUrl||post.thumbnailProxyUrl||m.images[0]||"");
2838
+ if(!thumb&&!m.video&&m.images.length<2)return "";
2839
+ var playable=!!m.video||m.images.length>1;
2840
+ // Once asked for, it draws through the same player every other view uses,
2841
+ // so the controls and the fallback retry are the proven ones.
2842
+ if(playing&&playable){
2843
+ return '<div class="mgroup-stage">'
2844
+ +mediaPlayerHtml(post,m.images,m.video,String(post.platform||""),false)+"</div>";
2845
+ }
2846
+ return '<button type="button" class="mgroup-media'+(playable?" is-playable":"")+'"'
2847
+ +' data-mplay="'+esc(String(key||""))+'"'
2848
+ +' aria-label="'+(playable?"Play this post":"Post image")+'">'
2849
+ +(thumb?'<img class="mgroup-media-img" data-avatar src="'+esc(thumb)+'" alt="" loading="lazy"/>'
2850
+ :'<span class="mgroup-media-blank">'+pSvg(String(post.platform||""),18)+"</span>")
2851
+ +(playable?'<span class="mgroup-media-play">'+mpIcon("play",16)+"</span>":"")
2852
+ +"</button>";
2853
+ }
2854
+
2044
2855
  function postCard(p,wide,pickable,hideActions){
2045
2856
  var platform=p.platform||"unknown",color=pColor(platform),brandSvg=pSvg(platform);
2046
2857
  var tcolor=pColorText(platform);
@@ -2056,35 +2867,19 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2056
2867
  // Prefer video playback over a static thumbnail when the post has one
2057
2868
  // (videoUrl, or a video mediaItem preview_url). Videos are proxied /
2058
2869
  // re-hosted to a permanent nooticr URL so they play inside the CSP.
2059
- var video="",images=[];
2060
2870
  // Play the platform URL directly. Going through /media/proxy did not fix
2061
2871
  // playback in practice, and the direct link is one less hop that can fail.
2062
2872
  // The proxied companion is kept as a fallback: the player retries with it
2063
2873
  // once if the direct URL errors, which covers the CDNs that refuse a
2064
- // cross-origin request from this frame.
2065
- var rawVideo=typeof p.videoUrl==="string"?p.videoUrl:"";
2066
- if(rawVideo)video=rawVideo;
2067
- else if(typeof p.videoProxyUrl==="string"&&p.videoProxyUrl)video=p.videoProxyUrl;
2068
- // Some feeds carry no stream at all - Douyin's search endpoint returns a
2069
- // permalink and a poster and nothing else - but the post detail does. The
2070
- // resolver fetches it on demand, so the card plays instead of sitting
2071
- // there as a still image. Only offered where resolution actually works.
2072
- else if(typeof p.videoFallbackUrl==="string"&&p.videoFallbackUrl
2073
- &&(p.contentType==="video"||Number(p.duration)>0))video=p.videoFallbackUrl;
2874
+ // cross-origin request from this frame. Some feeds carry no stream at all
2875
+ // - Douyin's search endpoint returns a permalink and a poster and nothing
2876
+ // else - but the post detail does, so the resolver fetches it on demand.
2877
+ var resolved=resolvePostMedia(p);
2878
+ var video=resolved.video,images=resolved.images,rawVideo=resolved.rawVideo;
2074
2879
  // The retry target: the resolver re-fetches the post and hands back a
2075
2880
  // fresh link, which is the only thing that helps once the signed URL has
2076
2881
  // expired - a proxy of a dead link is just as dead.
2077
2882
  lastVideoFallback=(rawVideo&&(p.videoFallbackUrl||p.videoProxyUrl))||"";
2078
- if(p.mediaItems&&p.mediaItems.length){
2079
- for(var i=0;i<p.mediaItems.length;i++){
2080
- var m=p.mediaItems[i];
2081
- // "proxy_url" is the same asset served through Nooticr — the raw
2082
- // preview_url is a signed CDN link that this frame can't load.
2083
- var mu=m.proxy_url||m.preview_url;
2084
- if(m.kind==="video"&&mu){if(!video)video=mu;}
2085
- else if(m.kind==="image"&&mu&&images.length<12)images.push(mu);
2086
- }
2087
- }
2088
2883
  var mediaHtml="";
2089
2884
  var vh=wide?"340px":"200px";
2090
2885
  var bodyText=p.caption||p.text||"";
@@ -2461,6 +3256,277 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2461
3256
  try{renderView(result);}finally{initPlayers();setTimeout(reportSize,80);}
2462
3257
  }
2463
3258
 
3259
+ // ─── Own account ───
3260
+
3261
+ /** yes / no / unknown, as the chip that says which. */
3262
+ // Three-valued, and the third value is the whole point: an absent scope
3263
+ // means nobody recorded it, which is not the same answer as "no". Drawing
3264
+ // it red would turn "we do not know" into "you cannot", which is the exact
3265
+ // refusal the grant never made.
3266
+ function capChip(label,value){
3267
+ var v=String(value==null?"unknown":value).toLowerCase();
3268
+ var cls=v==="yes"?"chip-positive":v==="no"?"chip-negative":"chip-mixed";
3269
+ return '<span class="chip '+cls+'">'+esc(label)+" · "+esc(v)+"</span>";
3270
+ }
3271
+
3272
+ /** The account row's face: the handle's initial under the network's mark. */
3273
+ function connAvatar(name,plat,dashed){
3274
+ if(dashed){
3275
+ return '<span class="conn-av conn-av-empty" style="--brand:'+pColor(plat)+'">'
3276
+ +'<span class="conn-av-mark">'+pSvg(plat,14)+"</span></span>";
3277
+ }
3278
+ return '<span class="conn-av" style="--brand:'+pColor(plat)+'">'
3279
+ +'<span class="mention-av-init" style="font-size:12px">'+esc(initialOf(name))+"</span>"
3280
+ +'<span class="mention-av-badge">'+pSvg(plat,9)+"</span></span>";
3281
+ }
3282
+
3283
+ /**
3284
+ * list_social_connections — the grant, stated per connection.
3285
+ *
3286
+ * The gap worth stating on this screen is that reading a network and
3287
+ * connecting an account are different sets, and the difference surprises
3288
+ * people: nooticr sweeps nine networks publicly and only a couple of them
3289
+ * can be linked to an account at all. So the connectable ones get a row
3290
+ * with a button and the rest get named as read-only, rather than being left
3291
+ * out and read as an omission.
3292
+ */
3293
+ function renderConnections(d){
3294
+ var conns=Array.isArray(d.connections)?d.connections.filter(Boolean):[];
3295
+ var connectable=(Array.isArray(d.connectable)?d.connectable:[]).map(String);
3296
+ var linked={};
3297
+ conns.forEach(function(c){linked[String(c.platform||"")]=true;});
3298
+
3299
+ var rows=conns.map(function(c){
3300
+ var plat=String(c.platform||"");
3301
+ var who=String(c.accountName||"").replace(/^@/,"");
3302
+ var status=String(c.status||"active").toLowerCase();
3303
+ var statusCls=status==="active"?"chip-positive":status==="expired"?"chip-mixed":"chip-negative";
3304
+ var owner=[pTitle(plat),String(c.influencerName||c.appName||"")].filter(Boolean).join(" · ");
3305
+ return '<div class="conn-row">'
3306
+ +connAvatar(who,plat,false)
3307
+ +'<div class="conn-body">'
3308
+ +'<div class="conn-who">'+(who?"@"+esc(who):esc(pTitle(plat)+" account"))+"</div>"
3309
+ +'<div class="conn-sub">'+esc(owner)+"</div>"
3310
+ +'<div class="chiprow" style="margin-top:6px">'
3311
+ +capChip("read",c.canReadAccount)+capChip("publish",c.canPublishPost)
3312
+ +capChip("comments",c.canManageComments)+"</div>"
3313
+ // Said once per connection that needs it, rather than once at the
3314
+ // bottom where it would be read as being about all of them.
3315
+ +(c.scopeRecorded===false
3316
+ ?'<div class="conn-note">Unknown means the grant predates scope recording — try it, '
3317
+ +"it is not a refusal.</div>":"")
3318
+ +"</div>"
3319
+ +'<span class="chip '+statusCls+' conn-status">'+esc(status)+"</span>"
3320
+ +"</div>";
3321
+ }).join("");
3322
+
3323
+ var open=connectable.filter(function(k){return !linked[k];}).map(function(plat){
3324
+ return '<div class="conn-row">'
3325
+ +connAvatar("",plat,true)
3326
+ +'<div class="conn-body">'
3327
+ +'<div class="conn-who">'+esc(pTitle(plat))+"</div>"
3328
+ +'<div class="conn-sub">not connected · can be linked</div>'
3329
+ +"</div>"
3330
+ +'<button type="button" class="ai-btn conn-connect" data-connect="'+esc(plat)+'">Connect</button>'
3331
+ +"</div>";
3332
+ }).join("");
3333
+
3334
+ // Everything the sweep can read that no account can be linked to. Derived
3335
+ // from what the payload says is connectable rather than hard-coded, so a
3336
+ // network that becomes linkable stops being listed here on its own.
3337
+ var readOnly=ALL_PLATFORMS.filter(function(k){return connectable.indexOf(k)<0;});
3338
+ var readOnlyTags=readOnly.map(function(k){
3339
+ return '<span class="tag">'+esc(pTitle(k))+"</span>";
3340
+ }).join("");
3341
+
3342
+ var n=conns.length;
3343
+ return '<div class="fade-in">'
3344
+ +'<div class="own-title">Connected accounts</div>'
3345
+ +'<div class="own-sub">'+esc(String(n))+" connected · "+esc(String(connectable.length))
3346
+ +" platform"+(connectable.length===1?"":"s")+" can be linked. Free to call, and nothing "
3347
+ +"here spends credits.</div>"
3348
+ +(rows||open
3349
+ ?'<div class="card card-wide"><div class="card-body" style="padding:4px 14px 12px">'+rows+open+"</div></div>"
3350
+ :'<div class="empty-state"><div class="icon">🔌</div>'
3351
+ +'<div class="text">No accounts connected yet</div></div>')
3352
+ +'<div class="ai-note">Each press mints a fresh link — an old one does not work twice. '
3353
+ +"Approve in your own browser; nothing is connected until you do.</div>"
3354
+ +(readOnlyTags
3355
+ ?'<div class="card card-wide" style="margin-top:12px"><div class="card-body">'
3356
+ +'<div class="sec-label">Read-only — nooticr can sweep these, no account to link</div>'
3357
+ +'<div class="chiprow">'+readOnlyTags+"</div>"
3358
+ +'<div class="section-text" style="margin-top:12px;color:var(--muted)">So "monitor my '
3359
+ +'brand on Weibo" works and "reply to that Weibo comment" cannot — no nooticr '
3360
+ +"connection carries comment-write permission on any network.</div>"
3361
+ +"</div></div>"
3362
+ :"")
3363
+ +"</div>";
3364
+ }
3365
+
3366
+ /**
3367
+ * connect_social_account — the hand-off, and nothing more.
3368
+ *
3369
+ * The tool mints a link; the user approves at the provider. Nothing here
3370
+ * pretends to own the session, so the screen's whole job is to state that
3371
+ * and hand over the link.
3372
+ */
3373
+ function renderConnectLink(d){
3374
+ var plat=String(d.platform||"");
3375
+ var url=String(d.connectUrl||"");
3376
+ return '<div class="fade-in own-connect">'
3377
+ +mkSvg(96,55,true)
3378
+ +'<div class="own-title" style="margin-top:14px">Waiting for you</div>'
3379
+ +'<div class="own-sub" style="max-width:34ch;margin-inline:auto">'
3380
+ +esc(String(d.message||"Open this link and approve the connection there. Nothing is "
3381
+ +"connected until you do."))+"</div>"
3382
+ +(url?'<a class="btn" href="'+esc(url)+'" target="_blank" rel="noopener">Open '
3383
+ +esc(pTitle(plat))+"'s consent screen ↗</a>":"")
3384
+ +'<div class="ai-note">This link is fresh and single-use. When you have approved it, ask '
3385
+ +"for your connections again to see what the grant actually covers.</div>"
3386
+ +"</div>";
3387
+ }
3388
+
3389
+ /**
3390
+ * create_product / update_product — the row that was written.
3391
+ *
3392
+ * Not the form the design drew. This view is mounted by a tool result, so
3393
+ * by the time it renders the row already exists; a form here could only
3394
+ * offer to create a second one. What it shows instead is the design's own
3395
+ * "created" state — what was written, and the two things that can now
3396
+ * happen — and, for a patch, which fields actually moved.
3397
+ */
3398
+ function renderProduct(d){
3399
+ // Flat, as product_summary sends it; a {product:...} wrapper is accepted
3400
+ // too so a later shape change does not blank the screen.
3401
+ var pr=(d.product&&typeof d.product==="object")?d.product:d;
3402
+ // The payload does not say which call produced it - create and update
3403
+ // both return the same row - but the view does: every tool is served at
3404
+ // its own resource URI with its name baked in.
3405
+ var created=bakedTool()!=="update_product";
3406
+ var patched=(Array.isArray(d.patched)?d.patched:[]).map(String);
3407
+ var name=String(pr.name||"Product");
3408
+ var slug=String(pr.slug||"");
3409
+ var type=String(pr.productType||"");
3410
+ var idLine=[pr.appId!=null?"appId "+esc(String(pr.appId)):"",
3411
+ slug?'<span class="mono">'+esc(slug)+"</span>":"",
3412
+ type?esc(type):""].filter(Boolean).join(" · ");
3413
+
3414
+ // Which fields moved, and which were left alone on purpose. An omitted
3415
+ // field keeping its value is the tool's contract, not an accident, so the
3416
+ // patch says so rather than leaving the reader to infer it from silence.
3417
+ var FIELD_LABELS={name:"Name",slug:"Slug",description:"Description",
3418
+ website_url:"Website",niche:"Niche",product_type:"Type",icon_url:"Icon",
3419
+ primary_cta_label:"CTA label",primary_cta_url:"CTA link",
3420
+ external_listing_id:"Listing id",ios_bundle_id:"iOS",android_package:"Android"};
3421
+ var VALUE_OF={name:"name",slug:"slug",description:"description",
3422
+ website_url:"websiteUrl",niche:"niche",product_type:"productType",
3423
+ icon_url:"iconUrl",primary_cta_label:"primaryCtaLabel",
3424
+ primary_cta_url:"primaryCtaUrl",external_listing_id:"externalListingId",
3425
+ ios_bundle_id:"iosBundleId",android_package:"androidPackage"};
3426
+ var steps=patched.map(function(k){
3427
+ var v=pr[VALUE_OF[k]||k];
3428
+ return '<div class="step-row"><span class="step-k">'+esc(FIELD_LABELS[k]||k)+"</span>"
3429
+ +'<span class="step-v">'+esc(v==null?"—":String(v))
3430
+ +' <span class="chip chip-mixed">patched</span></span></div>';
3431
+ }).join("");
3432
+
3433
+ // What the row actually holds. The server returns the whole product
3434
+ // rather than a list of which columns moved, so the honest thing to draw
3435
+ // for a patch is its current state rather than an invented diff.
3436
+ var FIELD_ORDER=["websiteUrl","niche","productType","primaryCtaLabel",
3437
+ "primaryCtaUrl","iosBundleId","androidPackage","description"];
3438
+ var FIELD_TITLES={websiteUrl:"Website",niche:"Niche",productType:"Type",
3439
+ primaryCtaLabel:"CTA label",primaryCtaUrl:"CTA link",iosBundleId:"iOS",
3440
+ androidPackage:"Android",description:"Description"};
3441
+ function currentFields(row){
3442
+ return FIELD_ORDER.filter(function(k){return row[k]!=null&&row[k]!=="";})
3443
+ .map(function(k){
3444
+ return '<div class="step-row"><span class="step-k">'+esc(FIELD_TITLES[k])+"</span>"
3445
+ +'<span class="step-v">'+esc(String(row[k]))+"</span></div>";
3446
+ }).join("");
3447
+ }
3448
+
3449
+ var head=created
3450
+ ? '<div class="own-head"><span class="own-tick">'+mpIcon("check",18)+"</span>"
3451
+ +"<span>"+esc(name)+"</span></div>"
3452
+ : '<div class="own-head"><span>'+esc(name)+"</span>"
3453
+ +(pr.appId!=null?'<span class="chip chip-cat" style="margin-left:auto">appId '
3454
+ +esc(String(pr.appId))+"</span>":"")+"</div>";
3455
+
3456
+ return '<div class="fade-in"><div class="card card-wide"><div class="card-body">'
3457
+ +head
3458
+ +'<div class="own-sub">'+idLine+"</div>"
3459
+ +(created
3460
+ ?'<div class="lede-box">'+esc(String(d.note
3461
+ ||"The row exists and every other own-account tool can reach it. It has no brand "
3462
+ +"playbook yet — that is a separate call, and it is the one that costs."))+"</div>"
3463
+ :(steps||currentFields(pr)
3464
+ ?'<div class="steps-list">'+(steps||currentFields(pr))+"</div>"
3465
+ +'<div class="ai-note">The row as it now stands. An omitted field was left alone '
3466
+ +"rather than blanked — and a field you sent that is not here was not recognised, "
3467
+ +"since these are read by exact snake_case key.</div>"
3468
+ :'<div class="lede-box">The row is saved. Nothing else is set on it yet.</div>'))
3469
+ +'<div class="chiprow" style="margin-top:12px">'
3470
+ +'<span class="tag">'+(pr.hasBrandPlaybook?"playbook: set":"playbook: none")+"</span>"
3471
+ +(pr.websiteUrl?'<span class="tag">'+esc(String(pr.websiteUrl))+"</span>":"")
3472
+ +"</div>"
3473
+ // Both buttons the design drew. analyze_product is a real tool now, and
3474
+ // it is the call that writes the playbook the row above says it lacks -
3475
+ // billed against the workspace's plan AI credits rather than the
3476
+ // personal MCP balance, which is why the pill carries no number.
3477
+ +'<div class="ai-actions">'
3478
+ +(pr.hasBrandPlaybook?"":'<button type="button" class="ai-btn" data-analyze-product="1">'
3479
+ +'<span class="spark">'+mpIcon("sparkle",14)+"</span>Analyse the product"
3480
+ +'<span class="price">plan AI credits</span></button>')
3481
+ +'<button type="button" class="ai-btn" data-list-connections="1">Connect an account'
3482
+ +'<span class="price">free</span></button></div>'
3483
+ +'<div class="ai-note">analyze_product runs in the background and returns a jobId - poll '
3484
+ +"it with analyze_product_status, which is free because the spend already happened.</div>"
3485
+ +"</div></div></div>";
3486
+ }
3487
+
3488
+ // Connecting is the one action on these screens, and it is a hand-off: the
3489
+ // tool mints a link, the view swaps itself for the screen that shows it, and
3490
+ // the user approves in their own browser. A host that cannot run the call
3491
+ // in-band says so rather than leaving a dead button.
3492
+ function ownActionButton(btn,tool,args,busyLabel){
3493
+ if(btn.disabled)return;
3494
+ var was=btn.innerHTML;
3495
+ btn.disabled=true;
3496
+ btn.textContent=busyLabel;
3497
+ var restore=function(label){
3498
+ btn.disabled=false;
3499
+ if(label){
3500
+ btn.textContent=label;
3501
+ setTimeout(function(){btn.innerHTML=was;},1800);
3502
+ }else{
3503
+ btn.innerHTML=was;
3504
+ }
3505
+ };
3506
+ invokeTool(tool,args,function(r){
3507
+ // A host that also pushes a tool-result notification renders the same
3508
+ // payload again; renderToolResult keeps the bookkeeping in step so the
3509
+ // second pass is skipped. If it answered with nothing we can draw, put
3510
+ // the button back rather than leaving it spinning.
3511
+ if(!renderToolResult(r))restore("");
3512
+ },function(){restore("Ask in chat");});
3513
+ }
3514
+
3515
+ document.addEventListener("click",function(e){
3516
+ if(!e.target.closest)return;
3517
+ var c=e.target.closest("[data-connect]");
3518
+ if(c){
3519
+ var plat=c.getAttribute("data-connect")||"";
3520
+ if(!plat)return;
3521
+ ownActionButton(c,"connect_social_account",{platform:plat},"Opening…");
3522
+ return;
3523
+ }
3524
+ var l=e.target.closest("[data-list-connections]");
3525
+ if(l){ownActionButton(l,"list_social_connections",{},"Loading…");return;}
3526
+ var an=e.target.closest("[data-analyze-product]");
3527
+ if(an)ownActionButton(an,"analyze_product",{},"Starting…");
3528
+ });
3529
+
2464
3530
  function renderView(result){
2465
3531
  var app=document.getElementById("app");if(!app)return;
2466
3532
  var d=extractResult(result);
@@ -2468,6 +3534,18 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2468
3534
  // Raw HTML-only result — inject it directly.
2469
3535
  if(d._html){app.innerHTML=d._html;setTimeout(reportSize,50);return;}
2470
3536
 
3537
+ // Own account. Checked before the feeds below because all three shapes are
3538
+ // distinctive and none of them carries posts, threads or an analysis.
3539
+ if(Array.isArray(d.connections)||Array.isArray(d.connectable)&&d.connectedCount!==undefined){
3540
+ app.innerHTML=renderConnections(d);setTimeout(reportSize,50);return;}
3541
+ if(d.connectUrl!==undefined){
3542
+ app.innerHTML=renderConnectLink(d);setTimeout(reportSize,50);return;}
3543
+ // product_summary is flat: {appId, name, slug, productType, ...}. An
3544
+ // earlier version of this looked for a d.product wrapper the server never
3545
+ // sends, so a real create_product fell through to the raw-JSON branch.
3546
+ if(d.appId!==undefined&&d.slug!==undefined){
3547
+ app.innerHTML=renderProduct(d);setTimeout(reportSize,50);return;}
3548
+
2471
3549
  // Analysis
2472
3550
  if(d.analysis&&(d.post||d.url)){app.innerHTML=analysisCard(d);return;}
2473
3551
  // Post comparison
@@ -2539,8 +3617,16 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2539
3617
  };
2540
3618
  // A new term addresses different comments, so a selection made against
2541
3619
  // the old one is stale rather than carried over.
2542
- if(monitorTerm!==monitorState.term){monitorTerm=monitorState.term;pickedMentions=[];}
2543
- renderMonitor();
3620
+ if(monitorTerm!==monitorState.term){
3621
+ monitorTerm=monitorState.term;
3622
+ pickedMentions=[];
3623
+ // A new name is a different question: the other sweep's answer is
3624
+ // about the old one and must not merge into this.
3625
+ if(spokenState&&spokenState.term!==monitorState.term)spokenState=null;
3626
+ feedSource="both";feedFilter="";
3627
+ }
3628
+ lastMentionKind="typed";
3629
+ renderMentions();
2544
3630
  return;
2545
3631
  }
2546
3632
 
@@ -2562,7 +3648,14 @@ export const NOOTICR_UI_TEMPLATE = `<!DOCTYPE html>
2562
3648
  filter:"",
2563
3649
  sort:"loud",
2564
3650
  };
2565
- renderSpoken();
3651
+ if(monitorState&&monitorState.term!==spokenState.term){
3652
+ monitorState=null;
3653
+ pickedMentions=[];
3654
+ }
3655
+ if(monitorTerm!==spokenState.term){monitorTerm=spokenState.term;pickedMentions=[];}
3656
+ feedSource="both";feedFilter="";
3657
+ lastMentionKind="spoken";
3658
+ renderMentions();
2566
3659
  return;
2567
3660
  }
2568
3661