跳转到内容
打开/关闭菜单
  • 487 条目
  • 873 文件
  • 22 用户
  • 9665 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块:System map:修订间差异

来自Blockland Wiki
创建页面,内容为“-- 模块:SystemMapNavbox -- Blockland Wiki: horizontal stellar-system map, traditional Navbox data input. -- Self-contained: only Scribunto, TemplateStyles and the site's existing Navbox are needed. local p = {} local function trim(v) local s = tostring(v or '') s = s:gsub('^%s+', '') s = s:gsub('%s+$','') return s end local function arg(args, key, alt) local v=trim(args[key]) if v == '' and alt then v=trim(args[alt]) end return v…”
 
无编辑摘要
 
(未显示同一用户的1个中间版本)
第1行: 第1行:
-- 模块:SystemMapNavbox
-- 模块:System map
-- Blockland Wiki: horizontal stellar-system map, traditional Navbox data input.
-- Blockland Wiki: horizontal stellar-system map, traditional Navbox data input.
-- Self-contained: only Scribunto, TemplateStyles and the site's existing Navbox are needed.
-- Self-contained: only Scribunto, TemplateStyles and the site's existing Navbox are needed.
第102行: 第102行:
     if name=='' then return nil end
     if name=='' then return nil end
     return {name=name,page=arg(args,b..'链接'),english=arg(args,b..'英文'),
     return {name=name,page=arg(args,b..'链接'),english=arg(args,b..'英文'),
         icon=arg(args,b..'图'),radiusEarth=number(arg(args,b..'半径'),nil),
         icon=arg(args,b..'图'),rotate=number(arg(args,b..'图旋转'),0),radiusEarth=number(arg(args,b..'半径'),nil),
         diameterKm=number(arg(args,b..'直径km'),nil),color=tint(arg(args,b..'颜色'),'#bac3cc'),
         diameterKm=number(arg(args,b..'直径km'),nil),color=tint(arg(args,b..'颜色'),'#bac3cc'),
         designation=arg(args,b..'编号')}
         designation=arg(args,b..'编号')}
