Jump to content
Toggle menu
  • 1 articles
  • 7 files
  • 3 users
  • 102 edits
Shroudforge
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.
Revision as of 21:58, 4 November 2026 by Bonsaibauer (talk | contribs) (1 revision imported: Import of Star-Citizen-Modules)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Module documentation
<details class="t-mbox t-mbox--notice navigation-not-searchable noexcerpt t-dependencies" open="no"><summary class="t-mbox__header">Technical detailswiki-tools · uses 9</summary>
Source
Synced with wiki-tools on GitHub
Used by
Unavailable: looking up the pages that use it failed on this render. Purge the page to retry.
Functions
</details>
{{#seo:

|type = website |description = Module:Mainpage is a template page used on the Star Citizen Wiki. Templates are pages that are embedded (transcluded) into other pages to allow for the repetition of information. |site_name = Star Citizen Wiki |locale = en-gb }}

Renders the main page: hero, highlights, featured article, on this day, the editing invitation, two community cards, and the directory.

Editors use this through {{Mainpage}}; see Template:Mainpage for what an editor can change and where.

For module editors

Submodules

Submodule Renders
Mainpage/Config Loads settings.json; normalises it to plain Lua and resolves which build is live.
Mainpage/Nav Turns a settings link entry into wikitext; the hero chips and the directory both read it.
Mainpage/Hero The full-bleed hero band: patch status, wiki statline, lede, search trigger, chip strip.
Mainpage/Highlights The band under the hero: the event card (whichever design applies) and the current-patch card.
Mainpage/Event The event card built on a 1080×83 banner strip.
Mainpage/Event/Legacy The event card built on an ordinary photograph.
Mainpage/Featured The featured card: a whole-card link over the featured article's picture.
Mainpage/OnThisDay Today's date page, transcluded as a two-panel tabber.
Mainpage/Editing The editing invitation; the recent-changes list beneath it is built entirely by the mainpage gadget.
Mainpage/Community The funding card and the Discord/follow card.
Mainpage/Directory The grouped text directory at the foot.

Mainpage.lua composes these into the page and renders the foot row itself; that row is small enough to not need a submodule of its own. Buttons come from Module:ButtonLua, the badge from Module:BadgeLua, and the clock from Module:Countdown: each keeps its own look, and this module supplies only content and placement.

Settings contract

Every other submodule reads settings through Config, never through mw.loadJsonData directly:

  • Config.section(name) / Config.list(name): one top-level object or array, {}/empty when the section is missing or blank, so a caller never has to nil-check first (Config.lua:101-116).
  • Config.toIso(value): a settings date to ISO 8601 UTC, or nil for anything it can't read; it never raises (Config.lua:134-163).
  • Config.livePatch(): the build every consumer agrees is "live" (the first patches entry with channel: "LIVE", else the first with a name), resolved once so the hero's status chip and the patch card can't disagree (Config.lua:177-193).
  • load() wraps mw.loadJsonData in pcall; a moved or deleted settings page degrades to {} rather than raising (Config.lua:80-90).

toPlain (Config.lua:47-77) copies the loaded page's read-only table (whose metatable breaks # and next()) into a plain one, dropping blank strings to nil, compacting arrays, and discarding every _-prefixed key as editor guidance. Read a new settings field through Config, never a second mw.loadJsonData call.

Styles

Page Holds
Mainpage/styles.css The hero, the bands, the grid, and the responsive stages.
Mainpage/cards.css The individual cards, the directory, and the foot.
Mainpage/ground.css The graduation tapes and the dot lattice behind the page.

The page runs full bleed: MediaWiki:Citizen.css drops the body container's gutter for this page, letting the bands paint edge to edge; that rule lives outside this module and this repository (Mainpage/styles.css:11-12).

Gadget

MediaWiki:Gadget-mainpage.js enhances the rendered page: it loads the hero artwork after page load, rolls the stat digits and search-label tail, drives the clock, manages the two scrolling cards' fade cues, and builds the activity list, reading its context from data-gadget-mainpage-* attributes (so grep gadget-mainpage- finds every emitter). The page renders and reads correctly with the gadget absent, except the activity list: without the gadget it stays empty and the card offers its "See all changes" link instead.

Previewing

<syntaxhighlight lang="wikitext">

Articles
1
Edits
102
Registered users
3
The Enshrouded Modding Wiki. Mods, guides, tools, and more...
Articles
1
Edits
102
Registered users
3
The Enshrouded Modding Wiki. Mods, guides, tools, and more...

</syntaxhighlight>

