Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/app-discovery-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
- support/twotaku/index.html
- support/debate-me/index.html
- support/idea-market/index.html
- support/blur/index.html
- tests/blur-support-growth.test.mjs
- tests/idea-market-support-growth.test.mjs
- tests/debate-me-support-growth.test.mjs
- tests/dotchi-support-growth.test.mjs
Expand All @@ -36,6 +38,8 @@ on:
- support/twotaku/index.html
- support/debate-me/index.html
- support/idea-market/index.html
- support/blur/index.html
- tests/blur-support-growth.test.mjs
- tests/idea-market-support-growth.test.mjs
- tests/debate-me-support-growth.test.mjs
- tests/dotchi-support-growth.test.mjs
Expand Down Expand Up @@ -71,4 +75,4 @@ jobs:
- name: Verify non-destructive release scripts have valid shell syntax
run: bash -n scripts/publish_app_discovery.sh scripts/check_app_discovery_live.sh
- name: Verify search, referral links and guide listings
run: node --test tests/catalog-growth.test.mjs tests/search-landing-growth.test.mjs tests/dotchi-support-growth.test.mjs tests/debate-me-support-growth.test.mjs tests/idea-market-support-growth.test.mjs
run: node --test tests/catalog-growth.test.mjs tests/search-landing-growth.test.mjs tests/dotchi-support-growth.test.mjs tests/debate-me-support-growth.test.mjs tests/idea-market-support-growth.test.mjs tests/blur-support-growth.test.mjs
8 changes: 8 additions & 0 deletions scripts/check_app_discovery_live.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ grep -Fq 'support_idea_market_hero' "$tmp/idea-market.html" || { echo 'FAIL Idea
if grep -Eq 'voice into a personal diary|Voice-to-text transcription|AI Transcription' "$tmp/idea-market.html"; then
echo 'FAIL Idea Market page still describes a voice diary' >&2; exit 1
fi
# Blur listing must show a real single-player image-reveal experience.
fetch '/support/blur/' 'blur.html'
grep -Fq '<h1>Blur: Guess the Image</h1>' "$tmp/blur.html" || { echo 'FAIL Blur support landing missing' >&2; exit 1; }
grep -Fq 'data-position="hero"' "$tmp/blur.html" || { echo 'FAIL Blur first-screen Play CTA not live' >&2; exit 1; }
grep -Fq 'support_blur_hero' "$tmp/blur.html" || { echo 'FAIL Blur Play campaign missing' >&2; exit 1; }
if grep -Eq 'Multiplayer mode|Multiple difficulty levels|Timed challenges' "$tmp/blur.html"; then
echo 'FAIL Blur advertises unverified game modes' >&2; exit 1
fi
fetch '/robots.txt' 'robots.txt'
grep -Fq 'Allow: /icons/apps/' "$tmp/robots.txt" || { echo 'FAIL Google image crawl allow not live' >&2; exit 1; }
echo 'PASS: catalog, guides, priority search landings, store CTAs, icon crawl policy and sitemap current (HTTP 200)'
2 changes: 1 addition & 1 deletion scripts/publish_app_discovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ root="${APP_SITE_DEPLOY_ROOT:-/var/www/html}"
if [[ ! "$host" =~ ^[a-zA-Z0-9_.@-]+$ || ! "$root" =~ ^/var/www/[a-zA-Z0-9_./-]+$ ]]; then
echo 'Invalid APP_SITE_DEPLOY_HOST or APP_SITE_DEPLOY_ROOT' >&2; exit 2
fi
files=(index.html apps.html sitemap.xml robots.txt guides/quick-games/index.html guides/daily-habits/index.html guides/decision-helpers/index.html guides/ai-tools/index.html support/brain-burst/index.html support/snaplingo/index.html support/face-reaction-battle/index.html support/twotaku/index.html support/debate-me/index.html support/idea-market/index.html)
files=(index.html apps.html sitemap.xml robots.txt guides/quick-games/index.html guides/daily-habits/index.html guides/decision-helpers/index.html guides/ai-tools/index.html support/brain-burst/index.html support/snaplingo/index.html support/face-reaction-battle/index.html support/twotaku/index.html support/debate-me/index.html support/idea-market/index.html support/blur/index.html)
for file in "${files[@]}"; do
[[ -s "$file" ]] || { echo "Missing source file: $file" >&2; exit 2; }
done
Expand Down
9 changes: 5 additions & 4 deletions support/blur/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
if (!a) return;
var href = a.getAttribute('href') || '';
if (/play\.google\.com/.test(href)) {
gtag('event', 'download_click', { store: 'google_play', location: 'support', link_url: href });
gtag('event', 'download_click', { store: 'google_play', location: 'support', placement: a.getAttribute('data-position') || 'other', link_url: href });
}
}, true);
</script>
Expand Down Expand Up @@ -104,19 +104,20 @@
<div class="hero-cat">Game</div>
<h1>Blur: Guess the Image</h1>
<p id="heroDesc">A fun image guessing game where pictures gradually become clearer. Can you guess before the reveal?</p>
<div class="download-btns" style="justify-content:center"><a href="https://play.google.com/store/apps/details?id=com.blurapp.blur_app&amp;utm_source=appdevelopsk&amp;utm_medium=web&amp;utm_campaign=support_blur_hero&amp;referrer=utm_source%3Dappdevelopsk%26utm_medium%3Dweb%26utm_campaign%3Dsupport_blur_hero" class="dl-btn dl-btn-play" data-position="hero" rel="noopener" target="_blank">Google Play</a></div>
</div>