end
end
local function append(parts,s) parts[#parts+1]=s end
local function append(parts,s) parts[#parts+1]=s end
-- Moons and companion stars appear as vertical linked rows, as in the reference System map.
-- The orbital rail is positioned independently from the Navbox cell intrinsic width.
local function renderChild(ch, title)
-- All moon glyphs sit on the same vertical descendant line as their parent disc centre.
local function rotationStyle(deg)
    local n=number(deg,0)
    if n == 0 then return '' end
    return ' style="transform:rotate('..string.format('%.2f',clamp(n,-180,180))..'deg)"'
end
local function renderChild(ch,title)
     local current=ch.page~='' and (title==ch.page or title=='模板:'..ch.page)
     local current=ch.page~='' and (title==ch.page or title=='模板:'..ch.page)
     local d=discSize(ch.type=='star' and 'star' or 'moon',ch.radiusEarth,ch.radiusSolar,ch.diameterKm)
     local d=math.min(discSize(ch.type=='star' and 'star' or 'moon',ch.radiusEarth,ch.radiusSolar,ch.diameterKm),24)
    d=math.min(d,24)
    local parts={}
     local tip=ch.name
     local tip=ch.name
     if ch.english~='' then tip=tip..' · '..ch.english end
     if ch.english~='' then tip=tip..' · '..ch.english end
     append(parts,'<span class="blsm-child'..(current and ' blsm-current' or '')..'" title="'..html(tip)..'">')
     local parts={'<div class="blsm-child'..(current and ' blsm-current' or '')..'" title="'..html(tip)..'">',
    append(parts,'<span class="blsm-child-glyph">')
        '<span class="blsm-child-glyph"><span class="blsm-child-art"'..rotationStyle(ch.rotate)..'>'}
     if ch.icon~='' then
     if ch.icon~='' then
         append(parts,imageWiki(ch.icon,d))
         append(parts,imageWiki(ch.icon,d))
第122行: 第126行:
         append(parts,'<span class="blsm-child-disc" style="width:'..math.floor(d)..'px;height:'..math.floor(d)..'px;background-color:'..ch.color..'"></span>')
         append(parts,'<span class="blsm-child-disc" style="width:'..math.floor(d)..'px;height:'..math.floor(d)..'px;background-color:'..ch.color..'"></span>')
     end
     end
     append(parts,'</span><span class="blsm-child-name">'..wikiLink(ch.page~='' and ch.page or ch.name,ch.name)..'</span>')
     append(parts,'</span></span><span class="blsm-child-name">'..wikiLink(ch.page~='' and ch.page or ch.name,ch.name)..'</span>')
     if ch.designation~='' then append(parts,'<span class="blsm-child-code">'..html(ch.designation)..'</span>') end
     if ch.designation~='' then append(parts,'<span class="blsm-child-code">'..html(ch.designation)..'</span>') end
     append(parts,'</span>')
     append(parts,'</div>')
     return table.concat(parts)
     return table.concat(parts)
end
end
local function renderRing(name, page, current)
local function renderRing(name,page,current)
     local s='<span class="blsm-child blsm-child--ring'..(current and ' blsm-current' or '')..'" title="行星环">'
     return '<div class="blsm-child blsm-child--ring'..(current and ' blsm-current' or '')..'" title="行星环">'
         ..'<span class="blsm-child-glyph"><span class="blsm-ring-band"></span></span>'
         ..'<span class="blsm-child-glyph"><span class="blsm-ring-band"></span></span>'
         ..'<span class="blsm-child-name">'..wikiLink(page~='' and page or name,name)..'</span></span>'
         ..'<span class="blsm-child-name">'..wikiLink(page~='' and page or name,name)..'</span></div>'
    return s
end
end
local function renderDisc(o, title)
local function renderDisc(o)
     local size=discSize(o.type,o.radiusEarth,o.radiusSolar,o.diameterKm)
     local size=discSize(o.type,o.radiusEarth,o.radiusSolar,o.diameterKm)
    local parts={}
     if o.type=='belt' or o.type=='debris' then
     if o.type=='belt' or o.type=='debris' then
         append(parts,'<span class="blsm-belt-band" title="轨道碎片与小天体带"></span>')
         return '<span class="blsm-belt-band" title="小天体带"></span>'
     elseif o.icon~='' then
     end
         append(parts,'<span class="blsm-image" style="min-height:'..math.ceil(size)..'px">'..imageWiki(o.icon,size)..'</span>')
    if o.icon~='' then
    else
         return '<span class="blsm-image"'..rotationStyle(o.rotate)..'>'..imageWiki(o.icon,size)..'</span>'
        local c=tint(o.color,colors[o.visual] or colors.unknown)
        append(parts,'<span class="blsm-disc blsm-disc--'..o.visual..'" style="width:'..math.floor(size)..'px;height:'..math.floor(size)..'px;background-color:'..c..'"></span>')
     end
     end
     return table.concat(parts)
    local c=tint(o.color,colors[o.visual] or colors.unknown)
     return '<span class="blsm-disc blsm-disc--'..o.visual..'" style="width:'..math.floor(size)..'px;height:'..math.floor(size)..'px;background-color:'..c..'"></span>'
end
end
local function renderBody(o, title, opts)
local function renderBody(o,title,opts)
     local parts={}
     local parts={}
     local isBelt=(o.type=='belt' or o.type=='debris')
     local isBelt=o.type=='belt' or o.type=='debris'
     local ringShown=opts.showRings and o.ring~=''
     local ringShown=opts.showRings and o.ring~=''
     local children=#o.moons+(ringShown and 1 or 0)
     local childCount=#o.moons+(ringShown and 1 or 0)
     local width=isBelt and 98 or (o.type=='star' and 118 or (children>0 and 148 or 116))
     local width=isBelt and 175 or (o.type=='star' and 196 or (childCount>0 and 226 or 190))
     local active=(title==o.page or (opts.highlight~='' and (opts.highlight==o.page or opts.highlight==o.name)))
     local active=(title==o.page or (opts.highlight~='' and (opts.highlight==o.page or opts.highlight==o.name)))
     local meta={o.name}
     local meta={o.name}
     if o.english~='' then meta[#meta+1]=o.english end
     if o.english~='' then append(meta,o.english) end
     if o.orbit~='' then meta[#meta+1]='轨道: '..o.orbit end
     if o.orbit~='' then append(meta,'轨道: '..o.orbit) end
     if o.radiusEarth then meta[#meta+1]='半径: '..tostring(o.radiusEarth)..' R⊕' end
     if o.radiusEarth then append(meta,'半径: '..tostring(o.radiusEarth)..' R⊕') end
     if o.radiusSolar then meta[#meta+1]='半径: '..tostring(o.radiusSolar)..' R☉' end
     if o.radiusSolar then append(meta,'半径: '..tostring(o.radiusSolar)..' R☉') end
     local cls=isBelt and 'blsm-slot--belt' or 'blsm-slot--body'
     local cls=isBelt and 'blsm-slot--belt' or 'blsm-slot--body'
     if o.type=='star' then cls=cls..' blsm-slot--star' end
     if o.type=='star' then cls=cls..' blsm-slot--star' end
     if active then cls=cls..' blsm-current' end
     if active then cls=cls..' blsm-current' end
     append(parts,'<div class="blsm-slot '..cls..'" style="width:'..width..'px" title="'..html(table.concat(meta,' · '))..'">')
     append(parts,'<div class="blsm-slot '..cls..'" style="width:'..width..'px" title="'..html(table.concat(meta,' · '))..'">')
    append(parts,'<div class="blsm-designation">'..html(o.designation)..'</div>')
     append(parts,'<div class="blsm-glyph">'..renderDisc(o)..'</div>')
     append(parts,'<div class="blsm-glyph">'..renderDisc(o,title)..'</div>')
     append(parts,'<div class="blsm-name">'..wikiLink(o.page,o.name)..'</div>')
     append(parts,'<div class="blsm-name">'..wikiLink(o.page,o.name)..'</div>')
     if opts.details then
    local subtitle=''
         append(parts,'<div class="blsm-caption">'..html(isBelt and o.orbit or (o.english~='' and o.english or o.orbit))..'</div>')
     if isBelt then
    else
        subtitle=o.orbit~='' and o.orbit or o.english
         append(parts,'<div class="blsm-caption blsm-caption--hidden"></div>')
    elseif o.type=='star' then
        subtitle=o.english
    elseif o.designation~='' then
        subtitle=(opts.numberPrefix~='' and (opts.numberPrefix..' ') or '')..o.designation
    elseif o.english~='' then
         subtitle=o.english
    end
    append(parts,'<div class="blsm-caption">'..html(subtitle)..'</div>')
    if opts.details and o.english~='' and o.type~='star' and not isBelt then
         append(parts,'<div class="blsm-extra">'..html(o.english)..'</div>')
     end
     end
     if children>0 then
     if childCount>0 then
         append(parts,'<div class="blsm-children" aria-label="'..html(o.name)..'的卫星">')
         append(parts,'<div class="blsm-children">')
         if ringShown then append(parts,renderRing(o.ring,o.ringPage,title==o.ringPage)) end
         if ringShown then append(parts,renderRing(o.ring,o.ringPage,title==o.ringPage)) end
         for _,ch in ipairs(o.moons) do append(parts,renderChild(ch,title)) end
         for _,ch in ipairs(o.moons) do append(parts,renderChild(ch,title)) end
第185行: 第194行:
     return {name=name,english=arg(args,base..'英文'),page=arg(args,base..'链接')~='' and arg(args,base..'链接') or name,
     return {name=name,english=arg(args,base..'英文'),page=arg(args,base..'链接')~='' and arg(args,base..'链接') or name,
       type='star',visual='star',designation=arg(args,base..'序号')~='' and arg(args,base..'序号') or '★',
       type='star',visual='star',designation=arg(args,base..'序号')~='' and arg(args,base..'序号') or '★',
       icon=arg(args,base..'图'),color=arg(args,base..'颜色'),orbit='',
       icon=arg(args,base..'图'),rotate=number(arg(args,base..'图旋转'),0),color=arg(args,base..'颜色'),orbit='',
       radiusSolar=number(arg(args,base..'半径'),nil),diameterKm=number(arg(args,base..'直径km'),nil),
       radiusSolar=number(arg(args,base..'半径'),nil),diameterKm=number(arg(args,base..'直径km'),nil),
       moons={},ring='',ringPage=''}
       moons={},ring='',ringPage=''}
第202行: 第211行:
     if num=='' and ty~='belt' and ty~='debris' then num=roman(planetIndex) end
     if num=='' and ty~='belt' and ty~='debris' then num=roman(planetIndex) end
     local o={name=name,page=fld('链接')~='' and fld('链接') or name,english=fld('英文'),type=ty,visual=visual,
     local o={name=name,page=fld('链接')~='' and fld('链接') or name,english=fld('英文'),type=ty,visual=visual,
       designation=num,icon=fld('图'),color=fld('颜色'),orbit=fld('轨道'),
       designation=num,icon=fld('图'),rotate=number(fld('图旋转'),0),color=fld('颜色'),orbit=fld('轨道'),
       radiusEarth=radiusEarth,diameterKm=diameterKm,moons={},ring=fld('环'),ringPage=fld('环链接')}
       radiusEarth=radiusEarth,diameterKm=diameterKm,moons={},ring=fld('环'),ringPage=fld('环链接')}
     for j=1,16 do
     for j=1,16 do
第216行: 第225行:
     args=args or {}
     args=args or {}
     local title=trim(currentTitle)
     local title=trim(currentTitle)
     local opts={details=arg(args,'显示详情')=='1' or arg(args,'显示详情')=='yes',highlight=arg(args,'highlight','高亮'),showRings=arg(args,'显示行星环')=='1' or arg(args,'显示行星环')=='yes'}
     local opts={details=arg(args,'显示详情')=='1' or arg(args,'显示详情')=='yes',highlight=arg(args,'highlight','高亮'),showRings=arg(args,'显示行星环')=='1' or arg(args,'显示行星环')=='yes',numberPrefix=arg(args,'编号前缀')}
     local bodies={}
     local bodies={}
     local primary=makeStar(args,1)
     local primary=makeStar(args,1)
第235行: 第244行:
     end
     end
     if #bodies==0 then return '<span class="blsm-empty">没有配置天体。</span>' end
     if #bodies==0 then return '<span class="blsm-empty">没有配置天体。</span>' end
     local parts={'<div class="blsm-map"><div class="blsm-scroll" tabindex="0" role="region" aria-label="星系轨道与天体导航">',
    local maxChildren=0
        '<div class="blsm-rail">'}
    for _,o in ipairs(bodies) do
        local c=#o.moons+(opts.showRings and o.ring~='' and 1 or 0)
        if c>maxChildren then maxChildren=c end
    end
    local viewportHeight=math.min(460,math.max(166,148+maxChildren*30+(opts.details and 15 or 0)))
    -- Crucial: the scroll container is position:absolute inside a width-constrained
    -- relative viewport. Its max-content orbital rail does NOT participate in the
    -- intrinsic sizing of the Navbox table cell (Vector 2022 page overflow fix).
     local parts={'<div class="blsm-map"><div class="blsm-viewport" style="height:'..viewportHeight..'px">',
        '<div class="blsm-scroll" tabindex="0" role="region" aria-label="星系轨道与天体导航"><div class="blsm-rail">'}
     for _,o in ipairs(bodies) do append(parts,renderBody(o,title,opts)) end
     for _,o in ipairs(bodies) do append(parts,renderBody(o,title,opts)) end
     append(parts,'</div></div>')
     append(parts,'</div></div></div></div>')
    local countBelt=0
    for _,o in ipairs(bodies) do if o.type=='belt' or o.type=='debris' then countBelt=countBelt+1 end end
    append(parts,'<div class="blsm-foot">按轨道顺序排列 · 天体直径对数缩放 · 轨道间距不代表真实比例')
    if countBelt>0 then append(parts,' · 小天体带按其轨道位置单列') end
    append(parts,'</div></div>')
     return table.concat(parts)
     return table.concat(parts)
end
end

2026年10月31日 (六) 12:43的最新版本

此模块的文档可以在模块:System map/doc创建

-- 模块:System map
-- Blockland Wiki: horizontal stellar-system map, traditional Navbox data input.
-- Self-contained: only Scribunto, TemplateStyles and the site's existing Navbox are needed.
local p = {}

local function trim(v)
    local s = tostring(v or '')
    s = s:gsub('^%s+', '')
    s = s:gsub('%s+$','')
    return s
end
local function arg(args, key, alt)
    local v=trim(args[key])
    if v == '' and alt then v=trim(args[alt]) end
    return v
end
local function html(v)
    return trim(v):gsub('&','&amp;'):gsub('<','&lt;'):gsub('>','&gt;'):gsub('"','&quot;')
end
local function wikiLink(page,label)
    page=trim(page);label=trim(label)
    if label=='' then label=page end
    page=page:gsub('[%[%]|{}<>\r\n]','')
    label=label:gsub('[%[%]|{}<>\r\n]','')
    if page=='' then return html(label) end
    return '[['..page..'|'..label..']]'
end
local function imageWiki(file,size)
    file=trim(file):gsub('^[Ff]ile:', ''):gsub('^文件:', '')
    if file=='' or file:find('[%[%]|{}<>\r\n]') then return '' end
    return '[[File:'..file..'|x'..math.floor(size+0.5)..'px|link=|alt=]]'
end
local function number(v,default)
    local n=tonumber(trim(v))
    if not n or n ~= n or math.abs(n)>1e15 then return default end
    return n
end
local function clamp(v,a,b) return math.max(a,math.min(b,v)) end
local function roman(n)
    local vs={1000,900,500,400,100,90,50,40,10,9,5,4,1}
    local ss={'M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'}
    local s=''
    for i,v in ipairs(vs) do while n>=v do n=n-v;s=s..ss[i] end end
    return s
end
local function kind(v)
    v=trim(v):lower()
    if v=='带' or v=='小行星带' or v=='belt' or v=='asteroid' or v=='cluster' then return 'belt' end
    if v=='碎片' or v=='碎片带' or v=='debris' then return 'debris' end
    if v=='ring' or v=='环' then return 'ring' end
    if v=='star' or v=='恒星' then return 'star' end
    if v=='dwarf' or v=='矮行星' then return 'dwarf' end
    return 'planet'
end
local function tint(v, fallback)
    v=trim(v)
    if v:match('^#%x%x%x%x%x%x$') then return v end
    return fallback
end
local colors={
    star='#f0c775', rock='#b67653', lava='#e16f2e', desert='#b69a79',
    ocean='#2e83ab', habitable='#2475ac', gas='#b9a9a1',
    ['gas-blue']='#87c7e0', ['gas-purple']='#8868b5',
    ['gas-orange']='#d4a279', ice='#8ec3cc', ['ice-dark']='#566c80',
    dwarf='#d2dadd', moon='#b8b8b1', unknown='#a2a9b1'
}
local function styleKind(ty,classification)
    local c=trim(classification):lower()
    if ty=='star' then return 'star' end
    if ty=='dwarf' then return 'dwarf' end
    if c=='lava' or c=='熔岩' then return 'lava' end
    if c=='desert' or c=='荒漠' then return 'desert' end
    if c=='ocean' or c=='海洋' then return 'ocean' end
    if c=='habitable' or c=='宜居' then return 'habitable' end
    if c=='gas' or c=='气态' then return 'gas' end
    if c=='gas-blue' or c=='蓝色气态' then return 'gas-blue' end
    if c=='gas-purple' or c=='紫色气态' then return 'gas-purple' end
    if c=='gas-orange' or c=='橙色气态' then return 'gas-orange' end
    if c=='ice' or c=='冰质' then return 'ice' end
    if c=='ice-dark' or c=='暗冰' then return 'ice-dark' end
    if c=='rock' or c=='岩质' then return 'rock' end
    return 'rock'
end
-- Fixed scale across systems, inspired by logarithmic tier scaling rather than AU scale.
-- Values are visual conventions, not strict physical size ratios.
local function discSize(tier, radiusEarth, radiusSolar, diameterKm)
    if tier=='star' then
        local r=radiusSolar or (diameterKm and diameterKm/1392700) or 1
        return clamp(52+11*math.log(clamp(r,0.03,25))/math.log(3.5),36,66)
    elseif tier=='moon' then
        local r=radiusEarth or (diameterKm and diameterKm/12742) or .18
        return clamp(8+9*math.log(clamp(r,.02,1.2)/.02)/math.log(1.2/.02),8,20)
    elseif tier=='belt' or tier=='debris' then return 40
    elseif tier=='ring' then return 39
    end
    local r=radiusEarth or (diameterKm and diameterKm/12742) or 1
    return clamp(15+38*math.log(clamp(r,.13,20)/.13)/math.log(20/.13),15,53)
end
local function child(args,prefix,i)
    local b=prefix..'卫星'..i
    local name=arg(args,b)
    if name=='' then return nil end
    return {name=name,page=arg(args,b..'链接'),english=arg(args,b..'英文'),
        icon=arg(args,b..'图'),rotate=number(arg(args,b..'图旋转'),0),radiusEarth=number(arg(args,b..'半径'),nil),
        diameterKm=number(arg(args,b..'直径km'),nil),color=tint(arg(args,b..'颜色'),'#bac3cc'),
        designation=arg(args,b..'编号')}
end
local function append(parts,s) parts[#parts+1]=s end
-- The orbital rail is positioned independently from the Navbox cell intrinsic width.
-- All moon glyphs sit on the same vertical descendant line as their parent disc centre.
local function rotationStyle(deg)
    local n=number(deg,0)
    if n == 0 then return '' end
    return ' style="transform:rotate('..string.format('%.2f',clamp(n,-180,180))..'deg)"'
end
local function renderChild(ch,title)
    local current=ch.page~='' and (title==ch.page or title=='模板:'..ch.page)
    local d=math.min(discSize(ch.type=='star' and 'star' or 'moon',ch.radiusEarth,ch.radiusSolar,ch.diameterKm),24)
    local tip=ch.name
    if ch.english~='' then tip=tip..' · '..ch.english end
    local parts={'<div class="blsm-child'..(current and ' blsm-current' or '')..'" title="'..html(tip)..'">',
        '<span class="blsm-child-glyph"><span class="blsm-child-art"'..rotationStyle(ch.rotate)..'>'}
    if ch.icon~='' then
        append(parts,imageWiki(ch.icon,d))
    else
        append(parts,'<span class="blsm-child-disc" style="width:'..math.floor(d)..'px;height:'..math.floor(d)..'px;background-color:'..ch.color..'"></span>')
    end
    append(parts,'</span></span><span class="blsm-child-name">'..wikiLink(ch.page~='' and ch.page or ch.name,ch.name)..'</span>')
    if ch.designation~='' then append(parts,'<span class="blsm-child-code">'..html(ch.designation)..'</span>') end
    append(parts,'</div>')
    return table.concat(parts)
end
local function renderRing(name,page,current)
    return '<div class="blsm-child blsm-child--ring'..(current and ' blsm-current' or '')..'" title="行星环">'
        ..'<span class="blsm-child-glyph"><span class="blsm-ring-band"></span></span>'
        ..'<span class="blsm-child-name">'..wikiLink(page~='' and page or name,name)..'</span></div>'
end
local function renderDisc(o)
    local size=discSize(o.type,o.radiusEarth,o.radiusSolar,o.diameterKm)
    if o.type=='belt' or o.type=='debris' then
        return '<span class="blsm-belt-band" title="小天体带"></span>'
    end
    if o.icon~='' then
        return '<span class="blsm-image"'..rotationStyle(o.rotate)..'>'..imageWiki(o.icon,size)..'</span>'
    end
    local c=tint(o.color,colors[o.visual] or colors.unknown)
    return '<span class="blsm-disc blsm-disc--'..o.visual..'" style="width:'..math.floor(size)..'px;height:'..math.floor(size)..'px;background-color:'..c..'"></span>'
end
local function renderBody(o,title,opts)
    local parts={}
    local isBelt=o.type=='belt' or o.type=='debris'
    local ringShown=opts.showRings and o.ring~=''
    local childCount=#o.moons+(ringShown and 1 or 0)
    local width=isBelt and 175 or (o.type=='star' and 196 or (childCount>0 and 226 or 190))
    local active=(title==o.page or (opts.highlight~='' and (opts.highlight==o.page or opts.highlight==o.name)))
    local meta={o.name}
    if o.english~='' then append(meta,o.english) end
    if o.orbit~='' then append(meta,'轨道: '..o.orbit) end
    if o.radiusEarth then append(meta,'半径: '..tostring(o.radiusEarth)..' R⊕') end
    if o.radiusSolar then append(meta,'半径: '..tostring(o.radiusSolar)..' R☉') end
    local cls=isBelt and 'blsm-slot--belt' or 'blsm-slot--body'
    if o.type=='star' then cls=cls..' blsm-slot--star' end
    if active then cls=cls..' blsm-current' end
    append(parts,'<div class="blsm-slot '..cls..'" style="width:'..width..'px" title="'..html(table.concat(meta,' · '))..'">')
    append(parts,'<div class="blsm-glyph">'..renderDisc(o)..'</div>')
    append(parts,'<div class="blsm-name">'..wikiLink(o.page,o.name)..'</div>')
    local subtitle=''
    if isBelt then
        subtitle=o.orbit~='' and o.orbit or o.english
    elseif o.type=='star' then
        subtitle=o.english
    elseif o.designation~='' then
        subtitle=(opts.numberPrefix~='' and (opts.numberPrefix..' ') or '')..o.designation
    elseif o.english~='' then
        subtitle=o.english
    end
    append(parts,'<div class="blsm-caption">'..html(subtitle)..'</div>')
    if opts.details and o.english~='' and o.type~='star' and not isBelt then
        append(parts,'<div class="blsm-extra">'..html(o.english)..'</div>')
    end
    if childCount>0 then
        append(parts,'<div class="blsm-children">')
        if ringShown then append(parts,renderRing(o.ring,o.ringPage,title==o.ringPage)) end
        for _,ch in ipairs(o.moons) do append(parts,renderChild(ch,title)) end
        append(parts,'</div>')
    end
    append(parts,'</div>')
    return table.concat(parts)
end
local function makeStar(args,i)
    local base=i==1 and '恒星' or ('恒星'..i)
    local name=arg(args,base)
    if name=='' then return nil end
    return {name=name,english=arg(args,base..'英文'),page=arg(args,base..'链接')~='' and arg(args,base..'链接') or name,
      type='star',visual='star',designation=arg(args,base..'序号')~='' and arg(args,base..'序号') or '★',
      icon=arg(args,base..'图'),rotate=number(arg(args,base..'图旋转'),0),color=arg(args,base..'颜色'),orbit='',
      radiusSolar=number(arg(args,base..'半径'),nil),diameterKm=number(arg(args,base..'直径km'),nil),
      moons={},ring='',ringPage=''}
end
local function makeBody(args,i,planetIndex)
    local base='天体'..i
    local name=arg(args,base,'行星'..i)
    if name=='' then return nil end
    local legacy='行星'..i
    local function fld(k) return arg(args,base..k,legacy..k) end
    local ty=kind(fld('类型'))
    local visual=styleKind(ty,fld('外观'))
    local radiusEarth=number(fld('半径'),nil)
    local diameterKm=number(fld('直径km'),nil)
    local num=fld('序号')
    if num=='' and ty~='belt' and ty~='debris' then num=roman(planetIndex) end
    local o={name=name,page=fld('链接')~='' and fld('链接') or name,english=fld('英文'),type=ty,visual=visual,
      designation=num,icon=fld('图'),rotate=number(fld('图旋转'),0),color=fld('颜色'),orbit=fld('轨道'),
      radiusEarth=radiusEarth,diameterKm=diameterKm,moons={},ring=fld('环'),ringPage=fld('环链接')}
    for j=1,16 do
        local ch=child(args,base,j)
        if not ch and legacy~=base then ch=child(args,legacy,j) end
        if ch then o.moons[#o.moons+1]=ch end
    end
    if ty=='belt' or ty=='debris' then o.ring='';o.moons={} end
    if ty=='ring' then o.type='belt' end
    return o
end
function p.render(args,currentTitle)
    args=args or {}
    local title=trim(currentTitle)
    local opts={details=arg(args,'显示详情')=='1' or arg(args,'显示详情')=='yes',highlight=arg(args,'highlight','高亮'),showRings=arg(args,'显示行星环')=='1' or arg(args,'显示行星环')=='yes',numberPrefix=arg(args,'编号前缀')}
    local bodies={}
    local primary=makeStar(args,1)
    local second=makeStar(args,2)
    if primary then bodies[#bodies+1]=primary end
    if second and arg(args,'双星布局')~='伴星' then bodies[#bodies+1]=second end
    if second and primary and arg(args,'双星布局')=='伴星' then
        primary.moons[#primary.moons+1]={name=second.name,page=second.page,english=second.english,icon=second.icon,
             radiusEarth=nil,radiusSolar=second.radiusSolar,diameterKm=second.diameterKm,color=tint(second.color,'#f5d78c'),type='star',designation='B'}
    end
    local planetCounter=0
    for i=1,48 do
        local o=makeBody(args,i,planetCounter+1)
        if o then
            if o.type~='belt' and o.type~='debris' then planetCounter=planetCounter+1 end
            bodies[#bodies+1]=o
        end
    end
    if #bodies==0 then return '<span class="blsm-empty">没有配置天体。</span>' end
    local maxChildren=0
    for _,o in ipairs(bodies) do
        local c=#o.moons+(opts.showRings and o.ring~='' and 1 or 0)
        if c>maxChildren then maxChildren=c end
    end
    local viewportHeight=math.min(460,math.max(166,148+maxChildren*30+(opts.details and 15 or 0)))
    -- Crucial: the scroll container is position:absolute inside a width-constrained
    -- relative viewport. Its max-content orbital rail does NOT participate in the
    -- intrinsic sizing of the Navbox table cell (Vector 2022 page overflow fix).
    local parts={'<div class="blsm-map"><div class="blsm-viewport" style="height:'..viewportHeight..'px">',
        '<div class="blsm-scroll" tabindex="0" role="region" aria-label="星系轨道与天体导航"><div class="blsm-rail">'}
    for _,o in ipairs(bodies) do append(parts,renderBody(o,title,opts)) end
    append(parts,'</div></div></div></div>')
    return table.concat(parts)
end
function p.main(frame)
    local args=frame:getParent() and frame:getParent().args or frame.args
    local title=''
    if mw and mw.title and mw.title.getCurrentTitle then
        local t=mw.title.getCurrentTitle();title=t and (t.text or t.prefixedText) or ''
    end
    return p.render(args,title)
end
return p