@fish-under-sea/magpie-neartoken 0.4.1 → 0.5.0

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.
package/index.mjs CHANGED
@@ -163,27 +163,32 @@ async function catalog(disabledGroups = new Set(), disabledModels = new Set()) {
163
163
  }
164
164
  const groups = all.filter((g) => !disabledGroups.has(String(g ?? '').trim()))
165
165
  if (!groups.length) continue
166
- for (const raw of groups) {
167
- const group = String(raw ?? '').trim()
168
- const ref = group && refs[group] ? String(refs[group]) : ''
169
- const ratio = ratioOf(ratios, group)
170
- const id = ref ? `${ref}::${model}` : model
171
- const label = group ? (ratio > 0 ? `${group} ${ratioText(ratio)}x` : group) + ' · ' + model : model
172
- out[id] = {
173
- name: label,
174
- api: { id: model, url: SITE + '/v1', npm: '@ai-sdk/openai-compatible' },
175
- limit: {
176
- context: Number(row?.context_window) > 0 ? Number(row.context_window) : 0,
177
- output: Number(row?.max_tokens) > 0 ? Number(row.max_tokens) : 0,
178
- },
179
- ...(ref ? { headers: { 'x-neartoken-group': ref } } : {}),
180
- // 站点自己的分类,交给 magpie 的四个分区(图像/视频/语音/向量)
181
- ...(kind === 'chat' ? {} : { kind }),
182
- capabilities: {
183
- toolcall: true,
184
- output: kindOutput(kind),
185
- },
186
- }
166
+ // 一个模型只出一行:多分组不再展开成多行,否则「只启用一个模型」在
167
+ // 供应商列表里仍会冒出五六行同名条目。分组与倍率折进显示名里,
168
+ // 请求按该模型最便宜(倍率最低)的分组走。
169
+ const ranked = groups.map((g) => ({
170
+ name: String(g ?? '').trim(),
171
+ ref: String(g ?? '').trim() && refs[String(g ?? '').trim()] ? String(refs[String(g ?? '').trim()]) : '',
172
+ ratio: ratioOf(ratios, String(g ?? '').trim()),
173
+ })).sort((a, b) => (a.ratio || 1e9) - (b.ratio || 1e9))
174
+ const main = ranked[0] || { name: '', ref: '', ratio: 0 }
175
+ const label = main.name
176
+ ? (main.ratio > 0 ? `${main.name} ${ratioText(main.ratio)}x` : main.name) + ' · ' + model
177
+ : model
178
+ out[model] = {
179
+ name: label,
180
+ api: { id: model, url: SITE + '/v1', npm: '@ai-sdk/openai-compatible' },
181
+ limit: {
182
+ context: Number(row?.context_window) > 0 ? Number(row.context_window) : 0,
183
+ output: Number(row?.max_tokens) > 0 ? Number(row.max_tokens) : 0,
184
+ },
185
+ ...(main.ref ? { headers: { 'x-neartoken-group': main.ref } } : {}),
186
+ // 站点自己的分类,交给 magpie 的四个分区(图像/视频/语音/向量)
187
+ ...(kind === 'chat' ? {} : { kind }),
188
+ capabilities: {
189
+ toolcall: true,
190
+ output: kindOutput(kind),
191
+ },
187
192
  }
188
193
  }
189
194
  writeGroups([...groupsOut.values()].map((g) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fish-under-sea/magpie-neartoken",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "NearToken for magpie: a provider whose sign-in is the site's own device authorization, models listed per billing group with their multiplier, balance and vouchers on magpie's usage card, plus a page for spending and top-ups.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",
package/page/app.js CHANGED
@@ -84,6 +84,8 @@
84
84
 
85
85
  /** showError 把宿主的话放到页脚的一条提示上,不弹窗。 */
86
86
  let lastError = ''
87
+ let notice = ''
88
+ function showNotice(text) { notice = text; lastError = ''; render() }
87
89
  function showError(error) {
88
90
  const said = String(error && error.message ? error.message : error)
89
91
  // options 写入限流由 saveDisabled 自己合并重试,不用挂到页脚
@@ -197,13 +199,14 @@
197
199
  if (tab === 'market') frag.append(marketCard())
198
200
  else if (tab === 'logs') frag.append(logsCard())
199
201
  else frag.append(el('div', { class: 'home-grid' },
200
- accountCard(), boardCard(), topupCard(), providerCard()))
202
+ accountCard(), boardCard(), topupCard(), providerCard(), referralCard()))
201
203
  } else {
202
204
  setStatus('未连接')
203
205
  frag.append(connectCard())
204
206
  }