Gotchas

  • Module:Mainpage/settings.json is editor-owned content, not tracked in this repository (gitignored: .gitignore:20); deploy-to-wiki skips it even if a stale copy is sitting on disk, since deploying it would silently revert an editor's changes.
  • STYLESHEETS lists Module:CardLua's styles.css explicitly: six of the seven cards use plain .t-card divs, so their surface, border, and radius would otherwise depend on the event card (the only CardLua consumer) rendering (Mainpage.lua:41-54).
  • Card spans (.home-card--read, --aside, --tall) are placement properties, not display ones: a class that sets display must never share an element with t-card, since CardLua's own display would collide (Mainpage.lua:18-21).
  • The split event card's clock is restyled from the page for one viewport range (640-899.98px, Mainpage/styles.css:541-603), duplicating Module:Countdown's stacked/flat declarations by hand, since a CSS class can't be conditional on viewport width. The banner card avoids this by asking Countdown for t-countdown--flat at every width instead.
  • band() takes varargs, not a table, since ipairs stops at the first nil and would let one declined card drop every card listed after it (Mainpage.lua:87-105).
  • The foot row's edit link and Mainpage/OnThisDay's "Add an event" link both use mw.uri.fullUrl, not callParserFunction: fullurl is a colon magic word (fullurl:) that callParserFunction cannot resolve (Mainpage.lua:107-130, OnThisDay.lua:94-104).
  • Mainpage/Editing's recent-changes list is built entirely by MediaWiki:Gadget-mainpage.js from list=recentchanges: without JS the container renders empty and the card falls back to its "See all changes" link (Editing.lua:10-12).
  • The directory's column count isn't set in Lua: .home-dir is repeat(auto-fit, minmax(125px, 1fr)), so the groups reflow on their own and Mainpage/Directory just emits them (Directory.lua:10-13).

Extending

Every band shares one layout contract: .home-band is the full-bleed ground, .home-band__inner the measured column inside it, and a band of cards adds .home-grid on that inner element, all off one shared twelve-column grid; a band with its own column ratios breaks alignment for every other band (Mainpage.lua:11-17).

A new card is a submodule with a render() that returns nil or a string; wire its call into Mainpage.render's band composition and, if it ships its own stylesheet, add it to STYLESHEETS explicitly. It then picks one of the three span classes (.home-card--read spans 8, .home-card--aside 4, .home-card--tall 8 across two rows); between 640 and 899.98px every span collapses to 6, and below 640 the page is one column in DOM order, so a new span value needs checking against both breakpoints.


require('strict')

--- @module Mainpage
--- Renders the main page.
---
--- Everything editors change — the featured article, the running event, the
--- build chips, the hero, and both link lists — lives in
--- [[Module:Mainpage/settings.json]]. Nothing on this page needs a module edit
--- to keep it current, and the foot links straight to it.
---
--- LAYOUT CONTRACT. Every band is `.home-band` for its full-bleed ground and
--- `.home-band__inner` for the measured column inside it, and every band of
--- cards puts `.home-grid` on that inner element. One twelve-column grid is
--- shared by all of them, so a card edge in one band lands on the same line as
--- a card edge in the next. A band that sets its own column ratios breaks that
--- alignment for every band on the page.
---
--- Card spans go on the card itself (`.home-card--read`, `--aside`, `--tall`)
--- because `grid-column` is a placement property and cannot collide with
--- Module:CardLua's own `display` on `.t-card`. A class that sets `display`
--- must never share an element with `t-card`.
---
--- Structure:
---
---   hero          full bleed, gadget-loaded artwork
---   highlights    [ event 8 ][ patch 4 ]
---   band 1        [ featured 8 ][ on this day 4 ]
---   band 2        [ editing 8, two rows ][ support 4 ]
---                                        [ discussion 4 ]
---   directory     six groups, no card
---   foot          where to edit what the page says

local community = require('Module:Mainpage/Community')
local directory = require('Module:Mainpage/Directory')
local editing = require('Module:Mainpage/Editing')
local featured = require('Module:Mainpage/Featured')
local hero = require('Module:Mainpage/Hero')
local highlights = require('Module:Mainpage/Highlights')
local onThisDay = require('Module:Mainpage/OnThisDay')

--- Loaded in this order: the card primitive first, then the grid and hero, then
--- the components that sit on the grid, then the ground behind both.
---
--- CardLua's sheet is listed EXPLICITLY rather than left to arrive with a
--- CardLua call. Six of the seven cards are built here with `.t-card` on a
--- plain div, so the card surface, border and radius would otherwise depend on
--- the event card happening to render — and it does not when the settings
--- carry no event.
local STYLESHEETS = {
	'Module:CardLua/styles.css',
	'Module:Mainpage/styles.css',
	'Module:Mainpage/cards.css',
	'Module:Mainpage/ground.css',
}