<div class="container">
<div class="card">
<h2 data-i18n="features">Key Features</h2>
<div class="features-grid"><div class="feature"><div class="feature-dot"></div><span data-feat="0">Multiple difficulty levels</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="1">Various categories</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="2">Timed challenges</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="3">Multiplayer mode</span></div></div>
<div class="features-grid"><div class="feature"><div class="feature-dot"></div><span data-feat="0">Daily blurred-image guessing challenge</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="1">Reveal more of the picture step by step</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="2">Guess before the full image appears</span></div><div class="feature"><div class="feature-dot"></div><span data-feat="3">Keep playing in infinite mode</span></div></div>
</div>

<div class="card">
<h2 data-i18n="download">Download</h2>
<p data-i18n="download_desc">Available on Android. Download for free from Google Play and start using today.</p>
<div class="download-btns">
<a href="https://play.google.com/store/apps/details?id=com.blurapp.blur_app&amp;utm_source=appdevelopsk&amp;utm_medium=web&amp;utm_campaign=support_blur&amp;referrer=utm_source%3Dappdevelopsk%26utm_medium%3Dweb%26utm_campaign%3Dsupport_blur" class="dl-btn dl-btn-play" rel="noopener" target="_blank">Google Play</a>
<a href="https://play.google.com/store/apps/details?id=com.blurapp.blur_app&amp;utm_source=appdevelopsk&amp;utm_medium=web&amp;utm_campaign=support_blur&amp;referrer=utm_source%3Dappdevelopsk%26utm_medium%3Dweb%26utm_campaign%3Dsupport_blur" class="dl-btn dl-btn-play" data-position="details" rel="noopener" target="_blank">Google Play</a>
</div>
</div>

Expand Down Expand Up @@ -183,7 +184,7 @@ <h2 data-i18n="privacy">Privacy &amp; Data</h2>
const _oA=applyLang;applyLang=function(l){_oA(l);const t=APP_T[l]||APP_T.en;const h=document.getElementById('heroDesc');if(h&&t)h.textContent=t;};
(function(){const l=localStorage.getItem('sk_lang');const lang=l||detectLang();const t=APP_T[lang]||APP_T.en;const h=document.getElementById('heroDesc');if(h&&t)h.textContent=t;})();