205
207
  if (view.error) frag.append(el('p', { class: 'note bad', text: view.error }))
206
208
  if (lastError) frag.append(el('p', { class: 'note bad', text: lastError }))
209
+ if (notice) frag.append(el('p', { class: 'note', text: notice }))
207
210
  if (pendingAct) frag.append(el('p', { class: 'note', text: '正在处理:' + pendingAct }))
208
211
  body.replaceChildren(frag)
209
212
  if (view.pending) paintCountdown()
@@ -213,8 +216,9 @@
213
216
 
214
217
  /** equalizeHome 量第一张卡(账户)的高度,锁到同组所有卡上。 */
215
218
  function equalizeHome() {
216
- const cards = document.querySelectorAll('.home-grid > .card')
219
+ const cards = [...document.querySelectorAll('.home-grid > .card:not(.home-wide)')]
217
220
  if (cards.length < 2) return
221
+ cards.forEach((c) => { c.style.height = '' })
218
222
  const h = Math.round(cards[0].getBoundingClientRect().height)
219
223
  if (!h) return
220
224
  cards.forEach((c) => { c.style.height = h + 'px' })
@@ -290,6 +294,23 @@
290
294
  el('p', { class: 'note', id: 'copy-hint', text: '' }))
291
295
  }
292
296
 
297
+ /** 推荐计划:站点「复制推荐链接」那一栏的长条卡,跨整行放最下面。 */
298
+ function referralCard() {
299
+ const aff = view.affCode || view.referralCode || ''
300
+ const url = aff ? 'https://neartoken.cc/sign-up?aff=' + encodeURIComponent(aff)
301
+ : 'https://neartoken.cc/console'
302
+ const invites = Number(view.invites) || 0
303
+ return el('section', { class: 'card home-wide' },
304
+ el('div', { class: 'rf' },
305
+ el('div', { class: 'rf-ico', text: '◈' }),
306
+ el('div', { class: 'rf-text' },
307
+ el('h2', { text: '推荐计划', style: 'margin:0' }),
308
+ el('p', { class: 'note', text: '用户通过您的推荐链接注册后,您即可获得奖励。可随时将累计奖励转入余额。', style: 'margin:2px 0 0' })),
309
+ el('div', { class: 'rf-invites' }, el('div', { class: 'k', text: '邀请' }), el('div', { class: 'v', text: String(invites) })),
310
+ el('div', { class: 'rf-link' }, el('span', { text: url })),
311
+ el('button', { class: 'rf-copy', title: '复制推荐链接', onclick: () => copy(url) }, '⧉')))
312
+ }
313
+
293
314
  /** 附件:账户、余额、代金券。 */
294
315
  function accountCard() {
295
316
  const account = view.account || {}
@@ -360,47 +381,72 @@
360
381
  return card(null, ...parts)
361
382
  }
362
383
 