--- Where to go to change what the page says, linked at the foot.
---
--- NOT "edit this page". The page is a single transclusion, so an edit link on
--- it lands an editor in a file with nothing in it to change. This is the one
--- surface that holds content, and it covers everything an editor can change
--- without touching Lua.
local EDIT_TARGETS = {
	{ page = 'Module:Mainpage/settings.json', label = 'Settings' },
}

--- What loads [[MediaWiki:Gadget-mainpage.js]]. Emitted by the module so the
--- invocation is self-sufficient: a page that renders this and does not carry
--- the category gets no lazy artwork, no rolling digits and a clock that never
--- starts, with nothing on the page to say why.
local GADGET_CATEGORY = 'Pages using main page gadget'

local p = {}

--- @param frame mw.frame
--- @return string
local function stylesheets(frame)
	local tags = {}
	for _, src in ipairs(STYLESHEETS) do
		tags[#tags + 1] = frame:extensionTag({
			name = 'templatestyles',
			args = { src = src },
		})
	end
	return table.concat(tags)
end

--- One band of cards on the shared grid.
---
--- Varargs and not a table: a table would be walked with ipairs, which stops at
--- the first nil, so a card that declined to render would silently take every
--- card after it with it. select('#') counts the nils.
---
--- @param ... string|nil
--- @return string
local function band(...)
	local root = mw.html.create('div'):addClass('home-band'):addClass('home-wiki')

	local inner = root:tag('div'):addClass('home-band__inner'):addClass('home-grid')

	for i = 1, select('#', ...) do
		inner:wikitext((select(i, ...)))
	end

	return tostring(root)
end

--- The maintenance affordance at the foot of the page.
---
--- External-link syntax because an internal link cannot carry a query string,
--- and `plainlinks` because the destinations are on this wiki and the arrow
--- icon would say otherwise.
---
--- mw.uri.fullUrl and NOT callParserFunction: `fullurl` is a colon magic word,
--- registered under the name `fullurl:`, and callParserFunction resolves it as
--- `fullurl`, which does not exist. mw.uri is the addressable API for this.
---
--- @return string
local function footer()
	local root = mw.html.create('div'):addClass('home-band'):addClass('home-wiki')

	local inner = root:tag('div'):addClass('home-band__inner'):addClass('home-foot'):addClass('plainlinks')

	for _, target in ipairs(EDIT_TARGETS) do
		inner
			:tag('span')
			:wikitext(string.format('[%s %s]', tostring(mw.uri.fullUrl(target.page, 'action=edit')), target.label))
	end

	return tostring(root)
end

--- @return string
function p.render()
	local frame = mw.getCurrentFrame()

	local root = mw.html.create('div'):addClass('home-page')

	-- Both are empty and painted entirely in CSS: the dot lattice outside the
	-- tapes, and the tapes themselves. The tape is `position: fixed` and the
	-- dots are not, so the instrument holds still while the page scrolls past.
	root:tag('div'):addClass('home-sky')
	root:tag('div'):addClass('home-tape')

	root:wikitext(hero.render({}))
	root:wikitext(highlights.render())
	root:wikitext(band(featured.render(), onThisDay.render()))
	root:wikitext(band(editing.render(), community.renderSupport(), community.renderDiscussion()))
	root:wikitext(directory.render())
	root:wikitext(footer())

	return stylesheets(frame) .. tostring(root) .. string.format('[[Category:%s]]', GADGET_CATEGORY)
end

--- Wikitext entry point. Reached through [[Template:Mainpage]] rather than
--- invoked directly, so the page that carries it stays one transclusion.
---
--- @param frame mw.frame
--- @return string
function p.main(frame)
	return p.render()
end

--- The hero on its own, for previewing it outside the page. `noscript=yes`
--- renders it without the gadget's artwork hook, which is what a reader with
--- scripts off or Save-Data on receives.
---
--- @param frame mw.frame
--- @return string
function p.hero(frame)
	local args = require('Module:Arguments').getArgs(frame)
	local yesno = require('Module:Yesno')

	return stylesheets(frame)
		.. tostring(
			mw.html
				.create('div')
				:addClass('home-page')
				:wikitext(hero.render({ lazyArt = yesno(args.noscript, false) ~= true }))
		)
end

return p