const FEAT_T={en:{0:"Each image starts with maximum blur",1:"Tap \"Reveal\" to reduce the blur",2:"Press \"I Know!\" when you recognize",3:"Score \"Got it [Correct]\" or \"Missed",4:"The fewer reveals you use",5:"Daily Challenge"},ja:{0:"遊び方",1:"画像は最大限のぼかしからスタート",2:"「Reveal」をタップするたびにぼかしが軽減",3:"画像がわかったら「I Know!」を押す",4:"タップ回数が少ないほど高スコア!",5:"特徴"},zh:{0:"自然与风景",1:"动物",2:"食物与饮品",3:"建筑与城市",4:"名人",5:"物品与静物"},zh_TW:{0:"自然與風景",1:"動物",2:"食物與飲品",3:"建築與城市",4:"名人",5:"物品與靜物"},ko:{0:"자연과 풍경",1:"동물",2:"음식과 음료",3:"건축과 도시",4:"유명인",5:"사물과 정물"},es:{0:"Naturaleza y Paisajes",1:"Animales",2:"Comida y Bebida",3:"Arquitectura y Ciudades",4:"Personas Famosas",5:"Objetos y Naturaleza Muerta"},pt:{0:"Natureza e Paisagens",1:"Animais",2:"Comida e Bebida",3:"Arquitetura e Cidades",4:"Pessoas Famosas",5:"Objetos e Natureza Morta"},fr:{0:"Nature et Paysages",1:"Animaux",2:"Nourriture et Boissons",3:"Architecture et Villes",4:"Célébrités",5:"Objets et Natures Mortes"},de:{0:"Natur und Landschaften",1:"Tiere",2:"Essen und Trinken",3:"Architektur und Städte",4:"Berühmte Persönlichkeiten",5:"Objekte und Stillleben"},it:{0:"Natura e Paesaggi",1:"Animali",2:"Cibo e Bevande",3:"Architettura e Città",4:"Persone Famose",5:"Oggetti e Nature Morte"},ru:{0:"Природа и Пейзажи",1:"Животные",2:"Еда и Напитки",3:"Архитектура и Города",4:"Известные Личности",5:"Объекты и Натюрморты"},ar:{0:"الطبيعة والمناظر الطبيعية",1:"الحيوانات",2:"الطعام والشراب",3:"العمارة والمدن",4:"المشاهير",5:"الأشياء والطبيعة الصامتة"},th:{0:"ธรรมชาติและภูมิทัศน์",1:"สัตว์",2:"อาหารและเครื่องดื่ม",3:"สถาปัตยกรรมและเมือง",4:"บุคคลที่มีชื่อเสียง",5:"วัตถุและภาพนิ่ง"},id:{0:"Alam dan Pemandangan",1:"Hewan",2:"Makanan dan Minuman",3:"Arsitektur dan Kota",4:"Orang Terkenal",5:"Benda dan Alam Benda"},tr:{0:"Doğa ve Manzaralar",1:"Hayvanlar",2:"Yiyecek ve İçecek",3:"Mimari ve Şehirler",4:"Ünlü İnsanlar",5:"Nesneler ve Natürmort"},nl:{0:"Natuur en Landschappen",1:"Dieren",2:"Eten en Drinken",3:"Architectuur en Steden",4:"Bekende Personen",5:"Voorwerpen en Stillevens"},vi:{0:"Thiên nhiên và Phong cảnh",1:"Động vật",2:"Thức ăn và Đồ uống",3:"Kiến trúc và Thành phố",4:"Người nổi tiếng",5:"Đồ vật và Tĩnh vật"}};
const FEAT_T={en:{0:"Play the daily blurred-image puzzle",1:"Reveal the picture step by step",2:"Guess before the full reveal",3:"Keep guessing in infinite mode"},ja:{0:"毎日のぼかし画像クイズに挑戦",1:"画像を少しずつ鮮明にする",2:"全部見える前に答えを予想",3:"無限モードでも遊べる"},zh:{0:"挑战每日模糊图片谜题",1:"逐步揭示图片内容",2:"在完全显示前猜出答案",3:"在无限模式继续挑战"},zh_TW:{0:"挑戰每日模糊圖片謎題",1:"逐步揭示圖片內容",2:"在完全顯示前猜出答案",3:"在無限模式繼續挑戰"},ko:{0:"매일 흐릿한 이미지 퀴즈 도전",1:"사진을 단계별로 선명하게 보기",2:"완전히 공개되기 전에 정답 맞히기",3:"무한 모드로 계속 플레이"},es:{0:"Resuelve el acertijo diario de imagen borrosa",1:"Revela la imagen paso a paso",2:"Adivina antes de descubrirla por completo",3:"Sigue jugando en modo infinito"},pt:{0:"Resolva o desafio diário de imagem borrada",1:"Revele a imagem passo a passo",2:"Adivinhe antes da revelação completa",3:"Continue no modo infinito"},fr:{0:"Relevez le défi quotidien de l'image floue",1:"Révélez l'image progressivement",2:"Devinez avant la révélation complète",3:"Continuez en mode infini"},de:{0:"Löse das tägliche Rätsel mit verschwommenem Bild",1:"Enthülle das Bild Schritt für Schritt",2:"Rate vor der vollständigen Auflösung",3:"Spiele im Endlosmodus weiter"},it:{0:"Gioca all'indovinello giornaliero con immagini sfocate",1:"Rivela l'immagine poco alla volta",2:"Indovina prima che sia del tutto visibile",3:"Continua nella modalità infinita"},ru:{0:"Решайте ежедневную загадку с размытым фото",1:"Открывайте изображение постепенно",2:"Угадайте до полного раскрытия",3:"Продолжайте в бесконечном режиме"},ar:{0:"حل لغز الصورة الضبابية اليومي",1:"اكشف الصورة تدريجيًا",2:"خمّن قبل ظهور الصورة بالكامل",3:"واصل اللعب في الوضع اللانهائي"},th:{0:"ทายภาพเบลอประจำวัน",1:"ค่อย ๆ เปิดเผยภาพทีละขั้น",2:"ทายให้ได้ก่อนเห็นภาพทั้งหมด",3:"เล่นต่อได้ในโหมดไม่สิ้นสุด"},id:{0:"Mainkan teka-teki gambar buram harian",1:"Ungkap gambar sedikit demi sedikit",2:"Tebak sebelum gambar terlihat penuh",3:"Lanjutkan bermain dalam mode tanpa akhir"},tr:{0:"Günlük bulanık resim bulmacasını çözün",1:"Resmi adım adım açın",2:"Tamamen görünmeden önce tahmin edin",3:"Sonsuz modda oynamaya devam edin"},nl:{0:"Speel de dagelijkse wazige-fotoquiz",1:"Onthul de foto stap voor stap",2:"Raad voordat het beeld helemaal zichtbaar is",3:"Speel verder in de eindeloze modus"},vi:{0:"Chơi thử thách ảnh mờ mỗi ngày",1:"Mở dần hình ảnh từng bước",2:"Đoán trước khi ảnh hiện rõ hoàn toàn",3:"Tiếp tục chơi ở chế độ vô tận"}};
const __oF=applyLang;applyLang=function(l){__oF(l);var ft=FEAT_T[l]||FEAT_T.en;var g=document.querySelector('.features-grid');if(ft&&g){g.innerHTML='';Object.keys(ft).sort(function(a,b){return a-b;}).forEach(function(k){if(ft[k]){var d=document.createElement('div');d.className='feature';d.innerHTML='<div class="feature-dot"></div><span>'+ft[k]+'</span>';g.appendChild(d);}});}};
(function(){var l=localStorage.getItem('sk_lang')||detectLang();var ft=FEAT_T[l]||FEAT_T.en;var g=document.querySelector('.features-grid');if(ft&&g){g.innerHTML='';Object.keys(ft).sort(function(a,b){return a-b;}).forEach(function(k){if(ft[k]){var d=document.createElement('div');d.className='feature';d.innerHTML='<div class="feature-dot"></div><span>'+ft[k]+'</span>';g.appendChild(d);}});}})();
</script>
Expand Down
46 changes: 46 additions & 0 deletions tests/blur-support-growth.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import {runInNewContext} from 'node:vm';
const read = p => readFileSync(new URL('../'+p, import.meta.url), 'utf8');
const html = read('support/blur/index.html');
const langs = ['en','ja','zh','zh_TW','ko','es','pt','fr','de','it','ru','ar','th','id','tr','nl','vi'];