363
- /** 充值卡:档位、渠道、去支付。 */
384
+ /**
385
+ * 充值卡:站点「添加资金」页的缩小版。
386
+ * 金额大卡(带优惠角标)、自定义金额 + 待支付、支付方式图标卡、兑换码。
387
+ */
364
388
  function topupCard() {
365
389
  const info = topupInfo
366
- if (!info) return card('充值', el('p', { class: 'note', text: '正在读取充值档位…' }))
367
- if (info.unavailable) return card('充值', el('p', { class: 'note', text: info.unavailable }))
390
+ if (!info) return card(null, el('p', { class: 'note', text: '正在读取充值档位…' }))
391
+ if (info.unavailable) return card(null, el('p', { class: 'note', text: info.unavailable }))
368
392
  const options = Array.isArray(info.options) && info.options.length ? info.options : []
369
- const amount = pick.amount
370
- const method = pick.method || (info.methods[0] && info.methods[0].type) || ''
371
- const parts = [
372
- el('div', { class: 'topup' },
373
- el('div', { class: 'row wrap' },
374
- options.map((value) => el('button', {
375
- 'aria-pressed': String(value === amount),
376
- onclick: () => { pick.amount = value; pick.custom = ''; render() },
377
- }, String(value))),
378
- el('label', { class: 'field' },
379
- el('span', { class: 'cur', text: info.currency || '¥' }),
380
- el('input', {
381
- type: 'text',
382
- inputmode: 'numeric',
383
- placeholder: '其他',
384
- value: pick.custom,
385
- 'aria-label': '自定义金额',
386
- oninput: (e) => { pick.custom = e.target.value.replace(/[^\d]/g, ''); pick.amount = null },
387
- }))),
388
- el('div', { class: 'row' },
389
- info.methods.length > 1
390
- ? el('select', {
391
- 'aria-label': '收款方式',
392
- onchange: (e) => { pick.method = e.target.value; render() },
393
- }, info.methods.map((m) => el('option', { value: m.type, selected: m.type === method }, m.name)))
394
- : el('span', { class: 'note', text: info.methods.length ? info.methods[0].name : '' }),
395
- el('span', { class: 'grow' }),
396
- el('button', {
397
- class: 'primary',
398
- disabled: !!pendingAct,
399
- onclick: () => pay(method),
400
- }, '去支付'))),
401
- el('p', { class: 'note', text: '付完 5 分钟内余额会自动刷新;订单与发票在站点的钱包页。' }),
402
- ]
403
- return card('充值', ...parts)
393
+ const methods = Array.isArray(info.methods) ? info.methods : []
394
+ const cur = info.currency || '¥'
395
+ const amount = pick.amount || Number(pick.custom || 0)
396
+ const method = pick.method || (methods[0] && methods[0].type) || ''
397
+ const payable = (v) => v >= 500 ? Math.round(v * 0.96) : v >= 200 ? Math.round(v * 0.98) : v
398
+ const saved = (v) => v - payable(v)
399
+ const head = el('div', { class: 'tp-head' },
400
+ el('div', { class: 'tp-badge', text: '¥' }),
401
+ el('div', { class: 'grow' },
402
+ el('h2', { text: '添加资金', style: 'margin:0' }),
403
+ el('p', { class: 'note', text: '选择金额和支付方式', style: 'margin:2px 0 0' })),
404
+ el('a', { class: 'ghost link', href: 'https://neartoken.cc/wallet', target: '_blank', rel: 'noreferrer' }, '订单历史 ↗'),
405
+ el('button', { class: 'primary', disabled: !!pendingAct || !amount, onclick: () => pay(method) }, '去支付'))
406
+ const tiles = el('div', { class: 'tp-tiles' }, options.map((v) => {
407
+ const on = v === amount && !pick.custom
408
+ const discount = v >= 500 ? '优惠 4%' : v >= 200 ? '优惠 2%' : ''
409
+ return el('button', {
410
+ class: 'tp-tile' + (on ? ' on' : ''), 'aria-pressed': String(on),
411
+ onclick: () => { pick.amount = v; pick.custom = ''; render() },
412
+ },
413
+ el('b', { text: String(v) }),
414
+ el('span', { class: 'tp-pay', text: discount ? cur + payable(v) + ' · 节省 ' + saved(v) : cur + v }),
415
+ discount ? el('span', { class: 'tp-off', text: discount }) : null)
416
+ }))
417
+ const custom = el('div', { class: 'tp-custom' },
418
+ el('label', { class: 'tp-label', text: '自定义金额' }),
419
+ el('div', { class: 'tp-custom-row' },
420
+ el('label', { class: 'field tp-field' },
421
+ el('span', { class: 'cur', text: cur }),
422
+ el('input', {
423
+ type: 'text', inputmode: 'numeric', placeholder: '输入金额', value: pick.custom,
424
+ 'aria-label': '自定义金额',
425
+ oninput: (e) => { pick.custom = e.target.value.replace(/[^\d]/g, ''); pick.amount = null; render() },
426
+ })),
427
+ el('div', { class: 'tp-due' }, el('span', { text: '待支付金额:' }), el('b', { text: cur + (amount || 0) }))))
428
+ const payMethods = el('div', null,
429
+ el('div', { class: 'tp-label', text: '付款方式' }),
430
+ el('div', { class: 'tp-methods' }, methods.map((m) => {
431
+ const on = m.type === method
432
+ return el('button', {
433
+ class: 'tp-method' + (on ? ' on' : ''), 'aria-pressed': String(on),
434
+ onclick: () => { pick.method = m.type; render() },
435
+ },
436
+ el('span', { class: 'tp-micon ' + (m.type === 'alipay' ? 'ali' : 'wx'), text: m.type === 'alipay' ? '支' : '微' }),
437
+ el('b', { text: m.name }))
438
+ })),
439
+ el('p', { class: 'note', text: '其他支付方式请联系站长', style: 'margin:6px 0 0' }))
440
+ const redeem = el('div', { class: 'tp-redeem' },
441
+ el('label', { class: 'tp-label', text: '🎁 有兑换码吗?' }),
442
+ el('div', { class: 'tp-custom-row' },
443
+ el('input', {
444
+ class: 'tp-input', type: 'text', placeholder: '输入您的兑换码',
445
+ oninput: (e) => { pick.redeem = e.target.value },
446
+ }),
447
+ el('button', { class: 'ghost', onclick: () => showNotice('兑换额度需要宿主提供兑换端点,可在站点钱包页兑换。') }, '兑换额度')))
448
+ const foot = el('p', { class: 'note', text: '付完 5 分钟内余额会自动刷新;订单与发票在站点的钱包页。' })
449
+ return card(null, head, tiles, custom, payMethods, redeem, foot)
404
450
  }
