<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wikihm.henrywauzivuff.xyz/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=U0AHNB20WDD</id>
	<title>Hack Club Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wikihm.henrywauzivuff.xyz/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=U0AHNB20WDD"/>
	<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php/Special:Contributions/U0AHNB20WDD"/>
	<updated>2026-11-07T05:39:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList&amp;diff=322</id>
		<title>Module:DynamicYSWSList</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList&amp;diff=322"/>
		<updated>2026-11-05T13:22:23Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;-- Module:DynamicYSWSList&lt;br /&gt;
-- Public API only. Wikitext table avoids unsupported HTML tags in MediaWiki.&lt;br /&gt;
local p = {}&lt;br /&gt;
local API_URL = &#039;https://hackclub.com/api/v1/events&#039;&lt;br /&gt;
local MONTHS = {&#039;January&#039;,&#039;February&#039;,&#039;March&#039;,&#039;April&#039;,&#039;May&#039;,&#039;June&#039;,&#039;July&#039;,&#039;August&#039;,&#039;September&#039;,&#039;October&#039;,&#039;November&#039;,&#039;December&#039;}&lt;br /&gt;
&lt;br /&gt;
local function str(v)&lt;br /&gt;
    if type(v) ~= &#039;string&#039; and type(v) ~= &#039;number&#039; then return &#039;&#039; end&lt;br /&gt;
    return mw.text.trim(tostring(v))&lt;br /&gt;
end&lt;br /&gt;
local function first(...)&lt;br /&gt;
    for i = 1, select(&#039;#&#039;, ...) do&lt;br /&gt;
        local v = str(select(i, ...))&lt;br /&gt;
        if v ~= &#039;&#039; then return v end&lt;br /&gt;
    end&lt;br /&gt;
    return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
local function safe(v)&lt;br /&gt;
    -- Escape user/API text so it cannot inject table delimiters or wiki markup.&lt;br /&gt;
    return mw.text.nowiki(str(v))&lt;br /&gt;
end&lt;br /&gt;
local function isoDate(v)&lt;br /&gt;
    local y,m,d = str(v):match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)&#039;)&lt;br /&gt;
    if not y then return nil end&lt;br /&gt;
    y,m,d = tonumber(y),tonumber(m),tonumber(d)&lt;br /&gt;
    if m &amp;lt; 1 or m &amp;gt; 12 then return nil end&lt;br /&gt;
    local lengths = {31,28,31,30,31,30,31,31,30,31,30,31}&lt;br /&gt;
    if y % 4 == 0 and (y % 100 ~= 0 or y % 400 == 0) then lengths[2] = 29 end&lt;br /&gt;
    if d &amp;lt; 1 or d &amp;gt; lengths[m] then return nil end&lt;br /&gt;
    return string.format(&#039;%04d-%02d-%02d&#039;,y,m,d)&lt;br /&gt;
end&lt;br /&gt;
local function displayDate(date)&lt;br /&gt;
    if not date then return &#039;—&#039; end&lt;br /&gt;
    local y,m,d = date:match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)$&#039;)&lt;br /&gt;
    return string.format(&#039;%d %s %s&#039;, tonumber(d), MONTHS[tonumber(m)], y)&lt;br /&gt;
end&lt;br /&gt;
local function dateCell(date, class)&lt;br /&gt;
    return &#039;| data-sort-value=&amp;quot;&#039; .. (date or &#039;9999-12-31&#039;) .. &#039;&amp;quot; class=&amp;quot;&#039; .. class .. &#039;&amp;quot; | &#039; .. displayDate(date)&lt;br /&gt;
end&lt;br /&gt;
local function validUrl(v)&lt;br /&gt;
    local url = str(v)&lt;br /&gt;
    if url:match(&#039;^https?://[^%s%[%]&amp;lt;&amp;gt;|{}&amp;quot;\&#039;]+$&#039;) then return url end&lt;br /&gt;
    return nil&lt;br /&gt;
end&lt;br /&gt;
local function description(v)&lt;br /&gt;
    local s = str(v):gsub(&#039;&amp;lt;[^&amp;gt;]*&amp;gt;&#039;, &#039;&#039;):gsub(&#039;%s+&#039;, &#039; &#039;)&lt;br /&gt;
    s = mw.text.trim(mw.text.decode(s))&lt;br /&gt;
    if mw.ustring.len(s) &amp;gt; 240 then s = mw.ustring.sub(s, 1, 237) .. &#039;…&#039; end&lt;br /&gt;
    return s&lt;br /&gt;
end&lt;br /&gt;
local function fetch()&lt;br /&gt;
    if not (mw.ext and mw.ext.externalData and type(mw.ext.externalData.getExternalData) == &#039;function&#039;) then&lt;br /&gt;
        return nil, &#039;External Data is unavailable.&#039;&lt;br /&gt;
    end&lt;br /&gt;
    local ok, rows = pcall(mw.ext.externalData.getExternalData, {&lt;br /&gt;
        url = API_URL,&lt;br /&gt;
        format = &#039;json with jsonpath&#039;,&lt;br /&gt;
        data = {&lt;br /&gt;
            name = &#039;$.data[*].name&#039;,&lt;br /&gt;
            description = &#039;$.data[*].description&#039;,&lt;br /&gt;
            url = &#039;$.data[*].url&#039;,&lt;br /&gt;
            startDate = &#039;$.data[*].startDate&#039;,&lt;br /&gt;
            endDate = &#039;$.data[*].endDate&#039;&lt;br /&gt;
        }&lt;br /&gt;
    })&lt;br /&gt;
    if not ok or type(rows) ~= &#039;table&#039; then return nil, &#039;Could not load public events.&#039; end&lt;br /&gt;
    return rows&lt;br /&gt;
end&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
    local data, err = fetch()&lt;br /&gt;
    if not data then return &#039;&amp;lt;strong class=&amp;quot;error&amp;quot;&amp;gt;&#039; .. safe(err) .. &#039;&amp;lt;/strong&amp;gt;&#039; end&lt;br /&gt;
    local programs = {}&lt;br /&gt;
    for _, row in ipairs(data) do&lt;br /&gt;
        if type(row) == &#039;table&#039; then&lt;br /&gt;
            local name = first(row.name, row.title)&lt;br /&gt;
            if name ~= &#039;&#039; then&lt;br /&gt;
                programs[#programs + 1] = {&lt;br /&gt;
                    name = name,&lt;br /&gt;
                    description = description(first(row.description, row.summary)),&lt;br /&gt;
                    startDate = isoDate(first(row.startDate,row.start_date,row.start)),&lt;br /&gt;
                    endDate = isoDate(first(row.endDate,row.end_date,row[&#039;end&#039;])),&lt;br /&gt;
                    url = validUrl(first(row.url,row.website,row.link))&lt;br /&gt;
                }&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    table.sort(programs, function(a,b)&lt;br /&gt;
        return mw.ustring.lower(a.name) &amp;lt; mw.ustring.lower(b.name)&lt;br /&gt;
    end)&lt;br /&gt;
    if #programs == 0 then return &#039;No public programs were returned.&#039; end&lt;br /&gt;
    local out = {&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;ysws-directory&amp;quot;&amp;gt;&#039;,&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;ysws-directory-count&amp;quot;&amp;gt;&#039; .. #programs .. &#039; public event entries listed.&amp;lt;/p&amp;gt;&#039;,&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;ysws-table-wrapper&amp;quot;&amp;gt;&#039;,&lt;br /&gt;
        &#039;{| class=&amp;quot;wikitable sortable ysws-table&amp;quot;&#039;,&lt;br /&gt;
        &#039;! Program\n! Description\n! Start date\n! End date\n! Status\n! Website&#039;&lt;br /&gt;
    }&lt;br /&gt;
    for _, item in ipairs(programs) do&lt;br /&gt;
        out[#out+1] = &#039;|- class=&amp;quot;ysws-program&amp;quot; data-start=&amp;quot;&#039; .. (item.startDate or &#039;&#039;) .. &#039;&amp;quot; data-end=&amp;quot;&#039; .. (item.endDate or &#039;&#039;) .. &#039;&amp;quot;&#039;&lt;br /&gt;
        out[#out+1] = &#039;! scope=&amp;quot;row&amp;quot; class=&amp;quot;ysws-name&amp;quot; | &#039; .. safe(item.name)&lt;br /&gt;
        out[#out+1] = &#039;| class=&amp;quot;ysws-description&amp;quot; | &#039; .. (item.description ~= &#039;&#039; and safe(item.description) or &#039;—&#039;)&lt;br /&gt;
        out[#out+1] = dateCell(item.startDate, &#039;ysws-start&#039;)&lt;br /&gt;
        out[#out+1] = dateCell(item.endDate, &#039;ysws-end&#039;)&lt;br /&gt;
        out[#out+1] = &#039;| class=&amp;quot;ysws-status&amp;quot; | Unknown&#039;&lt;br /&gt;
        out[#out+1] = &#039;| class=&amp;quot;ysws-website&amp;quot; | &#039; .. (item.url and (&#039;[&#039; .. item.url .. &#039; Website]&#039;) or &#039;—&#039;)&lt;br /&gt;
    end&lt;br /&gt;
    out[#out+1] = &#039;|}&#039;&lt;br /&gt;
    out[#out+1] = &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    out[#out+1] = &#039;&amp;lt;p class=&amp;quot;ysws-source-note&amp;quot;&amp;gt;Generated from the public Hack Club events API. Dates and descriptions reflect the public feed.&amp;lt;/p&amp;gt;&#039;&lt;br /&gt;
    out[#out+1] = &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    return frame:preprocess(table.concat(out, &#039;\n&#039;))&lt;br /&gt;
end&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadgets-definition&amp;diff=321</id>
		<title>MediaWiki:Gadgets-definition</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadgets-definition&amp;diff=321"/>
		<updated>2026-11-05T13:20:14Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: Created page with &amp;quot;* YSWSSearch[ResourceLoader|default]|YSWSSearch.js|YSWSSearch.css&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* YSWSSearch[ResourceLoader|default]|YSWSSearch.js|YSWSSearch.css&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadget-YSWSSearch.js&amp;diff=320</id>
		<title>MediaWiki:Gadget-YSWSSearch.js</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadget-YSWSSearch.js&amp;diff=320"/>
		<updated>2026-11-05T13:18:54Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: Created page with &amp;quot;(function () {     &amp;#039;use strict&amp;#039;;     function todayISO() {         var d = new Date();         return d.getFullYear() + &amp;#039;-&amp;#039; + String(d.getMonth() + 1).padStart(2, &amp;#039;0&amp;#039;) + &amp;#039;-&amp;#039; + String(d.getDate()).padStart(2, &amp;#039;0&amp;#039;);     }     function init(root) {         if (root.dataset.yswsInitialized === &amp;#039;1&amp;#039;) return;         var rows = Array.from(root.querySelectorAll(&amp;#039;tr.ysws-program&amp;#039;));         if (!rows.length) return;         root.dataset.yswsInitialized = &amp;#039;1&amp;#039;;         var controls...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    function todayISO() {&lt;br /&gt;
        var d = new Date();&lt;br /&gt;
        return d.getFullYear() + &#039;-&#039; + String(d.getMonth() + 1).padStart(2, &#039;0&#039;) + &#039;-&#039; + String(d.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function init(root) {&lt;br /&gt;
        if (root.dataset.yswsInitialized === &#039;1&#039;) return;&lt;br /&gt;
        var rows = Array.from(root.querySelectorAll(&#039;tr.ysws-program&#039;));&lt;br /&gt;
        if (!rows.length) return;&lt;br /&gt;
        root.dataset.yswsInitialized = &#039;1&#039;;&lt;br /&gt;
        var controls = document.createElement(&#039;div&#039;);&lt;br /&gt;
        controls.className = &#039;ysws-controls&#039;;&lt;br /&gt;
        var label = document.createElement(&#039;label&#039;);&lt;br /&gt;
        label.textContent = &#039;Search programs&#039;;&lt;br /&gt;
        var input = document.createElement(&#039;input&#039;);&lt;br /&gt;
        input.type = &#039;search&#039;;&lt;br /&gt;
        input.className = &#039;ysws-search&#039;;&lt;br /&gt;
        input.placeholder = &#039;Search name or description&#039;;&lt;br /&gt;
        input.autocomplete = &#039;off&#039;;&lt;br /&gt;
        input.id = &#039;ysws-search-&#039; + Math.random().toString(36).slice(2);&lt;br /&gt;
        label.htmlFor = input.id;&lt;br /&gt;
        var results = document.createElement(&#039;p&#039;);&lt;br /&gt;
        results.className = &#039;ysws-results&#039;;&lt;br /&gt;
        results.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        results.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        controls.append(label, input, results);&lt;br /&gt;
        var tableWrap = root.querySelector(&#039;.ysws-table-wrapper&#039;);&lt;br /&gt;
        root.insertBefore(controls, tableWrap || root.firstChild);&lt;br /&gt;
        var empty = document.createElement(&#039;p&#039;);&lt;br /&gt;
        empty.className = &#039;ysws-empty&#039;;&lt;br /&gt;
        empty.textContent = &#039;No programs match your search.&#039;;&lt;br /&gt;
        empty.hidden = true;&lt;br /&gt;
        if (tableWrap) tableWrap.after(empty); else root.appendChild(empty);&lt;br /&gt;
        var today = todayISO();&lt;br /&gt;
        rows.forEach(function (row) {&lt;br /&gt;
            var start = row.dataset.start || &#039;&#039;;&lt;br /&gt;
            var end = row.dataset.end || &#039;&#039;;&lt;br /&gt;
            var status = &#039;Unknown&#039;;&lt;br /&gt;
            if (start &amp;amp;&amp;amp; start &amp;gt; today) status = &#039;Upcoming&#039;;&lt;br /&gt;
            else if (end &amp;amp;&amp;amp; end &amp;lt; today) status = &#039;Ended&#039;;&lt;br /&gt;
            else if (start &amp;amp;&amp;amp; end &amp;amp;&amp;amp; start &amp;lt;= today &amp;amp;&amp;amp; end &amp;gt;= today) status = &#039;Ongoing&#039;;&lt;br /&gt;
            var cell = row.querySelector(&#039;.ysws-status&#039;);&lt;br /&gt;
            if (cell) { cell.textContent = status; cell.setAttribute(&#039;data-sort-value&#039;, status); }&lt;br /&gt;
        });&lt;br /&gt;
        function update() {&lt;br /&gt;
            var q = input.value.trim().toLocaleLowerCase();&lt;br /&gt;
            var count = 0;&lt;br /&gt;
            rows.forEach(function (row) {&lt;br /&gt;
                var name = row.querySelector(&#039;.ysws-name&#039;);&lt;br /&gt;
                var desc = row.querySelector(&#039;.ysws-description&#039;);&lt;br /&gt;
                var haystack = ((name &amp;amp;&amp;amp; name.textContent) || &#039;&#039;) + &#039; &#039; + ((desc &amp;amp;&amp;amp; desc.textContent) || &#039;&#039;);&lt;br /&gt;
                var match = haystack.toLocaleLowerCase().includes(q);&lt;br /&gt;
                row.hidden = !match;&lt;br /&gt;
                if (match) count++;&lt;br /&gt;
            });&lt;br /&gt;
            results.textContent = count + &#039; of &#039; + rows.length + &#039; programs shown&#039;;&lt;br /&gt;
            empty.hidden = count !== 0;&lt;br /&gt;
        }&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, update);&lt;br /&gt;
        update();&lt;br /&gt;
    }&lt;br /&gt;
    function initAll(node) {&lt;br /&gt;
        if (!node || !node.querySelectorAll) return;&lt;br /&gt;
        node.querySelectorAll(&#039;.ysws-directory&#039;).forEach(init);&lt;br /&gt;
        if (node.matches &amp;amp;&amp;amp; node.matches(&#039;.ysws-directory&#039;)) init(node);&lt;br /&gt;
    }&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) { $content.each(function () { initAll(this); }); });&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, function () { initAll(document); });&lt;br /&gt;
    else initAll(document);&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadget-YSWSSearch.css&amp;diff=319</id>
		<title>MediaWiki:Gadget-YSWSSearch.css</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=MediaWiki:Gadget-YSWSSearch.css&amp;diff=319"/>
		<updated>2026-11-05T13:11:45Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: Created page with &amp;quot; /* MediaWiki:Gadget-YSWSSearch.css */  .ysws-directory {     max-width: 100%; }  .ysws-directory-count {     color: var(--color-subtle, #54595d);     font-size: 0.95em; }  .ysws-controls {     margin: 1.25em 0; }  .ysws-controls label {     display: block;     font-weight: 600;     margin-bottom: 0.4em; }  .ysws-controls input[type=&amp;quot;search&amp;quot;] {     box-sizing: border-box;     width: 100%;     max-width: 32em;     padding: 0.65em 0.85em;     border: 1px solid var(--border...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
/* MediaWiki:Gadget-YSWSSearch.css */&lt;br /&gt;
&lt;br /&gt;
.ysws-directory {&lt;br /&gt;
    max-width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-directory-count {&lt;br /&gt;
    color: var(--color-subtle, #54595d);&lt;br /&gt;
    font-size: 0.95em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-controls {&lt;br /&gt;
    margin: 1.25em 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-controls label {&lt;br /&gt;
    display: block;&lt;br /&gt;
    font-weight: 600;&lt;br /&gt;
    margin-bottom: 0.4em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-controls input[type=&amp;quot;search&amp;quot;] {&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 32em;&lt;br /&gt;
    padding: 0.65em 0.85em;&lt;br /&gt;
    border: 1px solid var(--border-color-base, #a2a9b1);&lt;br /&gt;
    border-radius: 5px;&lt;br /&gt;
    background: var(--background-color-base, #fff);&lt;br /&gt;
    color: var(--color-base, #202122);&lt;br /&gt;
    font: inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-controls input:focus {&lt;br /&gt;
    outline: 2px solid #36c;&lt;br /&gt;
    outline-offset: 1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-results,&lt;br /&gt;
.ysws-source-note {&lt;br /&gt;
    font-size: 0.9em;&lt;br /&gt;
    color: var(--color-subtle, #54595d);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-table-wrapper {&lt;br /&gt;
    max-width: 100%;&lt;br /&gt;
    overflow-x: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-table {&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    border-collapse: collapse;&lt;br /&gt;
    font-size: 0.93em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-table th,&lt;br /&gt;
.ysws-table td {&lt;br /&gt;
    padding: 0.65em 0.8em;&lt;br /&gt;
    vertical-align: top;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-table .ysws-name {&lt;br /&gt;
    min-width: 9em;&lt;br /&gt;
    font-weight: 600;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-description {&lt;br /&gt;
    min-width: 14em;&lt;br /&gt;
    max-width: 32em;&lt;br /&gt;
    line-height: 1.45;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-start,&lt;br /&gt;
.ysws-end {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-status {&lt;br /&gt;
    font-weight: 500;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-website {&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-table tbody tr:hover {&lt;br /&gt;
    background: var(--background-color-interactive-subtle, #eaecf0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-program[hidden],&lt;br /&gt;
.ysws-empty[hidden] {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ysws-empty {&lt;br /&gt;
    padding: 1em 0;&lt;br /&gt;
    font-style: italic;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 700px) {&lt;br /&gt;
    .ysws-table {&lt;br /&gt;
        font-size: 0.85em;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .ysws-table th,&lt;br /&gt;
    .ysws-table td {&lt;br /&gt;
        padding: 0.5em;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .ysws-description {&lt;br /&gt;
        min-width: 12em;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList/sandbox&amp;diff=318</id>
		<title>Module:DynamicYSWSList/sandbox</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList/sandbox&amp;diff=318"/>
		<updated>2026-11-05T13:10:35Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
-- Module:DynamicYSWSList&lt;br /&gt;
-- Public Hack Club YSWS directory&lt;br /&gt;
-- Data source: https://hackclub.com/api/v1/events&lt;br /&gt;
--&lt;br /&gt;
-- Generates a sortable MediaWiki table.&lt;br /&gt;
-- Search and status updates are handled by personal JS or a gadget.&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local API_URL = &#039;https://hackclub.com/api/v1/events&#039;&lt;br /&gt;
&lt;br /&gt;
local MONTHS = {&lt;br /&gt;
    &#039;January&#039;, &#039;February&#039;, &#039;March&#039;,&lt;br /&gt;
    &#039;April&#039;, &#039;May&#039;, &#039;June&#039;,&lt;br /&gt;
    &#039;July&#039;, &#039;August&#039;, &#039;September&#039;,&lt;br /&gt;
    &#039;October&#039;, &#039;November&#039;, &#039;December&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Utility functions&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function str(value)&lt;br /&gt;
    if value == nil then&lt;br /&gt;
        return &#039;&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if type(value) ~= &#039;string&#039;&lt;br /&gt;
        and type(value) ~= &#039;number&#039; then&lt;br /&gt;
        return &#039;&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return mw.text.trim(tostring(value))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function firstNonEmpty(...)&lt;br /&gt;
    for i = 1, select(&#039;#&#039;, ...) do&lt;br /&gt;
        local value = str(select(i, ...))&lt;br /&gt;
&lt;br /&gt;
        if value ~= &#039;&#039; then&lt;br /&gt;
            return value&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Add literal text safely to an mw.html element.&lt;br /&gt;
-- External data must not be interpreted as wiki markup.&lt;br /&gt;
local function addText(element, value)&lt;br /&gt;
    element:wikitext(&lt;br /&gt;
        mw.text.nowiki(tostring(value or &#039;&#039;))&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    return element&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function validUrl(url)&lt;br /&gt;
    url = str(url)&lt;br /&gt;
&lt;br /&gt;
    if url:match(&#039;^https?://[^%s&amp;lt;&amp;gt;&amp;quot;\&#039;]+$&#039;) then&lt;br /&gt;
        return url&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Date handling&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function normalizeDate(value)&lt;br /&gt;
    value = str(value)&lt;br /&gt;
&lt;br /&gt;
    -- Supports ISO dates and ISO timestamps.&lt;br /&gt;
    local year, month, day =&lt;br /&gt;
        value:match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)&#039;)&lt;br /&gt;
&lt;br /&gt;
    if not year then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    year = tonumber(year)&lt;br /&gt;
    month = tonumber(month)&lt;br /&gt;
    day = tonumber(day)&lt;br /&gt;
&lt;br /&gt;
    if not year or not month or not day then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if month &amp;lt; 1 or month &amp;gt; 12 then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local days = {&lt;br /&gt;
        31, 28, 31, 30, 31, 30,&lt;br /&gt;
        31, 31, 30, 31, 30, 31&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    local leap =&lt;br /&gt;
        year % 4 == 0 and&lt;br /&gt;
        (year % 100 ~= 0 or year % 400 == 0)&lt;br /&gt;
&lt;br /&gt;
    if leap then&lt;br /&gt;
        days[2] = 29&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if day &amp;lt; 1 or day &amp;gt; days[month] then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return string.format(&lt;br /&gt;
        &#039;%04d-%02d-%02d&#039;,&lt;br /&gt;
        year,&lt;br /&gt;
        month,&lt;br /&gt;
        day&lt;br /&gt;
    )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function displayDate(iso)&lt;br /&gt;
    if not iso then&lt;br /&gt;
        return &#039;—&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local year, month, day =&lt;br /&gt;
        iso:match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)$&#039;)&lt;br /&gt;
&lt;br /&gt;
    if not year then&lt;br /&gt;
        return &#039;—&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return string.format(&lt;br /&gt;
        &#039;%d %s %s&#039;,&lt;br /&gt;
        tonumber(day),&lt;br /&gt;
        MONTHS[tonumber(month)],&lt;br /&gt;
        year&lt;br /&gt;
    )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Public API&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function fetchPrograms()&lt;br /&gt;
    if not mw.ext or not mw.ext.externalData then&lt;br /&gt;
        return nil, &#039;External Data extension is unavailable.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local externalData = mw.ext.externalData&lt;br /&gt;
&lt;br /&gt;
    if type(externalData.getExternalData) ~= &#039;function&#039; then&lt;br /&gt;
        return nil, &#039;External Data Lua interface is unavailable.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local ok, data = pcall(&lt;br /&gt;
        externalData.getExternalData,&lt;br /&gt;
        {&lt;br /&gt;
            url = API_URL,&lt;br /&gt;
            format = &#039;json with jsonpath&#039;,&lt;br /&gt;
            data = {&lt;br /&gt;
                name = &#039;$.data[*].name&#039;,&lt;br /&gt;
                description = &#039;$.data[*].description&#039;,&lt;br /&gt;
                url = &#039;$.data[*].url&#039;,&lt;br /&gt;
                startDate = &#039;$.data[*].startDate&#039;,&lt;br /&gt;
                endDate = &#039;$.data[*].endDate&#039;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        return nil, &#039;Unable to retrieve the public event feed.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if type(data) ~= &#039;table&#039; or next(data) == nil then&lt;br /&gt;
        return nil, &#039;The public event feed returned no data.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Program normalization&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function cleanDescription(value)&lt;br /&gt;
    local description = str(value)&lt;br /&gt;
&lt;br /&gt;
    if description == &#039;&#039; then&lt;br /&gt;
        return &#039;&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Remove HTML markup from descriptions.&lt;br /&gt;
    description = description:gsub(&#039;&amp;lt;[^&amp;gt;]*&amp;gt;&#039;, &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Decode common HTML entities.&lt;br /&gt;
    description = mw.text.decode(description)&lt;br /&gt;
&lt;br /&gt;
    -- Collapse whitespace.&lt;br /&gt;
    description = description:gsub(&#039;%s+&#039;, &#039; &#039;)&lt;br /&gt;
    description = mw.text.trim(description)&lt;br /&gt;
&lt;br /&gt;
    -- Keep table cells reasonably compact.&lt;br /&gt;
    if mw.ustring.len(description) &amp;gt; 240 then&lt;br /&gt;
        description =&lt;br /&gt;
            mw.ustring.sub(description, 1, 237) .. &#039;…&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return description&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalizePrograms(data)&lt;br /&gt;
    local programs = {}&lt;br /&gt;
&lt;br /&gt;
    -- External Data may return an array of rows.&lt;br /&gt;
    for _, row in ipairs(data) do&lt;br /&gt;
        if type(row) == &#039;table&#039; then&lt;br /&gt;
            local name = firstNonEmpty(&lt;br /&gt;
                row.name,&lt;br /&gt;
                row.title&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
            if name ~= &#039;&#039; then&lt;br /&gt;
                local startDate = normalizeDate(&lt;br /&gt;
                    firstNonEmpty(&lt;br /&gt;
                        row.startDate,&lt;br /&gt;
                        row.start_date,&lt;br /&gt;
                        row.start&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
                local endDate = normalizeDate(&lt;br /&gt;
                    firstNonEmpty(&lt;br /&gt;
                        row.endDate,&lt;br /&gt;
                        row.end_date,&lt;br /&gt;
                        row[&#039;end&#039;]&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
                local description = cleanDescription(&lt;br /&gt;
                    firstNonEmpty(&lt;br /&gt;
                        row.description,&lt;br /&gt;
                        row.summary&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
                local url = validUrl(&lt;br /&gt;
                    firstNonEmpty(&lt;br /&gt;
                        row.url,&lt;br /&gt;
                        row.website,&lt;br /&gt;
                        row.link&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
                programs[#programs + 1] = {&lt;br /&gt;
                    name = name,&lt;br /&gt;
                    description = description,&lt;br /&gt;
                    startDate = startDate,&lt;br /&gt;
                    endDate = endDate,&lt;br /&gt;
                    url = url&lt;br /&gt;
                }&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    table.sort(programs, function(a, b)&lt;br /&gt;
        local aName = mw.ustring.lower(a.name)&lt;br /&gt;
        local bName = mw.ustring.lower(b.name)&lt;br /&gt;
&lt;br /&gt;
        return aName &amp;lt; bName&lt;br /&gt;
    end)&lt;br /&gt;
&lt;br /&gt;
    return programs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- HTML helpers&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function addDateCell(row, date, className)&lt;br /&gt;
    local cell = row:tag(&#039;td&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :attr(&lt;br /&gt;
            &#039;data-sort-value&#039;,&lt;br /&gt;
            date or &#039;9999-12-31&#039;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    if date then&lt;br /&gt;
        addText(&lt;br /&gt;
            cell:tag(&#039;time&#039;)&lt;br /&gt;
                :attr(&#039;datetime&#039;, date),&lt;br /&gt;
            displayDate(date)&lt;br /&gt;
        )&lt;br /&gt;
    else&lt;br /&gt;
        addText(cell, &#039;—&#039;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return cell&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function addHeader(row, heading)&lt;br /&gt;
    return addText(&lt;br /&gt;
        row:tag(&#039;th&#039;)&lt;br /&gt;
            :attr(&#039;scope&#039;, &#039;col&#039;),&lt;br /&gt;
        heading&lt;br /&gt;
    )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function addProgramRow(tbody, program)&lt;br /&gt;
    local row = tbody:tag(&#039;tr&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-program&#039;)&lt;br /&gt;
        :attr(&#039;data-start&#039;, program.startDate or &#039;&#039;)&lt;br /&gt;
        :attr(&#039;data-end&#039;, program.endDate or &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Program name&lt;br /&gt;
    addText(&lt;br /&gt;
        row:tag(&#039;th&#039;)&lt;br /&gt;
            :attr(&#039;scope&#039;, &#039;row&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-name&#039;),&lt;br /&gt;
        program.name&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Description&lt;br /&gt;
    addText(&lt;br /&gt;
        row:tag(&#039;td&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-description&#039;),&lt;br /&gt;
        program.description ~= &#039;&#039;&lt;br /&gt;
            and program.description&lt;br /&gt;
            or &#039;—&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Start date&lt;br /&gt;
    addDateCell(&lt;br /&gt;
        row,&lt;br /&gt;
        program.startDate,&lt;br /&gt;
        &#039;ysws-start&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- End date&lt;br /&gt;
    addDateCell(&lt;br /&gt;
        row,&lt;br /&gt;
        program.endDate,&lt;br /&gt;
        &#039;ysws-end&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Status is calculated by client-side JavaScript.&lt;br /&gt;
    addText(&lt;br /&gt;
        row:tag(&#039;td&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-status&#039;),&lt;br /&gt;
        &#039;Unknown&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Website&lt;br /&gt;
    local websiteCell = row:tag(&#039;td&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-website&#039;)&lt;br /&gt;
&lt;br /&gt;
    if program.url then&lt;br /&gt;
        addText(&lt;br /&gt;
            websiteCell:tag(&#039;a&#039;)&lt;br /&gt;
                :attr(&#039;href&#039;, program.url)&lt;br /&gt;
                :attr(&#039;rel&#039;, &#039;noopener noreferrer&#039;),&lt;br /&gt;
            &#039;Website&#039;&lt;br /&gt;
        )&lt;br /&gt;
    else&lt;br /&gt;
        addText(websiteCell, &#039;—&#039;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return row&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Directory renderer&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
local function renderTable(programs)&lt;br /&gt;
    local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-directory&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Entry count&lt;br /&gt;
    addText(&lt;br /&gt;
        root:tag(&#039;p&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-directory-count&#039;),&lt;br /&gt;
        string.format(&lt;br /&gt;
            &#039;%d public event entries listed.&#039;,&lt;br /&gt;
            #programs&lt;br /&gt;
        )&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Search controls&lt;br /&gt;
    local controls = root:tag(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-controls&#039;)&lt;br /&gt;
&lt;br /&gt;
    addText(&lt;br /&gt;
        controls:tag(&#039;label&#039;)&lt;br /&gt;
            :attr(&#039;for&#039;, &#039;ysws-search&#039;),&lt;br /&gt;
        &#039;Search programs&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    controls:tag(&#039;input&#039;)&lt;br /&gt;
        :attr(&#039;id&#039;, &#039;ysws-search&#039;)&lt;br /&gt;
        :attr(&#039;type&#039;, &#039;search&#039;)&lt;br /&gt;
        :attr(&lt;br /&gt;
            &#039;placeholder&#039;,&lt;br /&gt;
            &#039;Search name or description&#039;&lt;br /&gt;
        )&lt;br /&gt;
        :attr(&#039;autocomplete&#039;, &#039;off&#039;)&lt;br /&gt;
        :attr(&#039;aria-controls&#039;, &#039;ysws-table&#039;)&lt;br /&gt;
&lt;br /&gt;
    controls:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-results&#039;)&lt;br /&gt;
        :attr(&#039;role&#039;, &#039;status&#039;)&lt;br /&gt;
        :attr(&#039;aria-live&#039;, &#039;polite&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Table wrapper&lt;br /&gt;
    local wrapper = root:tag(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-table-wrapper&#039;)&lt;br /&gt;
&lt;br /&gt;
    local tbl = wrapper:tag(&#039;table&#039;)&lt;br /&gt;
        :attr(&#039;id&#039;, &#039;ysws-table&#039;)&lt;br /&gt;
        :addClass(&#039;wikitable&#039;)&lt;br /&gt;
        :addClass(&#039;sortable&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-table&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Table headings&lt;br /&gt;
    local header = tbl:tag(&#039;thead&#039;):tag(&#039;tr&#039;)&lt;br /&gt;
&lt;br /&gt;
    addHeader(header, &#039;Program&#039;)&lt;br /&gt;
    addHeader(header, &#039;Description&#039;)&lt;br /&gt;
    addHeader(header, &#039;Start date&#039;)&lt;br /&gt;
    addHeader(header, &#039;End date&#039;)&lt;br /&gt;
    addHeader(header, &#039;Status&#039;)&lt;br /&gt;
    addHeader(header, &#039;Website&#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Table body&lt;br /&gt;
    local tbody = tbl:tag(&#039;tbody&#039;)&lt;br /&gt;
&lt;br /&gt;
    for _, program in ipairs(programs) do&lt;br /&gt;
        addProgramRow(tbody, program)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Empty search result message&lt;br /&gt;
    local empty = root:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-empty&#039;)&lt;br /&gt;
        :attr(&#039;hidden&#039;, &#039;hidden&#039;)&lt;br /&gt;
&lt;br /&gt;
    addText(&lt;br /&gt;
        empty,&lt;br /&gt;
        &#039;No programs match your search.&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    -- Source information&lt;br /&gt;
    addText(&lt;br /&gt;
        root:tag(&#039;p&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-source-note&#039;),&lt;br /&gt;
        &#039;Generated from the public Hack Club events API. &#039;&lt;br /&gt;
            .. &#039;Dates and descriptions reflect the public feed.&#039;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ============================================================&lt;br /&gt;
-- Public entry point&lt;br /&gt;
-- ============================================================&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
    local data, err = fetchPrograms()&lt;br /&gt;
&lt;br /&gt;
    if not data then&lt;br /&gt;
        return tostring(&lt;br /&gt;
            mw.html.create(&#039;p&#039;)&lt;br /&gt;
                :addClass(&#039;error&#039;)&lt;br /&gt;
                :wikitext(&lt;br /&gt;
                    mw.text.nowiki(&lt;br /&gt;
                        err or &#039;Unable to load programs.&#039;&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local programs = normalizePrograms(data)&lt;br /&gt;
&lt;br /&gt;
    if #programs == 0 then&lt;br /&gt;
        return tostring(&lt;br /&gt;
            mw.html.create(&#039;p&#039;)&lt;br /&gt;
                :addClass(&#039;error&#039;)&lt;br /&gt;
                :wikitext(&lt;br /&gt;
                    &#039;No public programs were returned.&#039;&lt;br /&gt;
                )&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return renderTable(programs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=317</id>
		<title>Draft:List of YSWS</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=317"/>
		<updated>2026-11-05T13:07:12Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&#039;&#039;&#039;You Ship, We Ship&#039;&#039;&#039; (&#039;&#039;&#039;YSWS&#039;&#039;&#039;) is a type of [[Hack Club]] program in which participants create and submit projects in exchange for rewards.&lt;br /&gt;
&lt;br /&gt;
This page lists publicly documented Hack Club programs, including historical initiatives.&lt;br /&gt;
&lt;br /&gt;
== List of programs ==&lt;br /&gt;
&lt;br /&gt;
The following list is generated from Hack Club&#039;s public events API. It can be sorted by name, description, dates and status, and searched by program name or description.&lt;br /&gt;
&lt;br /&gt;
{{#invoke:DynamicYSWSList/sandbox|main}}&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
&lt;br /&gt;
* Dates may be unavailable for some programs.&lt;br /&gt;
* A program&#039;s status is calculated from its published dates, where sufficient information is available.&lt;br /&gt;
* Inclusion in this list does not necessarily indicate that applications are currently open.&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
&lt;br /&gt;
* [https://hackclub.com Hack Club official website]&lt;br /&gt;
&lt;br /&gt;
[[Category:Hack Club programs]]&lt;br /&gt;
[[Category:You Ship We Ship]]&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=316</id>
		<title>Draft:List of YSWS</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=316"/>
		<updated>2026-11-05T13:06:46Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&#039;&#039;&#039;You Ship, We Ship&#039;&#039;&#039; (&#039;&#039;&#039;YSWS&#039;&#039;&#039;) is a type of [[Hack Club]] program in which participants create and submit projects in exchange for rewards.&lt;br /&gt;
&lt;br /&gt;
This page lists publicly documented Hack Club programs, including historical initiatives.&lt;br /&gt;
&lt;br /&gt;
== List of programs ==&lt;br /&gt;
&lt;br /&gt;
The following list is generated from Hack Club&#039;s public events API. It can be sorted by name, description, dates and status, and searched by program name or description.&lt;br /&gt;
&lt;br /&gt;
{{#invoke:DynamicYSWSList|main}}&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
&lt;br /&gt;
* Dates may be unavailable for some programs.&lt;br /&gt;
* A program&#039;s status is calculated from its published dates, where sufficient information is available.&lt;br /&gt;
* Inclusion in this list does not necessarily indicate that applications are currently open.&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
&lt;br /&gt;
* [https://hackclub.com Hack Club official website]&lt;br /&gt;
&lt;br /&gt;
[[Category:Hack Club programs]]&lt;br /&gt;
[[Category:You Ship We Ship]]&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList/sandbox&amp;diff=312</id>
		<title>Module:DynamicYSWSList/sandbox</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Module:DynamicYSWSList/sandbox&amp;diff=312"/>
		<updated>2026-11-05T12:59:19Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: Created page with &amp;quot; -- Module:DynamicYSWSList -- Public Hack Club events API only.  local p = {}  local API_URL = &amp;#039;https://hackclub.com/api/v1/events&amp;#039;  local function str(value)     if value == nil then         return &amp;#039;&amp;#039;     end     if type(value) ~= &amp;#039;string&amp;#039; and type(value) ~= &amp;#039;number&amp;#039; then         return &amp;#039;&amp;#039;     end     return mw.text.trim(tostring(value)) end  local function firstNonEmpty(...)     for i = 1, select(&amp;#039;#&amp;#039;, ...) do         local value = str(select(i, ...))         if value ~...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
-- Module:DynamicYSWSList&lt;br /&gt;
-- Public Hack Club events API only.&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local API_URL = &#039;https://hackclub.com/api/v1/events&#039;&lt;br /&gt;
&lt;br /&gt;
local function str(value)&lt;br /&gt;
    if value == nil then&lt;br /&gt;
        return &#039;&#039;&lt;br /&gt;
    end&lt;br /&gt;
    if type(value) ~= &#039;string&#039; and type(value) ~= &#039;number&#039; then&lt;br /&gt;
        return &#039;&#039;&lt;br /&gt;
    end&lt;br /&gt;
    return mw.text.trim(tostring(value))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function firstNonEmpty(...)&lt;br /&gt;
    for i = 1, select(&#039;#&#039;, ...) do&lt;br /&gt;
        local value = str(select(i, ...))&lt;br /&gt;
        if value ~= &#039;&#039; then&lt;br /&gt;
            return value&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return &#039;&#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function validUrl(url)&lt;br /&gt;
    url = str(url)&lt;br /&gt;
    if url:match(&#039;^https?://[^%s&amp;lt;&amp;gt;]+$&#039;) then&lt;br /&gt;
        return url&lt;br /&gt;
    end&lt;br /&gt;
    return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalizeDate(value)&lt;br /&gt;
    value = str(value)&lt;br /&gt;
&lt;br /&gt;
    -- Accept ISO dates and ISO timestamps.&lt;br /&gt;
    local year, month, day =&lt;br /&gt;
        value:match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)&#039;)&lt;br /&gt;
&lt;br /&gt;
    if not year then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    year = tonumber(year)&lt;br /&gt;
    month = tonumber(month)&lt;br /&gt;
    day = tonumber(day)&lt;br /&gt;
&lt;br /&gt;
    if not year or not month or not day then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if month &amp;lt; 1 or month &amp;gt; 12 then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local days = {&lt;br /&gt;
        31, 28, 31, 30, 31, 30,&lt;br /&gt;
        31, 31, 30, 31, 30, 31&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    local leap = year % 4 == 0 and&lt;br /&gt;
        (year % 100 ~= 0 or year % 400 == 0)&lt;br /&gt;
&lt;br /&gt;
    if leap then&lt;br /&gt;
        days[2] = 29&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if day &amp;lt; 1 or day &amp;gt; days[month] then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return string.format(&lt;br /&gt;
        &#039;%04d-%02d-%02d&#039;, year, month, day&lt;br /&gt;
    )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local MONTHS = {&lt;br /&gt;
    &#039;January&#039;, &#039;February&#039;, &#039;March&#039;,&lt;br /&gt;
    &#039;April&#039;, &#039;May&#039;, &#039;June&#039;,&lt;br /&gt;
    &#039;July&#039;, &#039;August&#039;, &#039;September&#039;,&lt;br /&gt;
    &#039;October&#039;, &#039;November&#039;, &#039;December&#039;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function displayDate(iso)&lt;br /&gt;
    if not iso then&lt;br /&gt;
        return &#039;—&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local year, month, day =&lt;br /&gt;
        iso:match(&#039;^(%d%d%d%d)%-(%d%d)%-(%d%d)$&#039;)&lt;br /&gt;
&lt;br /&gt;
    return string.format(&lt;br /&gt;
        &#039;%d %s %s&#039;,&lt;br /&gt;
        tonumber(day),&lt;br /&gt;
        MONTHS[tonumber(month)],&lt;br /&gt;
        year&lt;br /&gt;
    )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function fetchPrograms()&lt;br /&gt;
    if not mw.ext or not mw.ext.externalData then&lt;br /&gt;
        return nil, &#039;External Data is unavailable.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local ok, data, errors = pcall(&lt;br /&gt;
        mw.ext.externalData.getExternalData,&lt;br /&gt;
        {&lt;br /&gt;
            url = API_URL,&lt;br /&gt;
            format = &#039;json with jsonpath&#039;,&lt;br /&gt;
            data = {&lt;br /&gt;
                name = &#039;$.data[*].name&#039;,&lt;br /&gt;
                description = &#039;$.data[*].description&#039;,&lt;br /&gt;
                url = &#039;$.data[*].url&#039;,&lt;br /&gt;
                startDate = &#039;$.data[*].startDate&#039;,&lt;br /&gt;
                endDate = &#039;$.data[*].endDate&#039;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    if not ok then&lt;br /&gt;
        return nil, &#039;Unable to retrieve the event feed.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not data or #data == 0 then&lt;br /&gt;
        return nil, &#039;The event feed returned no programs.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function normalizePrograms(data)&lt;br /&gt;
    local programs = {}&lt;br /&gt;
&lt;br /&gt;
    for _, row in ipairs(data) do&lt;br /&gt;
        local name = firstNonEmpty(row.name)&lt;br /&gt;
&lt;br /&gt;
        if name ~= &#039;&#039; then&lt;br /&gt;
            local description =&lt;br /&gt;
                firstNonEmpty(row.description)&lt;br /&gt;
&lt;br /&gt;
            -- Keep descriptions compact and plain-text.&lt;br /&gt;
            description = mw.text.decode(description)&lt;br /&gt;
            description = description:gsub(&#039;&amp;lt;[^&amp;gt;]*&amp;gt;&#039;, &#039;&#039;)&lt;br /&gt;
            description = description:gsub(&#039;%s+&#039;, &#039; &#039;)&lt;br /&gt;
&lt;br /&gt;
            if mw.ustring.len(description) &amp;gt; 240 then&lt;br /&gt;
                description =&lt;br /&gt;
                    mw.ustring.sub(description, 1, 237)&lt;br /&gt;
                    .. &#039;...&#039;&lt;br /&gt;
            end&lt;br /&gt;
&lt;br /&gt;
            local startDate = normalizeDate(&lt;br /&gt;
                firstNonEmpty(row.startDate, row.start)&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
            local endDate = normalizeDate(&lt;br /&gt;
                firstNonEmpty(row.endDate, row[&#039;end&#039;])&lt;br /&gt;
            )&lt;br /&gt;
&lt;br /&gt;
            programs[#programs + 1] = {&lt;br /&gt;
                name = name,&lt;br /&gt;
                description = description,&lt;br /&gt;
                url = validUrl(row.url),&lt;br /&gt;
                startDate = startDate,&lt;br /&gt;
                endDate = endDate&lt;br /&gt;
            }&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    table.sort(programs, function(a, b)&lt;br /&gt;
        return mw.ustring.lower(a.name) &amp;lt;&lt;br /&gt;
            mw.ustring.lower(b.name)&lt;br /&gt;
    end)&lt;br /&gt;
&lt;br /&gt;
    return programs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function addDateCell(tr, date, className)&lt;br /&gt;
    local cell = tr:tag(&#039;td&#039;)&lt;br /&gt;
        :addClass(className)&lt;br /&gt;
        :attr(&#039;data-sort-value&#039;, date or &#039;9999-12-31&#039;)&lt;br /&gt;
&lt;br /&gt;
    if date then&lt;br /&gt;
        cell:tag(&#039;time&#039;)&lt;br /&gt;
            :attr(&#039;datetime&#039;, date)&lt;br /&gt;
            :text(displayDate(date))&lt;br /&gt;
    else&lt;br /&gt;
        cell:text(&#039;—&#039;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function renderTable(programs)&lt;br /&gt;
    local root = mw.html.create(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-directory&#039;)&lt;br /&gt;
&lt;br /&gt;
    root:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-directory-count&#039;)&lt;br /&gt;
        :text(string.format(&lt;br /&gt;
            &#039;%d public event entries listed.&#039;,&lt;br /&gt;
            #programs&lt;br /&gt;
        ))&lt;br /&gt;
&lt;br /&gt;
    local controls = root:tag(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-controls&#039;)&lt;br /&gt;
&lt;br /&gt;
    controls:tag(&#039;label&#039;)&lt;br /&gt;
        :attr(&#039;for&#039;, &#039;ysws-search&#039;)&lt;br /&gt;
        :text(&#039;Search programs&#039;)&lt;br /&gt;
&lt;br /&gt;
    controls:tag(&#039;input&#039;)&lt;br /&gt;
        :attr(&#039;id&#039;, &#039;ysws-search&#039;)&lt;br /&gt;
        :attr(&#039;type&#039;, &#039;search&#039;)&lt;br /&gt;
        :attr(&#039;placeholder&#039;, &#039;Search name or description&#039;)&lt;br /&gt;
        :attr(&#039;autocomplete&#039;, &#039;off&#039;)&lt;br /&gt;
        :attr(&#039;aria-controls&#039;, &#039;ysws-table&#039;)&lt;br /&gt;
&lt;br /&gt;
    controls:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-results&#039;)&lt;br /&gt;
        :attr(&#039;role&#039;, &#039;status&#039;)&lt;br /&gt;
        :attr(&#039;aria-live&#039;, &#039;polite&#039;)&lt;br /&gt;
        :text(&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
    local wrapper = root:tag(&#039;div&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-table-wrapper&#039;)&lt;br /&gt;
&lt;br /&gt;
    local tbl = wrapper:tag(&#039;table&#039;)&lt;br /&gt;
        :attr(&#039;id&#039;, &#039;ysws-table&#039;)&lt;br /&gt;
        :addClass(&#039;wikitable&#039;)&lt;br /&gt;
        :addClass(&#039;sortable&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-table&#039;)&lt;br /&gt;
&lt;br /&gt;
    local header = tbl:tag(&#039;thead&#039;):tag(&#039;tr&#039;)&lt;br /&gt;
&lt;br /&gt;
    for _, heading in ipairs({&lt;br /&gt;
        &#039;Program&#039;,&lt;br /&gt;
        &#039;Description&#039;,&lt;br /&gt;
        &#039;Start date&#039;,&lt;br /&gt;
        &#039;End date&#039;,&lt;br /&gt;
        &#039;Status&#039;,&lt;br /&gt;
        &#039;Website&#039;&lt;br /&gt;
    }) do&lt;br /&gt;
        header:tag(&#039;th&#039;)&lt;br /&gt;
            :attr(&#039;scope&#039;, &#039;col&#039;)&lt;br /&gt;
            :text(heading)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local tbody = tbl:tag(&#039;tbody&#039;)&lt;br /&gt;
&lt;br /&gt;
    for _, program in ipairs(programs) do&lt;br /&gt;
        local tr = tbody:tag(&#039;tr&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-program&#039;)&lt;br /&gt;
            :attr(&#039;data-start&#039;, program.startDate or &#039;&#039;)&lt;br /&gt;
            :attr(&#039;data-end&#039;, program.endDate or &#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
        tr:tag(&#039;th&#039;)&lt;br /&gt;
            :attr(&#039;scope&#039;, &#039;row&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-name&#039;)&lt;br /&gt;
            :text(program.name)&lt;br /&gt;
&lt;br /&gt;
        tr:tag(&#039;td&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-description&#039;)&lt;br /&gt;
            :text(program.description ~= &#039;&#039;&lt;br /&gt;
                and program.description or &#039;—&#039;)&lt;br /&gt;
&lt;br /&gt;
        addDateCell(tr, program.startDate, &#039;ysws-start&#039;)&lt;br /&gt;
        addDateCell(tr, program.endDate, &#039;ysws-end&#039;)&lt;br /&gt;
&lt;br /&gt;
        tr:tag(&#039;td&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-status&#039;)&lt;br /&gt;
            :text(&#039;Unknown&#039;)&lt;br /&gt;
&lt;br /&gt;
        local website = tr:tag(&#039;td&#039;)&lt;br /&gt;
            :addClass(&#039;ysws-website&#039;)&lt;br /&gt;
&lt;br /&gt;
        if program.url then&lt;br /&gt;
            website:tag(&#039;a&#039;)&lt;br /&gt;
                :attr(&#039;href&#039;, program.url)&lt;br /&gt;
                :attr(&#039;rel&#039;, &#039;noopener noreferrer&#039;)&lt;br /&gt;
                :text(&#039;Website&#039;)&lt;br /&gt;
        else&lt;br /&gt;
            website:text(&#039;—&#039;)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    root:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-empty&#039;)&lt;br /&gt;
        :attr(&#039;hidden&#039;, &#039;hidden&#039;)&lt;br /&gt;
        :text(&#039;No programs match your search.&#039;)&lt;br /&gt;
&lt;br /&gt;
    root:tag(&#039;p&#039;)&lt;br /&gt;
        :addClass(&#039;ysws-source-note&#039;)&lt;br /&gt;
        :text(&lt;br /&gt;
            &#039;Generated from the public Hack Club events API. &#039;&lt;br /&gt;
            .. &#039;Dates and descriptions reflect the public feed.&#039;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
    local data, err = fetchPrograms()&lt;br /&gt;
&lt;br /&gt;
    if not data then&lt;br /&gt;
        return tostring(&lt;br /&gt;
            mw.html.create(&#039;p&#039;)&lt;br /&gt;
                :addClass(&#039;error&#039;)&lt;br /&gt;
                :text(err)&lt;br /&gt;
        )&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local programs = normalizePrograms(data)&lt;br /&gt;
&lt;br /&gt;
    if #programs == 0 then&lt;br /&gt;
        return &#039;No public programs were returned.&#039;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return renderTable(programs)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
	<entry>
		<id>https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=311</id>
		<title>Draft:List of YSWS</title>
		<link rel="alternate" type="text/html" href="https://wikihm.henrywauzivuff.xyz/index.php?title=Draft:List_of_YSWS&amp;diff=311"/>
		<updated>2026-11-05T12:50:45Z</updated>

		<summary type="html">&lt;p&gt;U0AHNB20WDD: Created page with &amp;quot;&amp;#039;&amp;#039;&amp;#039;You Ship, We Ship&amp;#039;&amp;#039;&amp;#039; (&amp;#039;&amp;#039;&amp;#039;YSWS&amp;#039;&amp;#039;&amp;#039;) is a type of Hack Club program in which participants create and submit a project in exchange for a reward, such as hardware, merchandise, funding, or other benefits.  This page documents Hack Club YSWS programs, including historical programs. Dates are as of 5 November, 2026.  For other events, see List of Hackathons.  == List of programs ==  The following table is sortable by program name and date. A dash indicates that a dat...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;You Ship, We Ship&#039;&#039;&#039; (&#039;&#039;&#039;YSWS&#039;&#039;&#039;) is a type of [[Hack Club]] program in which participants create and submit a project in exchange for a reward, such as hardware, merchandise, funding, or other benefits.&lt;br /&gt;
&lt;br /&gt;
This page documents Hack Club YSWS programs, including historical programs. Dates are as of 5 November, 2026.&lt;br /&gt;
&lt;br /&gt;
For other events, see [[List of Hackathons]].&lt;br /&gt;
&lt;br /&gt;
== List of programs ==&lt;br /&gt;
&lt;br /&gt;
The following table is sortable by program name and date. A dash indicates that a date is not recorded in the source database.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Program&lt;br /&gt;
! Start date&lt;br /&gt;
! End date&lt;br /&gt;
! Website&lt;br /&gt;
|-&lt;br /&gt;
| Boba Drops&lt;br /&gt;
| data-sort-value=&amp;quot;2024-04-14&amp;quot; | 14 April 2024&lt;br /&gt;
| data-sort-value=&amp;quot;9999&amp;quot; | —&lt;br /&gt;
| [https://boba.hackclub.com boba.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| High Seas&lt;br /&gt;
| data-sort-value=&amp;quot;2024-10-30&amp;quot; | 30 October 2024&lt;br /&gt;
| data-sort-value=&amp;quot;2025-01-31&amp;quot; | 31 January 2025&lt;br /&gt;
| [https://highseas.hackclub.com highseas.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Juice&lt;br /&gt;
| data-sort-value=&amp;quot;2025-01-25&amp;quot; | 25 January 2025&lt;br /&gt;
| data-sort-value=&amp;quot;2025-04-01&amp;quot; | 1 April 2025&lt;br /&gt;
| [https://juice.hackclub.com juice.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Summer of Making&lt;br /&gt;
| data-sort-value=&amp;quot;2025-06-16&amp;quot; | 16 June 2025&lt;br /&gt;
| data-sort-value=&amp;quot;2025-09-30&amp;quot; | 30 September 2025&lt;br /&gt;
| [https://summer.hackclub.com summer.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Waffles&lt;br /&gt;
| data-sort-value=&amp;quot;2025-07-13&amp;quot; | 13 July 2025&lt;br /&gt;
| data-sort-value=&amp;quot;2026-10-26&amp;quot; | 26 October 2026&lt;br /&gt;
| [https://waffles.hackclub.com waffles.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Blueprint&lt;br /&gt;
| data-sort-value=&amp;quot;2025-10-03&amp;quot; | 3 October 2025&lt;br /&gt;
| data-sort-value=&amp;quot;2026-03-31&amp;quot; | 31 March 2026&lt;br /&gt;
| [https://blueprint.hackclub.com blueprint.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Flavortown&lt;br /&gt;
| data-sort-value=&amp;quot;2025-12-19&amp;quot; | 19 December 2025&lt;br /&gt;
| data-sort-value=&amp;quot;2026-04-30&amp;quot; | 30 April 2026&lt;br /&gt;
| [https://flavortown.hackclub.com flavortown.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Forge&lt;br /&gt;
| data-sort-value=&amp;quot;2026-08-11&amp;quot; | 11 August 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2027-01-01&amp;quot; | 1 January 2027&lt;br /&gt;
| [https://forge.hackclub.com forge.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Atlantis&lt;br /&gt;
| data-sort-value=&amp;quot;2026-09-21&amp;quot; | 21 September 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2026-11-16&amp;quot; | 16 November 2026&lt;br /&gt;
| [https://atlantis.hackclub.com atlantis.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Fabricate&lt;br /&gt;
| data-sort-value=&amp;quot;2026-09-30&amp;quot; | 30 September 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2026-12-31&amp;quot; | 31 December 2026&lt;br /&gt;
| [https://fabricate.hackclub.com fabricate.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| HackTank&lt;br /&gt;
| data-sort-value=&amp;quot;2026-10-02&amp;quot; | 2 October 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2027-02-28&amp;quot; | 28 February 2027&lt;br /&gt;
| [https://hacktank.hackclub.com hacktank.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Torque&lt;br /&gt;
| data-sort-value=&amp;quot;2026-10-27&amp;quot; | 27 October 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2026-12-30&amp;quot; | 30 December 2026&lt;br /&gt;
| [https://torque.hackclub.com torque.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Chipper&lt;br /&gt;
| data-sort-value=&amp;quot;2026-10-28&amp;quot; | 28 October 2026&lt;br /&gt;
| data-sort-value=&amp;quot;2026-11-10&amp;quot; | 10 November 2026&lt;br /&gt;
| [https://chipper.hackclub.com chipper.hackclub.com]&lt;br /&gt;
|-&lt;br /&gt;
| Wonders&lt;br /&gt;
| data-sort-value=&amp;quot;2027-01-01&amp;quot; | 1 January 2027&lt;br /&gt;
| data-sort-value=&amp;quot;2027-03-01&amp;quot; | 1 March 2027&lt;br /&gt;
| [https://wonders.hackclub.com wonders.hackclub.com]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>U0AHNB20WDD</name></author>
	</entry>
</feed>