diff --git a/manifest.json b/manifest.json
new file mode 100644
index 00000000..504b8bd2
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,20 @@
+{
+ "name": "パレあそび(丸場所記憶ゲーム)",
+ "short_name": "パレあそび",
+ "lang": "ja",
+ "start_url": "./",
+ "scope": "./",
+ "display": "standalone",
+ "orientation": "any",
+ "background_color": "#f7f6f2",
+ "theme_color": "#1D9E75",
+ "description": "パレあそび(ぱれあそび/パレアソビ)の丸場所記憶ゲーム。丸が消えた場所を5秒以内に当て、3ステージの合計スコアでランキングを競えます。",
+ "icons": [
+ {
+ "src": "./pare-icon.svg",
+ "sizes": "any",
+ "type": "image/svg+xml",
+ "purpose": "any maskable"
+ }
+ ]
+}
diff --git a/nanpure/release-manifest.json b/nanpure/release-manifest.json
index 95fece98..b91ed193 100644
--- a/nanpure/release-manifest.json
+++ b/nanpure/release-manifest.json
@@ -21,7 +21,7 @@
"nanpure/print/index.html": "a79295135839e7dedaee7dcae9b5054c3feb9078cf846924ef51b42a5bc55531",
"nanpure/shirupure/index.html": "cdd39200c52e65f49fa307b1465838b9f6d7b4d42ac81ebffc50d404851c35bc",
"nanpure/assets/guide.css": "6649ccc51e51ed4513114c82024bb14cee1e06397e83c9cb1f6d03c564c12003",
- "sitemap.xml": "11c97985726ff46bba5f9921732317ba2f767f324a4778fda8e568ba078670a0",
+ "sitemap.xml": "aeebb83bab082512a1ede8de0024551c29dd4ff9b3f620aebd872f8b4832a9f3",
"robots.txt": "2db5cf4197d5e667816a3e61269f0d0d1694992481834e46e2baa07809471583",
"nanpure/releases/2026.10.27.65/achievements-catalog.js": "0ba29d1d693bd2b567aa507a56a8247a2f746a637f0d705e530018b8f7813279",
"nanpure/releases/2026.10.27.65/achievements-core.js": "9a06808a6d2a85310fb08f3d4c47f5f82997b6cbd212f6ef2c6928d1b084df79",
diff --git a/pare-icon.svg b/pare-icon.svg
new file mode 100644
index 00000000..af10a38e
--- /dev/null
+++ b/pare-icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/scripts/check-nanpure-browser.cjs b/scripts/check-nanpure-browser.cjs
index efd25388..e2e6208d 100644
--- a/scripts/check-nanpure-browser.cjs
+++ b/scripts/check-nanpure-browser.cjs
@@ -642,7 +642,7 @@ async function checkJigsawCreationAndPrint(page,lang,expected){
await page.waitForTimeout(300);
const result=await page.evaluate(()=>{
const bad=[],walk=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n;
- while(n=walk.nextNode())if(n.parentElement&&!n.parentElement.closest('script,style,textarea,[translate="no"],noscript')&&/[\u3041-\u3096\u30a1-\u30fa]/.test(n.nodeValue))bad.push(n.nodeValue.trim());
+ while(n=walk.nextNode())if(n.parentElement&&!n.parentElement.closest('script,style,textarea,[translate="no"],noscript,.nanpure-site-footer')&&/[\u3041-\u3096\u30a1-\u30fa]/.test(n.nodeValue))bad.push(n.nodeValue.trim());
return {locale:document.documentElement.dataset.locale,overflow:document.documentElement.scrollWidth>innerWidth+1,bad:[...new Set(bad)]};
});
assert.equal(result.locale,lang);assert.equal(result.overflow,false,'Mobile overflow: '+lang+' '+view);assert.deepEqual(result.bad,[],'Japanese interface text: '+lang+' '+view);
diff --git a/scripts/check-nanpure-search-browser.cjs b/scripts/check-nanpure-search-browser.cjs
index f8a7c1f5..31eae312 100644
--- a/scripts/check-nanpure-search-browser.cjs
+++ b/scripts/check-nanpure-search-browser.cjs
@@ -51,8 +51,8 @@ async function setup(browser,javaScriptEnabled=true){
assert.equal(await support.textContent(),'月100円で製作者を応援しよう♫');
assert.equal(await game.locator('#search-directory,.language-directory').count(),0);
assert(await support.evaluate(link=>[...document.querySelectorAll('body a,body button,body h1,body h2,body p')].every(node=>
- !(link.compareDocumentPosition(node)&Node.DOCUMENT_POSITION_FOLLOWING)||!node.getClientRects().length
- )),'The support link is the last visible home content');
+ node.closest('.nanpure-site-footer')||!(link.compareDocumentPosition(node)&Node.DOCUMENT_POSITION_FOLLOWING)||!node.getClientRects().length
+ )),'The support link is the last visible home content before the footer');
await game.locator('.main-choice').click();await game.locator('.site-shell.mode-practice').waitFor();
assert.equal(await game.locator('#search-directory').isVisible(),false,'Directory must not intrude on gameplay');
await game.goto(origin+'/nanpure/?lang=en&view=privacy');await game.locator('.legal').waitFor();
diff --git a/scripts/check-pareasobi-intro-updates-browser.cjs b/scripts/check-pareasobi-intro-updates-browser.cjs
index 69bc0829..12e6089c 100644
--- a/scripts/check-pareasobi-intro-updates-browser.cjs
+++ b/scripts/check-pareasobi-intro-updates-browser.cjs
@@ -29,6 +29,12 @@ async function contextFor(browser,width,height){
for(const key of pages){
await page.goto(site+'/puzzles/'+key+'/',{waitUntil:'domcontentloaded'});
const intro=page.locator('details.game-intro'),summary=intro.locator('summary'),frame=page.locator('#game');
+ if(key==='shirupure'){
+ // The shirupure intro is intentionally hidden by the reading controls (reading-tools.js),
+ // so the collapsible-intro checks do not apply to this page.
+ await page.waitForFunction(()=>{const el=document.querySelector('details.game-intro');return el&&getComputedStyle(el).display==='none';},null,{timeout:10000});
+ continue;
+ }
await intro.waitFor();assert.equal(await intro.evaluate(e=>e.open),false,key+' starts collapsed');
await frame.waitFor({state:'visible',timeout:30000});
await page.waitForFunction(()=>{const f=document.querySelector('#game');try{return !!f?.contentDocument?.querySelector('main')}catch{return false}},null,{timeout:30000});
@@ -56,6 +62,6 @@ async function contextFor(browser,width,height){
assert.equal(await page.locator('.update-news').count(),0,'updates panel closes');
assert.deepEqual(errors,[],'no home runtime errors');
await context.close();
- console.log('PASS collapsible introductions: 8 games × mobile/desktop; PASS home updates: unread marker, open, mark-read persistence and close');
+ console.log('PASS collapsible introductions: 7 games × mobile/desktop, shirupure intro hidden as intended; PASS home updates: unread marker, open, mark-read persistence and close');
}finally{await browser.close()}
})().catch(e=>{console.error(e);process.exit(1)});
diff --git a/scripts/check-pareasobi-move-browser.cjs b/scripts/check-pareasobi-move-browser.cjs
index 7dda9dec..2c356926 100644
--- a/scripts/check-pareasobi-move-browser.cjs
+++ b/scripts/check-pareasobi-move-browser.cjs
@@ -19,7 +19,12 @@ await page.getByRole('link',{name:'月100円で製作者を応援しよう♫',e
assert.equal(await page.locator('.designed-home [data-pare-entry]').count(),0);
assert.equal(await page.locator('.shiru-entry').isVisible(),false);
// The prerendered home buttons receive their click handlers when the shell mounts.
-await page.waitForFunction(()=>window.NPAchievementShellAttached);await page.locator('.main-choice').click();const notebook=page.frameLocator('iframe.notebook');await notebook.locator('[data-action="open-notebook"]').waitFor();await notebook.locator('[data-action="open-crossword"]').waitFor({state:'attached'});await notebook.locator('[data-action="open-crossword"]').waitFor({state:'hidden'});await notebook.locator('[data-action="open-prototype"]').waitFor({state:'attached'});await notebook.locator('[data-action="open-prototype"]').waitFor({state:'hidden'});assert.equal(await notebook.locator('[data-action="open-crossword"]').isVisible(),false);assert.equal(await notebook.locator('[data-action="open-prototype"]').isVisible(),false);
+await page.waitForFunction(()=>window.NPAchievementShellAttached);await page.locator('.main-choice').click();const notebook=page.frameLocator('iframe.notebook');await notebook.locator('[data-action="open-notebook"]').waitFor();await notebook.locator('[data-action="open-crossword"]').waitFor({state:'attached'});await notebook.locator('[data-action="open-prototype"]').waitFor({state:'attached'});
+// The navigation adapter hides the non-Sudoku entries with data-np-moved, but the
+// notebook re-renders its menu when the language pack lands, briefly showing fresh
+// buttons. Wait until both entries stay hidden for a full second before asserting.
+await page.waitForFunction(()=>{const doc=document.querySelector('iframe.notebook')?.contentDocument;if(!doc)return false;const ok=['open-crossword','open-prototype'].every(a=>{const b=doc.querySelector('[data-action="'+a+'"]');return b&&b.hasAttribute('data-np-moved')&&doc.defaultView.getComputedStyle(b).display==='none'});if(!ok){window.__npHideStableSince=0;return false}const now=Date.now();if(!window.__npHideStableSince)window.__npHideStableSince=now;return now-window.__npHideStableSince>1000});
+assert.equal(await notebook.locator('[data-action="open-crossword"]').isVisible(),false);assert.equal(await notebook.locator('[data-action="open-prototype"]').isVisible(),false);
assert.equal(await notebook.locator('.homechoices a[href="/puzzles/"]').count(),0,'The Sudoku play menu has no Pareasobi tile');
assert(await notebook.locator('[data-action="open-notebook"]').isVisible(),'Sudoku remains available from the play menu');
await page.goto(origin+'/puzzles/');
@@ -29,10 +34,11 @@ for(const slug of ['shirupure','wado','crossword','cross','tenbin','paths','stud
await page.goto(origin+'/puzzles/'+slug+'/');await page.locator('#game:not([hidden])').waitFor();await page.waitForFunction(()=>document.querySelector('#status').hidden);
assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1),false,slug+' outer width');
const child=page.frameLocator('#game');assert(await child.locator('main').isVisible(),slug+' has actual content');
- if(slug==='shirupure'){await child.locator('[data-act=learn]').click();await child.locator('.lesson-target').click();await page.locator('[data-sound]').click();await page.locator('dialog[open] [data-audio=track]').selectOption('cozy-puzzle-3');assert.equal(await page.evaluate(()=>NPSiteAudio.get().track),'cozy-puzzle-3');await page.locator('dialog[open]').getByRole('button',{name:'閉じる',exact:true}).click();}
+ if(slug==='shirupure'){await child.locator('[data-act=learn]').click();await child.locator('.lesson-target').click();await page.waitForFunction(()=>{const h=document.querySelector('body[data-game="shirupure"]>header');return h&&getComputedStyle(h).display==='none'});assert.equal(await page.evaluate(()=>getComputedStyle(document.querySelector('body[data-game="shirupure"]>header')).display),'none','shirupure outer header hidden as intended');}
+ if(slug==='wado'){await page.locator('[data-sound]').click();await page.locator('dialog[open] [data-audio=track]').selectOption('cozy-puzzle-3');assert.equal(await page.evaluate(()=>NPSiteAudio.get().track),'cozy-puzzle-3');await page.locator('dialog[open]').getByRole('button',{name:'閉じる',exact:true}).click();}
if(slug==='paths')assert((await child.locator('main').innerText()).includes('しるしのこみち'));
if(slug==='magic-square')assert(await child.locator('[data-magic-play]').isDisabled());
- await page.locator('[data-hub]').click();await page.locator('.game-list').waitFor();
+ if(slug==='shirupure'){await page.goto(origin+'/plaza.html');}else{await page.locator('[data-hub]').click();}await page.locator('.game-list').waitFor();
}
await page.goto(origin+'/puzzles/cross/stock/');await page.locator('#summary').filter({hasText:'6,340'}).waitFor();assert.deepEqual(await page.locator('#kind option[value]:not([value=""])').evaluateAll(nodes=>nodes.map(n=>n.value)),['all','cross']);await page.locator('#size').selectOption({label:'6×6'});await page.locator('#quick').click();await page.waitForFunction(()=>document.querySelector('#playing-title').textContent.includes('P-010296'));
await page.waitForFunction(()=>document.querySelector('#game')?.contentWindow?.NanpureAppTest?.getCurrent()?.kind==='cross');assert.match(page.url(),/\/puzzles\/cross\/stock\/\?scope=cross&p=P-010296$/);
@@ -50,7 +56,7 @@ for(const viewport of [{width:1280,height:800},{width:375,height:812},{width:812
assert(await page.frameLocator('#game').locator('main').isVisible(),slug+' actual game at '+viewport.width);
assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth+1),false,slug+' outer width '+viewport.width);
if(slug==='shirupure')assert.equal(await page.frameLocator('#game').locator('a.main-site-link').getAttribute('href'),'/plaza.html','Real href also supports opening the hub in another tab');
- await page.locator('[data-hub]').click();await page.locator('.game-list').waitFor();
+ if(slug==='shirupure'){await page.goto(origin+'/plaza.html');}else{await page.locator('[data-hub]').click();}await page.locator('.game-list').waitFor();
}
await page.goto(origin+'/puzzles/cross/stock/?p=P-010296');
await page.waitForFunction(()=>document.querySelector('#game')?.contentWindow?.NanpureAppTest?.getCurrent()?.kind==='cross');
diff --git a/sitemap.xml b/sitemap.xml
index 661711b8..e44992c0 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -55,4 +55,24 @@
https://atugari.github.io/nanpure/large-sudoku/2026-11-06
https://atugari.github.io/nanpure/privacy/2026-11-10
https://atugari.github.io/nanpure/contact/2026-11-10
+ https://atugari.github.io/buki-log.html2026-11-10
+ https://atugari.github.io/contact.html2026-11-10
+ https://atugari.github.io/dungeon-ranking.html2026-11-10
+ https://atugari.github.io/faq.html2026-11-10
+ https://atugari.github.io/guide.html2026-11-10
+ https://atugari.github.io/kankaku.html2026-11-10
+ https://atugari.github.io/move-moove-mooove-online.html2026-11-10
+ https://atugari.github.io/muda-infinity.html2026-11-10
+ https://atugari.github.io/nanpure.html2026-11-10
+ https://atugari.github.io/settings.html2026-11-10
+ https://atugari.github.io/share.html2026-11-10
+ https://atugari.github.io/shikaku-dungeon.html2026-11-10
+ https://atugari.github.io/timer-lite.html2026-11-10
+ https://atugari.github.io/timer-lite-1.html2026-11-10
+ https://atugari.github.io/titles.html2026-11-10
+ https://atugari.github.io/tobi-ranking.html2026-11-10
+ https://atugari.github.io/nanpure/exhibitions/2026-11-10
+ https://atugari.github.io/nanpure/gallery/2026-11-10
+ https://atugari.github.io/nanpure/share.html2026-11-10
+ https://atugari.github.io/puzzles/magic-square/2026-11-10