405
451
 
406
452
  /**
@@ -617,10 +663,29 @@
617
663
  el('a', { class: 'link', href: 'https://neartoken.cc/pricing', target: '_blank', rel: 'noreferrer' }, '详情 ›')))
618
664
  }
619
665
 
620
- /** 站点 /pricing 首屏的推荐顺序(抓自它的服务端渲染),默认排序时置顶。 */
621
- const SITE_FIRST = ['gpt-6-astra', 'gpt-6.1-sol', 'gpt-6-sol', 'gpt-6.1-terra', 'gpt-6-luna',
622
- 'claude-fable-5-1', 'claude-fable-5', 'claude-opus-5-5', 'claude-opus-5',
623
- 'claude-sonnet-5-5', 'claude-sonnet-5', 'glm-5.3']
666
+ /**
667
+ * 模型按系列归类:DeepSeek → GLM → Kimi → Qwen → GPT → Claude → Gemini
668
+ * → Grok → 生图 → 视频 → 语音 → 向量 → 其他。同系列挨在一起。
669
+ */
670
+ const SERIES = [
671
+ ['DeepSeek', /deepseek|ds-|kimseek/i],
672
+ ['GLM', /glm|智谱/i],
673
+ ['Kimi', /kimi|moonshot/i],
674
+ ['Qwen', /qwen|通义/i],
675
+ ['GPT', /gpt-|o[134]-|codex|sora/i],
676
+ ['Claude', /claude/i],
677
+ ['Gemini', /gemini/i],
678
+ ['Grok', /grok/i],
679
+ ['生图', /banana|seedream|flux|image|ideogram|z-image|qwen-image|dall/i],
680
+ ['视频', /veo|sd2|omni|seedance|minimax-h3|video|kling|sora/i],
681
+ ['语音', /tts|asr|suno|whisper|speech|lyrics|sound/i],
682
+ ['向量', /embedding|embed/i],
683
+ ]
684
+ function modelSeriesOf(name) {
685
+ const n = String(name || '').toLowerCase()
686
+ const i = SERIES.findIndex(([, re]) => re.test(n))
687
+ return i === -1 ? SERIES.length : i
688
+ }
624
689
 