test('first-rendered Blur benefits are verified single-player reveal mechanics',()=>{
for(const phrase of [
'Daily blurred-image guessing challenge',
'Reveal more of the picture step by step',
'Guess before the full image appears',
'Keep playing in infinite mode',
]) assert.ok(html.includes(phrase),phrase);
assert.doesNotMatch(html,/Multiplayer mode|Timed challenges|Multiple difficulty levels/);
});
test('all 17 locale feature blocks communicate the same four real game actions',()=>{
const match=html.match(/const FEAT_T=(\{[\s\S]*?\});\s*const __oF=applyLang;/);
assert.ok(match,'missing localized feature block');
const dict=runInNewContext('('+match[1]+')');
assert.deepEqual(Object.keys(dict).sort(),[...langs].sort());
for(const locale of langs){
assert.deepEqual(Object.keys(dict[locale]).sort(),['0','1','2','3'],locale+' must show four verified actions');
for(const item of Object.values(dict[locale])) assert.ok(Array.from(item).length>=6,locale+' blank feature');
}
assert.match(dict.en[0],/daily/);
assert.match(dict.ja[3],/無限モード/);
});
test('first screen and detail Play links use the legitimate package with different placement',()=>{
const links=[...html.matchAll(/<a href="(https:\/\/play\.google\.com[^"]+)" class="dl-btn dl-btn-play" data-position="(hero|details)"/g)];
assert.deepEqual(links.map(l=>l[2]).sort(),['details','hero']);
for(const link of links){
assert.ok(link[1].includes('id=com.blurapp.blur_app'));
assert.match(link[1],/referrer=utm_source%3Dappdevelopsk/);
assert.doesNotMatch(link[1],/user_id|device_id|email|quiz_answer/);
}
assert.match(html,/placement: a\.getAttribute\('data-position'\)/);
});
test('manual site deployment and read-only smoke include Blur landing',()=>{
assert.match(read('scripts/publish_app_discovery.sh'),/support\/blur\/index\.html/);
const live=read('scripts/check_app_discovery_live.sh');
assert.match(live,/fetch '\/support\/blur\/' 'blur.html'/);
assert.match(live,/FAIL Blur first-screen Play CTA not live/);
assert.match(live,/FAIL Blur advertises unverified game modes/);
});
Loading