<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Signups | 「开源之书 · 共读」</title><link>https://osbook.opensourceway.blog/signup/</link><atom:link href="https://osbook.opensourceway.blog/signup/index.xml" rel="self" type="application/rss+xml"/><description>Signups</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>zh</language><lastBuildDate>Sat, 31 Oct 2026 00:00:00 +0000</lastBuildDate><image><url>https://osbook.opensourceway.blog/media/logo_hu_e1fa4c2b9d4541c1.png</url><title>Signups</title><link>https://osbook.opensourceway.blog/signup/</link></image><item><title>报名 · 「OSCAR·开源之书·共读」北京站 2026-10-31</title><link>https://osbook.opensourceway.blog/signup/2026-10-31/</link><pubDate>Sat, 31 Oct 2026 00:00:00 +0000</pubDate><guid>https://osbook.opensourceway.blog/signup/2026-10-31/</guid><description>&lt;div class="signup-page"&gt;
&lt;div class="sp-hero"&gt;
&lt;div class="sp-kicker"&gt;OSCAR · 开源之书 · 共读&lt;/div&gt;
&lt;h1&gt;三十五载的自发秩序&lt;/h1&gt;
&lt;p class="sp-meta"&gt;2026 年 10 月 31 日（周六）· 13:30 签到 · 北京·海淀 · 智源大厦&lt;/p&gt;
&lt;div class="sp-banner"&gt;
&lt;span&gt;📍 场地&lt;/span&gt;
&lt;b&gt;智源大厦&lt;/b&gt; · 海淀区成府路 150 号楼
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="oscar-signup" class="oscar-signup" data-event="2026-10-31" data-api="https://signup-api.osbook.opensourceway.blog/api/signup"&gt;
&lt;div class="signup-header"&gt;
&lt;h2&gt;📝 &lt;span class="hl"&gt;报名参加&lt;/span&gt;&lt;/h2&gt;
&lt;p class="signup-sub"&gt;页面公开显示称呼与参与情况；&lt;b&gt;联系方式单独加密存放，不在网页公开&lt;/b&gt;。报名后可在下方凭姓名与联系方式自助修改或取消报名。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="signup-closed" id="signup-closed" hidden&gt;
&lt;div class="closed-icon"&gt;🏁&lt;/div&gt;
&lt;div class="closed-title"&gt;活动已开始，报名截止&lt;/div&gt;
&lt;div class="closed-sub" id="closed-sub"&gt;本场报名已关闭，感谢各位书友的关注。&lt;/div&gt;
&lt;/div&gt;
&lt;div class="signup-stats" id="signup-stats"&gt;
&lt;div class="stat-card"&gt;
&lt;div class="stat-num" id="stat-total"&gt;—&lt;/div&gt;
&lt;div class="stat-label"&gt;已报名&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat-card"&gt;
&lt;div class="stat-num" id="stat-dinner"&gt;—&lt;/div&gt;
&lt;div class="stat-label"&gt;参加聚餐&lt;/div&gt;
&lt;/div&gt;
&lt;div class="stat-card"&gt;
&lt;div class="stat-num" id="stat-venue"&gt;—&lt;/div&gt;
&lt;div class="stat-label"&gt;当前场地&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="venue-hint" id="venue-hint"&gt;
&lt;span class="venue-icon"&gt;📍&lt;/span&gt;
&lt;span class="venue-text" id="venue-text"&gt;正在获取报名人数…&lt;/span&gt;
&lt;/div&gt;
&lt;form id="signup-form" class="signup-form"&gt;
&lt;div class="form-row"&gt;
&lt;label for="sf-name"&gt;称呼 &lt;span class="req"&gt;*&lt;/span&gt;&lt;/label&gt;
&lt;input type="text" id="sf-name" name="name" required maxlength="40" placeholder="怎么称呼你"&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label for="sf-contact"&gt;联系方式 &lt;span class="req"&gt;*&lt;/span&gt;&lt;/label&gt;
&lt;input type="text" id="sf-contact" name="contact" required maxlength="120" placeholder="微信 / 邮箱，用于场地变更通知"&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label&gt;是否参加晚间聚餐&lt;/label&gt;
&lt;div class="radio-group"&gt;
&lt;label class="radio"&gt;&lt;input type="radio" name="dinner" value="yes"&gt; 参加&lt;/label&gt;
&lt;label class="radio"&gt;&lt;input type="radio" name="dinner" value="no" checked&gt; 不参加&lt;/label&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label for="sf-reason"&gt;想说的话 / 想讲的话题&lt;/label&gt;
&lt;textarea id="sf-reason" name="reason" rows="3" maxlength="500" placeholder="选填。手上有一本书想讲、一个判断想被反驳、一个还没答案的困惑——都欢迎写在这里"&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;button type="submit" class="submit-btn" id="sf-submit"&gt;提交报名&lt;/button&gt;
&lt;div class="form-msg" id="sf-msg"&gt;&lt;/div&gt;
&lt;/form&gt;
&lt;div class="signup-edit" id="signup-edit"&gt;
&lt;button type="button" class="edit-toggle" id="edit-toggle" aria-expanded="false"&gt;
&lt;span class="edit-arrow"&gt;▸&lt;/span&gt; 已报名？想修改聚餐或想说的话
&lt;/button&gt;
&lt;div class="edit-panel" id="edit-panel" hidden&gt;
&lt;div class="edit-step" id="edit-verify"&gt;
&lt;p class="edit-tip"&gt;填写报名时的&lt;b&gt;姓名&lt;/b&gt;与&lt;b&gt;联系方式&lt;/b&gt;，验证后可修改称呼、聚餐与想说的话。&lt;/p&gt;
&lt;div class="form-row"&gt;
&lt;label for="ef-no"&gt;姓名 &lt;span class="req"&gt;*&lt;/span&gt;&lt;/label&gt;
&lt;input type="text" id="ef-no" maxlength="40" placeholder="报名时填的称呼" autocomplete="off"&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label for="ef-contact"&gt;联系方式 &lt;span class="req"&gt;*&lt;/span&gt;&lt;/label&gt;
&lt;input type="text" id="ef-contact" maxlength="120" placeholder="报名时填的微信 / 邮箱" autocomplete="off"&gt;
&lt;/div&gt;
&lt;button type="button" class="submit-btn" id="ef-verify-btn"&gt;验证并载入&lt;/button&gt;
&lt;div class="form-msg" id="ef-msg"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;form class="edit-step edit-form" id="edit-form" hidden&gt;
&lt;div class="edit-who"&gt;正在修改：&lt;b id="ef-name"&gt;—&lt;/b&gt;（&lt;span id="ef-no-show"&gt;—&lt;/span&gt;）&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label for="ef-name-input"&gt;称呼 &lt;span class="req"&gt;*&lt;/span&gt;&lt;/label&gt;
&lt;input type="text" id="ef-name-input" maxlength="40" placeholder="怎么称呼你" autocomplete="off"&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label&gt;是否参加晚间聚餐&lt;/label&gt;
&lt;div class="radio-group"&gt;
&lt;label class="radio"&gt;&lt;input type="radio" name="ef-dinner" value="yes"&gt; 参加&lt;/label&gt;
&lt;label class="radio"&gt;&lt;input type="radio" name="ef-dinner" value="no" checked&gt; 不参加&lt;/label&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="form-row"&gt;
&lt;label for="ef-reason"&gt;想说的话 / 想讲的话题&lt;/label&gt;
&lt;textarea id="ef-reason" rows="3" maxlength="500" placeholder="选填，500 字以内"&gt;&lt;/textarea&gt;
&lt;/div&gt;
&lt;div class="edit-actions"&gt;
&lt;button type="submit" class="submit-btn" id="ef-save-btn"&gt;保存修改&lt;/button&gt;
&lt;button type="button" class="cancel-btn" id="ef-cancel-btn"&gt;返回&lt;/button&gt;
&lt;/div&gt;
&lt;div class="form-msg" id="ef-msg2"&gt;&lt;/div&gt;
&lt;div class="cancel-zone"&gt;
&lt;button type="button" class="danger-link" id="ef-cancel-signup-btn"&gt;取消报名&lt;/button&gt;
&lt;div class="cancel-confirm" id="ef-cancel-confirm" hidden&gt;
&lt;p class="cancel-warn"&gt;确定要取消报名吗？你的名字会从「已报名书友」名单中移除，&lt;b&gt;之后仍可重新报名&lt;/b&gt;。&lt;/p&gt;
&lt;div class="edit-actions"&gt;
&lt;button type="button" class="danger-btn" id="ef-cancel-yes"&gt;确定取消报名&lt;/button&gt;
&lt;button type="button" class="cancel-btn" id="ef-cancel-no"&gt;再想想&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="form-msg" id="ef-msg3"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="signup-list" id="signup-list" hidden&gt;
&lt;h3&gt;已报名书友&lt;/h3&gt;
&lt;ul id="signup-names"&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.oscar-signup {
margin: 2.5rem 0;
padding: 2rem 1.75rem;
border: 1px solid #E8E2D4;
border-radius: 12px;
background: #FAF8F3;
}
.signup-header {
text-align: center;
margin-bottom: 1.75rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid #E8E2D4;
}
.signup-header h2 {
margin: 0 0 .5rem;
font-size: 1.9rem;
font-weight: 700;
letter-spacing: .06em;
color: #8B6914;
line-height: 1.3;
}
.signup-header h2 .hl {
background: linear-gradient(transparent 62%, rgba(180,160,120,.35) 62%);
}
.signup-sub { margin: 0; font-size: .85rem; color: #6B6B6B; }
.signup-sub a { color: #8B6914; text-decoration: underline; }
.signup-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
gap: .75rem;
margin-bottom: 1.25rem;
}
.stat-card {
text-align: center;
padding: .9rem .5rem;
background: #fff;
border: 1px solid #E8E2D4;
border-radius: 8px;
}
.stat-num { font-size: 1.6rem; font-weight: 700; color: #8B6914; line-height: 1.1; }
.stat-label { font-size: .75rem; color: #6B6B6B; margin-top: .25rem; }
.venue-hint {
display: flex;
gap: .6rem;
align-items: flex-start;
padding: .9rem 1rem;
margin-bottom: 1.5rem;
background: #fff;
border-left: 3px solid #B4A078;
border-radius: 6px;
font-size: .9rem;
line-height: 1.6;
}
.venue-hint.is-confirmed { border-left-color: #2D6A4F; }
.venue-hint.is-coffee { border-left-color: #8B6914; }
.venue-icon { flex-shrink: 0; }
.signup-form { display: flex; flex-direction: column; gap: 1rem; }
.form-row { display: flex; flex-direction: column; gap: .4rem; }
.form-row &gt; label { font-size: .9rem; font-weight: 600; color: #2C2A26; }
.req { color: #B83232; }
.form-row input[type=text],
.form-row textarea {
width: 100%;
padding: .6rem .75rem;
border: 1px solid #D8D2C4;
border-radius: 6px;
font-size: .95rem;
font-family: inherit;
background: #fff;
color: #2C2A26;
}
.form-row input[type=text]:focus,
.form-row textarea:focus {
outline: none;
border-color: #B4A078;
box-shadow: 0 0 0 3px rgba(180,160,120,.15);
}
.form-row textarea { resize: vertical; }
.radio-group { display: flex; gap: 1.25rem; }
.radio { display: inline-flex; align-items: center; gap: .4rem; font-size: .92rem; cursor: pointer; }
.radio input { accent-color: #8B6914; }
.submit-btn {
align-self: flex-start;
padding: .7rem 2rem;
background: #8B6914;
color: #fff;
border: none;
border-radius: 6px;
font-size: .95rem;
font-weight: 600;
cursor: pointer;
transition: background .15s;
}
.submit-btn:hover:not(:disabled) { background: #6E530F; }
.submit-btn:disabled { opacity: .55; cursor: not-allowed; }
.form-msg { font-size: .9rem; line-height: 1.6; }
.form-msg.ok { color: #2D6A4F; }
.form-msg.err { color: #B83232; }
.form-msg code { background: #EFEBE0; padding: .1rem .35rem; border-radius: 3px; font-size: .88em; }
.signup-list { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid #E8E2D4; }
.signup-list h3 { margin: 0 0 .75rem; font-size: 1.05rem; color: #2C2A26; }
.signup-list ul { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; }
.signup-list li {
padding: .3rem .7rem;
background: #fff;
border: 1px solid #E8E2D4;
border-radius: 100px;
font-size: .85rem;
color: #2C2A26;
}
.signup-list li .tag { color: #6B6B6B; font-size: .78rem; margin-left: .3rem; }
.signup-closed {
text-align: center;
padding: 2rem 1.25rem;
margin-bottom: 1.5rem;
background: #F2EFE8;
border: 1px dashed #C9BFA8;
border-radius: 10px;
}
.closed-icon { font-size: 2rem; line-height: 1; margin-bottom: .6rem; }
.closed-title {
font-size: 1.15rem; font-weight: 700; color: #6B5A3E;
letter-spacing: .04em; margin-bottom: .4rem;
}
.closed-sub { font-size: .88rem; color: #8A8272; line-height: 1.6; }
.signup-form.is-closed { display: none; }
.signup-edit {
margin-top: 1.5rem;
padding-top: 1.25rem;
border-top: 1px dashed #E0D9C8;
}
.edit-toggle {
display: inline-flex;
align-items: center;
gap: .4rem;
padding: .45rem .9rem;
background: transparent;
border: 1px solid #D8D2C4;
border-radius: 6px;
color: #8B6914;
font-size: .88rem;
font-family: inherit;
cursor: pointer;
transition: background .15s;
}
.edit-toggle:hover:not(:disabled) { background: #F4F0E6; }
.edit-toggle:disabled { opacity: .5; cursor: not-allowed; }
.edit-arrow { transition: transform .15s; display: inline-block; }
.edit-toggle[aria-expanded="true"] .edit-arrow { transform: rotate(90deg); }
.edit-panel {
margin-top: 1rem;
padding: 1.1rem 1.15rem;
background: #fff;
border: 1px solid #E8E2D4;
border-radius: 8px;
}
.edit-step { display: flex; flex-direction: column; gap: .85rem; }
.edit-tip { margin: 0; font-size: .85rem; color: #6B6B6B; line-height: 1.6; }
.edit-who {
font-size: .88rem; color: #2C2A26;
padding-bottom: .5rem; border-bottom: 1px solid #EFEBE0;
}
.edit-actions { display: flex; gap: .7rem; align-items: center; }
.cancel-btn {
padding: .7rem 1.4rem;
background: #fff;
color: #6B6B6B;
border: 1px solid #D8D2C4;
border-radius: 6px;
font-size: .95rem;
font-family: inherit;
cursor: pointer;
}
.cancel-btn:hover { background: #F4F0E6; }
.oscar-signup.is-closed .edit-toggle { display: none; }
.cancel-zone {
margin-top: .4rem;
padding-top: .9rem;
border-top: 1px solid #EFEBE0;
}
.danger-link {
background: none;
border: none;
padding: 0;
color: #A03A2E;
font-size: .88rem;
font-family: inherit;
text-decoration: underline;
cursor: pointer;
}
.danger-link:hover { color: #7E2A20; }
.cancel-confirm {
margin-top: .75rem;
padding: .9rem 1rem;
background: #FCF4F2;
border: 1px solid #E8C9C2;
border-radius: 6px;
}
.cancel-warn {
margin: 0 0 .75rem;
font-size: .88rem;
color: #6B3A32;
line-height: 1.6;
}
.danger-btn {
padding: .6rem 1.2rem;
background: #A03A2E;
color: #fff;
border: none;
border-radius: 6px;
font-size: .92rem;
font-family: inherit;
cursor: pointer;
}
.danger-btn:hover:not(:disabled) { background: #7E2A20; }
.danger-btn:disabled { opacity: .55; cursor: not-allowed; }
.oscar-signup.is-closed .form-row input[type=text],
.oscar-signup.is-closed .form-row textarea {
background: #F0EDE6; color: #9A9488; cursor: not-allowed;
}
.oscar-signup.is-closed .radio { opacity: .55; cursor: not-allowed; }
&lt;/style&gt;
&lt;script&gt;
(function () {
const root = document.getElementById('oscar-signup');
if (!root) return;
const EVENT = root.dataset.event;
const API = root.dataset.api;
const DATA_URL = 'https://raw.githubusercontent.com/oscar-open-source-book/signups-data/main/' + EVENT + '.json';
const $ = (id) =&gt; document.getElementById(id);
let _deadline = '';
function setDeadline(v) {
if (!v) return;
_deadline = v;
applyClosedState(isExpired());
}
function isExpired() {
if (!_deadline) return false;
const t = Date.parse(_deadline);
return !isNaN(t) &amp;&amp; Date.now() &gt;= t;
}
function applyClosedState(expired) {
const box = $('signup-closed');
const form = $('signup-form');
if (!box) return;
if (!expired) {
box.hidden = true;
root.classList.remove('is-closed');
if (form) form.classList.remove('is-closed');
return;
}
box.hidden = false;
root.classList.add('is-closed');
if (form) {
form.classList.add('is-closed');
form.querySelectorAll('input, textarea, button').forEach(el =&gt; { el.disabled = true; });
}
const sub = $('closed-sub');
if (sub &amp;&amp; _deadline) {
const d = new Date(_deadline);
const pad = (n) =&gt; String(n).padStart(2, '0');
sub.textContent = '本场报名已于 ' + d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' +
pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ' 截止。';
}
}
function setStats(s) {
if (!s) return;
const total = s.total ?? 0;
$('stat-total').textContent = total;
$('stat-dinner').textContent = s.dinner ?? 0;
const vEl = $('stat-venue');
vEl.textContent = '智源大厦';
vEl.style.fontSize = '1.1rem';
updateVenue(total);
}
function updateVenue(total) {
const hint = $('venue-hint');
const text = $('venue-text');
hint.className = 'venue-hint is-confirmed';
if (total &gt;= 1) {
text.innerHTML = `&lt;strong&gt;场地：智源大厦（海淀区成府路 150 号楼）&lt;/strong&gt;&lt;br&gt;已报名 ${total} 人。具体楼层与集合方式将在活动前单独通知。`;
} else {
text.innerHTML = '&lt;strong&gt;场地：智源大厦（海淀区成府路 150 号楼）&lt;/strong&gt;';
}
}
function renderList(rows) {
const wrap = $('signup-list');
const ul = $('signup-names');
rows = (rows || []).filter(function (row) { return !row.cancelled; });
if (!rows.length) { wrap.hidden = true; return; }
wrap.hidden = false;
ul.innerHTML = rows.map(r =&gt; {
const tags = [];
if (r.dinner === 'yes' || r.dinner === true) tags.push('聚餐');
const t = tags.length ? `&lt;span class="tag"&gt;${tags.join(' · ')}&lt;/span&gt;` : '';
return `&lt;li&gt;${escapeHtml(r.name)}${t}&lt;/li&gt;`;
}).join('');
}
function escapeHtml(s) {
return String(s || '').replace(/[&amp;&lt;&gt;"']/g, c =&gt;
({ '&amp;': '&amp;amp;', '&lt;': '&amp;lt;', '&gt;': '&amp;gt;', '"': '&amp;quot;', "'": '&amp;#39;' }[c]));
}
async function refresh() {
try {
const r = await fetch(DATA_URL + '?t=' + Date.now(), { cache: 'no-store' });
if (r.ok) {
const d = await r.json();
if (d &amp;&amp; Array.isArray(d.signups)) {
if (d.deadline) setDeadline(d.deadline);
const live = d.signups.filter(function (row) { return !row.cancelled; });
setStats(computeStats(live));
renderList(live);
return;
}
}
} catch (_) { }
try {
const r = await fetch(API + '?event=' + encodeURIComponent(EVENT), { cache: 'no-store' });
const d = await r.json();
if (d.ok) {
if (d.deadline) setDeadline(d.deadline);
setStats(d.stats);
renderList(d.signups);
if (typeof d.expired === 'boolean' &amp;&amp; d.expired) applyClosedState(true);
}
} catch (e) { }
}
function computeStats(rows) {
const live = (rows || []).filter(function (r) { return !r.cancelled; });
let dinner = 0;
live.forEach(r =&gt; { if (r.dinner === 'yes' || r.dinner === true) dinner++; });
return { total: live.length, dinner: dinner };
}
$('signup-form').addEventListener('submit', async (ev) =&gt; {
ev.preventDefault();
const form = ev.currentTarget;
const btn = $('sf-submit');
const msg = $('sf-msg');
btn.disabled = true;
btn.textContent = '提交中…';
msg.className = 'form-msg';
msg.textContent = '提交中…';
function showMsg(kind, html) {
msg.className = 'form-msg ' + kind;
msg.innerHTML = html;
try {
msg.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (_) {
msg.scrollIntoView();
}
try {
sessionStorage.setItem('oscar_signup_msg', JSON.stringify({
kind: kind, html: html, ts: Date.now()
}));
} catch (_) {}
}
const fd = new FormData(form);
const payload = {
event: EVENT,
name: (fd.get('name') || '').trim(),
contact: (fd.get('contact') || '').trim(),
mode: 'offline',
dinner: fd.get('dinner') || 'no',
reason: (fd.get('reason') || '').trim()
};
try {
const r = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const d = await r.json();
if (d.ok) {
if (d.duplicate) {
if (d.name_conflict) {
showMsg('err', '该称呼已有报名记录。'
+ '&lt;br&gt;&lt;span style="font-size:.9em"&gt;'
+ '· 若是您本人要改信息 → 用页面下方「修改报名」&lt;br&gt;'
+ '· 若您与已报名者同名但并非同一人 → 请在称呼后加括号区分，'
+ '如「张三（另一位）」，或直接联系组织者'
+ '&lt;/span&gt;');
} else {
showMsg('ok', `该联系方式已报名过，编号 &lt;code&gt;${escapeHtml(d.signup_no)}&lt;/code&gt;。`);
}
} else {
showMsg('ok', `✓ 报名成功！你的报名编号：&lt;code&gt;${escapeHtml(d.signup_no)}&lt;/code&gt;&lt;br&gt;&lt;span style="font-size:.85em;color:#6B6B6B"&gt;如需修改或取消，可在页面下方凭姓名与联系方式自助操作。&lt;/span&gt;`);
setTimeout(() =&gt; { try { form.reset(); } catch (_) {} }, 300);
}
if (d.stats) setStats(d.stats);
refresh();
} else if (d.expired) {
applyClosedState(true);
showMsg('err', '活动已开始，报名截止。');
} else {
showMsg('err', '提交失败：' + (d.error || r.status));
}
} catch (e) {
showMsg('err', '网络错误：' + (e.message || e));
} finally {
btn.disabled = false;
btn.textContent = '提交报名';
}
});
applyClosedState(isExpired());
(function restoreMsg() {
const m = $('sf-msg');
if (!m) return;
try {
const raw = sessionStorage.getItem('oscar_signup_msg');
if (!raw) return;
const o = JSON.parse(raw);
if (!o || !o.html) return;
if (Date.now() - (o.ts || 0) &gt; 60000) { sessionStorage.removeItem('oscar_signup_msg'); return; }
m.className = 'form-msg ' + (o.kind || 'ok');
m.innerHTML = o.html;
setTimeout(() =&gt; {
try { m.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (_) { m.scrollIntoView(); }
}, 300);
} catch (_) {}
})();
refresh();
setInterval(refresh, 30000);
})();
&lt;/script&gt;
&lt;script&gt;
(function () {
var boot = document.getElementById('oscar-signup');
if (!boot) return;
var CFG = {
event: boot.dataset.event,
api: boot.dataset.api,
state: { verified: false, no: '', contact: '' }
};
window.__oscarEditCfg = CFG;
function byId(elId) {
return document.getElementById(elId);
}
function showBox(elId, text, kind) {
var el = byId(elId);
if (!el) return;
el.className = 'form-msg ' + (kind || '');
el.textContent = text || '';
}
function expired() {
var box = byId('signup-closed');
if (box &amp;&amp; !box.hidden) return true;
return false;
}
var toggle = byId('edit-toggle');
if (toggle) {
toggle.addEventListener('click', function () {
if (toggle.disabled || expired()) return;
var panel = byId('edit-panel');
var open = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', open ? 'false' : 'true');
if (panel) panel.hidden = open;
});
}
var verifyBtn = byId('ef-verify-btn');
if (verifyBtn) {
verifyBtn.addEventListener('click', function () {
if (expired()) {
showBox('ef-msg', '报名已截止，无法修改。', 'err');
return;
}
var noVal = (byId('ef-no') || {}).value || '';
var ctVal = (byId('ef-contact') || {}).value || '';
noVal = noVal.trim();
ctVal = ctVal.trim();
if (!noVal || !ctVal) {
showBox('ef-msg', '请填写姓名和联系方式。', 'err');
return;
}
verifyBtn.disabled = true;
verifyBtn.textContent = '验证中…';
var url = CFG.api + '/edit?event=' + encodeURIComponent(CFG.event) +
'&amp;signup_no=' + encodeURIComponent(noVal) +
'&amp;contact=' + encodeURIComponent(ctVal);
fetch(url, { cache: 'no-store' })
.then(function (r) { return r.json(); })
.then(function (d) {
if (!d || !d.ok) {
if (d &amp;&amp; d.expired) {
showBox('ef-msg', '报名已截止，无法修改。', 'err');
return;
}
showBox('ef-msg', (d &amp;&amp; d.error) || '验证失败', 'err');
return;
}
CFG.state.verified = true;
CFG.state.no = noVal;
CFG.state.contact = ctVal;
byId('ef-name').textContent = d.name || '—';
byId('ef-no-show').textContent = d.signup_no || noVal;
var nameInput = byId('ef-name-input');
if (nameInput) nameInput.value = d.name || '';
var reasonEl = byId('ef-reason');
if (reasonEl) reasonEl.value = d.reason || '';
var radios = document.querySelectorAll('input[name="ef-dinner"]');
for (var i = 0; i &lt; radios.length; i++) {
radios[i].checked = (radios[i].value === (d.dinner || 'no'));
}
byId('edit-verify').hidden = true;
byId('edit-form').hidden = false;
})
.catch(function (e) {
showBox('ef-msg', '网络错误：' + (e &amp;&amp; e.message ? e.message : e), 'err');
})
.finally(function () {
verifyBtn.disabled = false;
verifyBtn.textContent = '验证并载入';
});
});
}
var editForm = byId('edit-form');
if (editForm) {
editForm.addEventListener('submit', function (ev) {
ev.preventDefault();
if (!CFG.state.verified) {
showBox('ef-msg2', '请先验证身份。', 'err');
return;
}
if (expired()) {
showBox('ef-msg2', '报名已截止，无法修改。', 'err');
return;
}
var saveBtn = byId('ef-save-btn');
saveBtn.disabled = true;
saveBtn.textContent = '保存中…';
showBox('ef-msg2', '保存中…', '');
var picked = document.querySelector('input[name="ef-dinner"]:checked');
var newName = ((byId('ef-name-input') || {}).value || '').trim();
if (!newName) {
showBox('ef-msg2', '称呼不能为空。', 'err');
saveBtn.disabled = false;
saveBtn.textContent = '保存修改';
return;
}
var payload = {
event: CFG.event,
signup_no: CFG.state.no,
contact: CFG.state.contact,
name: newName,
dinner: picked ? picked.value : 'no',
reason: ((byId('ef-reason') || {}).value || '').trim()
};
fetch(CFG.api + '/edit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(function (r) { return r.json(); })
.then(function (d) {
if (d &amp;&amp; d.ok) {
showBox('ef-msg2', '✓ 已保存。公开名单上的聚餐标记已同步更新。', 'ok');
setTimeout(function () {
var panel = byId('edit-panel');
var tg = byId('edit-toggle');
if (panel) panel.hidden = true;
if (tg) tg.setAttribute('aria-expanded', 'false');
byId('edit-form').hidden = true;
byId('edit-verify').hidden = false;
showBox('ef-msg', '修改已保存。', 'ok');
}, 900);
} else if (d &amp;&amp; d.expired) {
showBox('ef-msg2', '报名已截止，无法修改。', 'err');
} else {
showBox('ef-msg2', (d &amp;&amp; d.error) || '保存失败', 'err');
}
})
.catch(function (e) {
showBox('ef-msg2', '网络错误：' + (e &amp;&amp; e.message ? e.message : e), 'err');
})
.finally(function () {
saveBtn.disabled = false;
saveBtn.textContent = '保存修改';
});
});
}
var cancelBtn = byId('ef-cancel-btn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function () {
var panel = byId('edit-panel');
var tg = byId('edit-toggle');
if (panel) panel.hidden = true;
if (tg) tg.setAttribute('aria-expanded', 'false');
byId('edit-form').hidden = true;
byId('edit-verify').hidden = false;
CFG.state.verified = false;
showBox('ef-msg', '', '');
showBox('ef-msg2', '', '');
var cc = byId('ef-cancel-confirm');
if (cc) cc.hidden = true;
showBox('ef-msg3', '', '');
});
}
var cancelLink = byId('ef-cancel-signup-btn');
if (cancelLink) {
cancelLink.addEventListener('click', function () {
var box = byId('ef-cancel-confirm');
if (box) box.hidden = false;
cancelLink.hidden = true;
});
}
var cancelNo = byId('ef-cancel-no');
if (cancelNo) {
cancelNo.addEventListener('click', function () {
var box = byId('ef-cancel-confirm');
if (box) box.hidden = true;
if (cancelLink) cancelLink.hidden = false;
});
}
var cancelYes = byId('ef-cancel-yes');
if (cancelYes) {
cancelYes.addEventListener('click', function () {
if (!CFG.state.verified) {
showBox('ef-msg3', '请先验证身份。', 'err');
return;
}
if (expired()) {
showBox('ef-msg3', '报名已截止，无法取消。', 'err');
return;
}
cancelYes.disabled = true;
cancelYes.textContent = '取消中…';
showBox('ef-msg3', '正在处理…', '');
fetch(CFG.api + '/cancel', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: CFG.event,
signup_no: CFG.state.no,
contact: CFG.state.contact
})
})
.then(function (r) { return r.json(); })
.then(function (d) {
if (d &amp;&amp; d.ok) {
showBox('ef-msg3', d.already ? '你的报名已经取消过了。'
: '✓ 已取消报名，名字已从名单移除。如需再来，可重新报名。', 'ok');
var cc = byId('ef-cancel-confirm');
if (cc) cc.hidden = true;
setTimeout(function () {
try {
location.reload();
} catch (_) {
location.href = location.href;
}
}, 1200);
} else if (d &amp;&amp; d.expired) {
showBox('ef-msg3', '报名已截止，无法取消。', 'err');
} else {
showBox('ef-msg3', (d &amp;&amp; d.error) || '取消失败', 'err');
}
})
.catch(function (e) {
showBox('ef-msg3', '网络错误：' + (e &amp;&amp; e.message ? e.message : e), 'err');
})
.finally(function () {
cancelYes.disabled = false;
cancelYes.textContent = '确定取消报名';
});
});
}
})();
&lt;/script&gt;
&lt;div class="sp-agenda"&gt;
&lt;h2&gt;本期议程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;签到、寒暄、Network&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Linux Kernel 35 周年的报告解读 ——《三十五载的自发秩序：Linux Kernel 35 周年报告深度解构》&lt;/li&gt;
&lt;li&gt;开源项目是否是 Public good？治理与法理视角的博弈&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;场域与权力：布迪厄能给开源分析什么？&lt;/strong&gt; —— 唯真分享 + 现场对话&lt;/li&gt;
&lt;li&gt;COSCon'26 的阅读 BoF 策划探讨&lt;/li&gt;
&lt;li&gt;拍照合影&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓冲时段&lt;/strong&gt;（应对超时；自由交流）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简易晚餐 · Social&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="sp-links"&gt;
&lt;a href="https://osbook.opensourceway.blog/events/2026-10-31-beijing-reading-meetup/" class="sp-link"&gt;📖 查看完整活动详情&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.signup-page { max-width: 720px; margin: 0 auto; padding: 0 1rem 3rem; }
.sp-hero { text-align: center; padding: 2.5rem 1rem 1.5rem; }
.sp-kicker {
font-size: .75rem; letter-spacing: .22em; text-transform: uppercase;
color: #B4A078; margin-bottom: .9rem;
}
.sp-hero h1 {
font-size: 2rem; font-weight: 700; letter-spacing: .05em;
color: #2C2A26; margin: 0 0 .75rem; line-height: 1.35;
}
.sp-meta { font-size: .95rem; color: #6B6B6B; margin: 0 0 1.5rem; }
.sp-banner {
display: inline-block; padding: .6rem 1.2rem;
background: #F5F1E8; border: 1px solid #E8E2D4; border-radius: 100px;
font-size: .85rem; color: #6B6B6B;
}
.sp-banner b { color: #8B6914; }
/* 覆盖 shortcode 内联样式的容器边距，让独立页更宽 */
.signup-page .oscar-signup { margin: 1rem 0 2rem; }
.sp-agenda { margin: 2.5rem 0; padding: 1.5rem 1.75rem; background: #fff; border: 1px solid #E8E2D4; border-radius: 12px; }
.sp-agenda h2 { font-size: 1.2rem; color: #8B6914; margin: 0 0 1rem; }
.sp-agenda ol { margin: 0; padding-left: 1.3rem; }
.sp-agenda li { margin-bottom: .9rem; font-size: .92rem; line-height: 1.65; color: #2C2A26; }
.sp-agenda li:last-child { margin-bottom: 0; }
.sp-agenda strong { color: #1B3B6B; }
.sp-links { text-align: center; margin-top: 2rem; }
.sp-link {
display: inline-block; padding: .65rem 1.6rem;
border: 1px solid #B4A078; border-radius: 8px;
color: #8B6914; text-decoration: none; font-size: .9rem;
transition: background .15s;
}
.sp-link:hover { background: #F5F1E8; }
&lt;/style&gt;</description></item></channel></rss>