625
690
  /** filteredModels 把搜索、分组、类型、厂商筛选叠起来。 */
626
691
  function filteredModels() {
@@ -628,12 +693,8 @@
628
693
  if (!p) return []
629
694
  let list = p.models.slice()
630
695
  if (marketSort !== 'latest') {
631
- // 默认按站点首屏顺序置顶,其余保持目录原序
632
- list.sort((a, b) => {
633
- const ia = SITE_FIRST.indexOf(a.name), ib = SITE_FIRST.indexOf(b.name)
634
- if (ia !== -1 || ib !== -1) return (ia === -1 ? 999 : ia) - (ib === -1 ? 999 : ib)
635
- return 0
636
- })
696
+ // 默认按系列归类(ds → glm → kimi → …),同系列内按名字排
697
+ list.sort((a, b) => modelSeriesOf(a.name) - modelSeriesOf(b.name) || a.name.localeCompare(b.name))
637
698
  }
638
699
  if (marketGroup) list = list.filter((m) => (m.enable_groups || []).includes(marketGroup))
639
700
  if (marketKind !== 'all') list = list.filter((m) => marketKindOf(m) === marketKind)
@@ -661,10 +722,13 @@
661
722
  const p = pricing()
662
723
  // 先按功能分栏,栏内按首标签(满血 → 官Key → 低价 → …)再按倍率排
663
724
  const FUNC_ORDER = ['对话', '生图', '视频', '语音', '向量']
725
+ // 分组栏:低价分组整体排上面(是列表里的上下位置,不是标签内前后权重),
726
+ // 其余按倍率升序。标签内部仍按「功能 → 满血/官Key → 低价」重绘。
727
+ const isLow = (g) => /低价/i.test(g)
664
728
  const rankOf = (g) => {
665
729
  const t = groupTags(g)
666
- return (FUNC_ORDER.indexOf(t.func) === -1 ? 99 : FUNC_ORDER.indexOf(t.func)) * 1000
667
- + leadRank(t.lead) * 10 + (Number(p.groups[g]) || 0)
730
+ const f = FUNC_ORDER.indexOf(t.func)
731
+ return (isLow(g) ? 0 : 1) * 10000 + (f === -1 ? 99 : f) * 100 + (Number(p.groups[g]) || 0)
668
732
  }
669
733
  const groups = Object.keys(p?.groups || {}).sort((a, b) => rankOf(a) - rankOf(b) || a.localeCompare(b, 'zh'))
670
734
  const vendors = (p?.vendors || []).slice().sort((a, b) => String(a.name).localeCompare(String(b.name), 'zh'))
@@ -696,27 +760,33 @@
696
760
  el('div', { class: 'market-seg' },
697
761
  el('button', { 'aria-pressed': String(marketLayout === 'grid'), title: '网格', onclick: () => { marketLayout = 'grid'; refreshMarketList() } }, '▦'),
698
762
  el('button', { 'aria-pressed': String(marketLayout === 'list'), title: '列表', onclick: () => { marketLayout = 'list'; refreshMarketList() } }, '☰')))
699
- // 分组按两级排:先功能(生图/视频/语音/向量/对话),再价格倍率
763
+ // 分组栏两级:低价分组整体排最上面,其余按功能分栏
764
+ const lowGroups = groups.filter(isLow)
765
+ const otherGroups = groups.filter((g) => !isLow(g))
700
766
  const byFunc = new Map()
701
- for (const g of groups) {
767
+ for (const g of otherGroups) {
702
768
  const f = groupTags(g).func
703
769
  if (!byFunc.has(f)) byFunc.set(f, [])
704
770
  byFunc.get(f).push(g)
705
771
  }
772
+ const chipOf = (g) => {
773
+ const ratio = Number(p.groups[g]) || 0
774
+ return el('button', {
775
+ class: 'market-chip' + (marketGroup === g ? ' on' : ''),
776
+ title: '按这个分组筛选',
777
+ onclick: () => { marketGroup = marketGroup === g ? '' : g; render() },
778
+ }, groupBadge(g), ratio > 0 ? el('span', { class: 'ratio', text: 'x' + ratioText(ratio) }) : null)
779
+ }
706
780
  const groupChips = el('div', { class: 'market-chips' },
707
781
  el('button', {
708
782
  class: 'market-chip' + (!marketGroup ? ' on' : ''), onclick: () => { marketGroup = ''; render() },
709
783
  }, '所有分组'),
784
+ lowGroups.length ? el('div', { class: 'chip-group' },
785
+ el('div', { class: 'chip-group-name low', text: '低价' }),
786
+ el('div', { class: 'market-chips' }, lowGroups.map(chipOf))) : null,
710
787
  FUNC_ORDER.filter((f) => byFunc.has(f)).map((f) => el('div', { class: 'chip-group' },
711
788
  el('div', { class: 'chip-group-name', text: f }),
712
- el('div', { class: 'market-chips' }, byFunc.get(f).map((g) => {
713
- const ratio = Number(p.groups[g]) || 0
714
- return el('button', {
715
- class: 'market-chip' + (marketGroup === g ? ' on' : ''),
716
- title: '按这个分组筛选',
717
- onclick: () => { marketGroup = marketGroup === g ? '' : g; render() },
718
- }, groupBadge(g), ratio > 0 ? el('span', { class: 'ratio', text: 'x' + ratioText(ratio) }) : null)
719
- })))))
789
+ el('div', { class: 'market-chips' }, byFunc.get(f).map(chipOf)))))
720
790
  const kinds = el('div', { class: 'market-chips' },
721
791
  MARKET_KINDS.map(([k, label]) => el('button', {
722
792
  class: 'market-chip' + (marketKind === k ? ' on' : ''),
package/page/market.css CHANGED
@@ -100,8 +100,52 @@
100
100
  .gb-tag.lead { font-weight: 700; opacity: 1; box-shadow: 0 0 0 1px rgba(255,255,255,.32); }
101
101
  .gb-tag.purple { background: #7b52b8; }
102
102
  .gb-tag.teal { background: #2f8f8a; }
103
+ .chip-group-name.low { color: #4cc38a; font-weight: 700; font-size: 11px; }
103
104
  .chip-group { width: 100%; margin: 2px 0 4px; }
104
105
  .chip-group-name { font-size: 10px; color: var(--nt-faint); margin: 6px 0 3px; letter-spacing: .04em; }
106
+
107
+ /* 充值卡:站点「添加资金」缩小版 */
108
+ .tp-head { display: flex; align-items: center; gap: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--nt-line); margin-bottom: 8px; }
109
+ .tp-badge { width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; background: rgba(76,195,138,.14); color: #4cc38a; font-size: 15px; font-weight: 700; }
110
+ .tp-head h2 { font-size: 14px; }
111
+ .tp-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
112
+ .tp-tile {
113
+ position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
114
+ min-height: 48px; padding: 6px 9px; text-align: left;
115
+ border: 1px solid var(--nt-line); border-radius: 9px; background: rgba(255,255,255,.02); color: var(--nt-ink);
116
+ }
117
+ .tp-tile b { font-size: 16px; font-variant-numeric: tabular-nums; }
118
+ .tp-tile .tp-pay { font-size: 9.5px; color: var(--nt-muted); }
119
+ .tp-tile .tp-off { position: absolute; right: 6px; top: 6px; font-size: 9px; color: #4cc38a; }
120
+ .tp-tile[aria-pressed="true"] { border-color: var(--nt-accent); background: var(--nt-accent-soft); box-shadow: inset 0 0 0 1px var(--nt-accent); }
121
+ .tp-label { display: block; font-size: 10px; color: var(--nt-muted); margin: 8px 0 4px; }
122
+ .tp-custom-row { display: flex; align-items: center; gap: 8px; }
123
+ .tp-field { height: 32px; }
124
+ .tp-field input { width: 100%; font-size: 12px; }
125
+ .tp-due { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 8px; height: 32px; padding: 0 9px; border: 1px solid var(--nt-line); border-radius: 9px; font-size: 10.5px; color: var(--nt-muted); }
126
+ .tp-due b { color: var(--nt-ink); font-family: var(--nt-code); font-size: 12px; }
127
+ .tp-methods { display: flex; gap: 8px; flex-wrap: wrap; }
128
+ .tp-method { display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 12px; border: 1px solid var(--nt-line); border-radius: 9px; background: rgba(255,255,255,.02); color: var(--nt-ink); font-size: 11.5px; }
129
+ .tp-method[aria-pressed="true"] { border-color: var(--nt-accent); background: var(--nt-accent-soft); }
130
+ .tp-micon { width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; color: #fff; font-size: 10px; font-weight: 700; }
131
+ .tp-micon.ali { background: #1677ff; }
132
+ .tp-micon.wx { background: #07c160; }
133
+ .tp-redeem { border-top: 1px solid var(--nt-line); margin-top: 8px; padding-top: 2px; }
134
+ .tp-input { flex: 1; height: 32px; padding: 0 9px; border: 1px solid var(--nt-line); border-radius: 9px; background: rgba(0,0,0,.2); color: var(--nt-ink); font-size: 11.5px; }
135
+ .tp-redeem .ghost { height: 32px; }
136
+
137
+ /* 推荐计划长条卡 */
138
+ .rf { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
139
+ .rf-ico { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; background: rgba(155,124,224,.16); color: #9b7ce0; font-size: 16px; }
140
+ .rf-text { flex: 1 1 240px; }
141
+ .rf-text h2 { font-size: 13px; }
142
+ .rf-invites { text-align: center; min-width: 54px; }
143
+ .rf-invites .k { font-size: 10px; color: var(--nt-muted); }
144
+ .rf-invites .v { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; }
145
+ .rf-link { flex: 1 1 260px; min-width: 0; display: flex; align-items: center; height: 36px; padding: 0 12px; border: 1px solid var(--nt-line); border-radius: 10px; background: rgba(0,0,0,.2); overflow: hidden; }
146
+ .rf-link span { font-family: var(--nt-code); font-size: 11.5px; color: var(--nt-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
147
+ .rf-copy { width: 36px; height: 36px; border: 1px solid var(--nt-accent); border-radius: 10px; background: var(--nt-accent-soft); color: var(--nt-accent); font-size: 15px; }
148
+ @media (max-width: 620px) { .tp-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
105
149
  .mc-image { border-color: rgba(139,79,158,.5); }
106
150
  .mc-video { border-color: rgba(214,140,60,.5); }
107
151
  .mc-audio { border-color: rgba(63,111,168,.5); }
package/page/style.css CHANGED
@@ -46,6 +46,11 @@ html, body {
46
46
  内容超出时卡片内部滚动,不把整页撑长。 */
47
47
  .home-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: start; }
48
48
  .home-grid > .card { margin: 0; overflow-y: auto; scrollbar-width: thin; }
49
+ .home-grid > .card.home-wide {
50
+ grid-column: 1 / -1;
51
+ overflow: visible; /* 长条卡不参与等高锁死 */
52
+ height: auto !important;
53
+ }
49
54
  .home-grid > .card::-webkit-scrollbar { width: 6px; }
50
55
  .home-grid > .card::-webkit-scrollbar-thumb { background: var(--nt-line); border-radius: 3px; }
51
56
  @media (max-width: 720px) { .home-grid { grid-template-columns: 1fr; } .home-grid > .card { height: auto !important; } }
package/page/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // 由 scripts/stamp.mjs 生成,别手改:版本号在 package.json。
2
- self.NEARTOKEN_VERSION = "0.4.1"
2
+ self.NEARTOKEN_VERSION = "0.5.0"