Colecionadora de Estrelinhas đ
by TurboMaker55420 lines20.4 KB
html <!--ARCADELAB title: Colecionadora de Estrelinhas description: Um cĂ©u de carinho para o meu denguinho. emoji: đ color: blue -->
<!DOCTYPE html>
<html lang="pt-BR">html
html <!--ARCADELAB title: Colecionadora de Estrelinhas description: Um cĂ©u de carinho para o meu denguinho. emoji: đ color: blue --> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#101b49">
<title>Colecionadora de Estrelinhas đ</title>
<style>
:root {
--night: #101b49;
--night2: #182b67;
--blue: #5279d8;
--sky: #9dc9ff;
--cream: #fff7df;
--gold: #ffe99b;
--white: #fffdf8;
--ink: #24315d;
}
* { box-sizing: border-box; }
html { min-height: 100%; background: var(--night); }
body {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
color: var(--white);
font-family: ui-rounded, "Trebuchet MS", system-ui, -apple-system, sans-serif;
background:
radial-gradient(ellipse at 20% 10%, rgba(99,151,255,.30), transparent 35%),
radial-gradient(ellipse at 85% 30%, rgba(116,134,255,.22), transparent 30%),
linear-gradient(165deg, #0d1741 0%, #1b3474 55%, #345aa1 100%);
overflow-x: hidden;
}
button { font: inherit; }
.sky { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.sparkle {
position: absolute; color: #fff8c9; opacity: .8;
animation: twinkle 3s ease-in-out infinite;
text-shadow: 0 0 12px rgba(255,244,170,.8);
}
.sparkle:nth-child(3n) { animation-delay: -1.1s; }
.sparkle:nth-child(4n) { animation-delay: -2s; }
.moon {
position: absolute; right: 7%; top: 7%; width: 70px; height: 70px;
border-radius: 50%; background: #fff1b7;
box-shadow: 0 0 38px rgba(255,236,163,.22);
}
.moon::after {
content: ""; position: absolute; width: 64px; height: 64px;
border-radius: 50%; background: #17265d; left: 19px; top: -8px;
}
.cloud {
position: absolute; bottom: 7%; width: 180px; height: 48px;
background: rgba(175,204,255,.12); border-radius: 50px;
filter: blur(.2px);
}
.cloud::before, .cloud::after {
content: ""; position: absolute; background: inherit; border-radius: 50%;
}
.cloud::before { width: 70px; height: 70px; left: 28px; bottom: 5px; }
.cloud::after { width: 88px; height: 88px; right: 25px; bottom: 0; }
.cloud.one { left: -45px; }
.cloud.two { right: -65px; bottom: 18%; transform: scale(.8); }
main {
position: relative; z-index: 1; width: min(100% - 28px, 620px);
margin: 0 auto; padding: max(26px, env(safe-area-inset-top)) 0 max(34px, env(safe-area-inset-bottom));
}
header { text-align: center; padding: 22px 8px 12px; }
.eyebrow {
display: inline-block; padding: 7px 12px; border-radius: 999px;
border: 1px solid rgba(255,255,255,.25); background: rgba(255,255,255,.08);
font-size: .76rem; letter-spacing: .08em; text-transform: uppercase; color: #d9e7ff;
}
h1 { font-size: clamp(2rem, 8vw, 3.15rem); line-height: 1.04; margin: 17px 0 10px; letter-spacing: -.045em; }
h1 span { color: var(--gold); }
.subtitle { margin: 0 auto; max-width: 420px; line-height: 1.6; color: #dce8ff; font-size: .98rem; }
.panel {
margin-top: 18px; padding: 20px; border-radius: 26px;
border: 1px solid rgba(255,255,255,.23);
background: linear-gradient(145deg, rgba(255,255,255,.14), rgba(255,255,255,.065));
box-shadow: 0 18px 45px rgba(4,12,45,.2);
backdrop-filter: blur(8px);
}
.topline { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
.day-label { color: #dbe8ff; font-size: .9rem; }
.count { font-weight: 800; color: var(--gold); white-space: nowrap; }
.progress-track { height: 8px; border-radius: 99px; background: rgba(7,15,50,.35); margin: 13px 0 20px; overflow: hidden; }
.progress-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #9fc9ff, #fff0a5); border-radius: inherit; transition: width .5s ease; }
.starfield {
min-height: 215px; position: relative; display: grid; grid-template-columns: repeat(5, 1fr);
gap: 12px 7px; align-items: center; justify-items: center;
padding: 12px 2px 17px;
}
.star-button {
border: 0; color: var(--gold); background: transparent; padding: 5px;
width: 100%; max-width: 76px; aspect-ratio: 1; cursor: pointer;
display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 3px;
border-radius: 18px; transition: transform .2s ease, background .2s ease;
-webkit-tap-highlight-color: transparent;
}
.star-button:focus-visible { outline: 3px solid white; outline-offset: 3px; }
.star-button:not(:disabled):active { transform: scale(.9); }
.star-button:disabled { cursor: default; opacity: .38; filter: saturate(.5); }
.star-button .star-icon { font-size: clamp(1.9rem, 7vw, 2.55rem); line-height: 1; filter: drop-shadow(0 0 9px rgba(255,229,130,.42)); }
.star-button.collected { background: rgba(255,238,169,.13); opacity: 1; }
.star-button.collected .star-icon { animation: pop .45s ease; }
.star-button .star-day { font-size: .68rem; color: #e2ebff; }
.star-button.locked .star-icon { color: #b6c8f1; filter: none; }
.star-button.locked .star-icon::selection { background: transparent; }
.hint { text-align: center; color: #d9e5ff; font-size: .88rem; margin: 0; line-height: 1.5; }
.message-card {
display: none; margin-top: 17px; border-radius: 23px; padding: 21px 18px;
color: var(--ink); background: var(--cream); text-align: center;
box-shadow: 0 12px 32px rgba(3,10,36,.18); animation: rise .35s ease both;
}
.message-card.show { display: block; }
.message-card .little { font-size: 1.55rem; }
.message-card h2 { margin: 8px 0 9px; font-size: 1.3rem; }
.message-card p { margin: 0; line-height: 1.7; white-space: pre-line; }
.message-card .signature { margin-top: 15px; color: #4f68a2; font-size: .9rem; }
.secret-card { background: linear-gradient(145deg, #fff4c8, #fffdf4); }
.secret-card h2 { color: #324b91; }
.actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; margin-top: 17px; }
.btn {
border: 0; border-radius: 999px; padding: 12px 17px; cursor: pointer; font-weight: 800;
transition: transform .18s ease, filter .18s ease; min-height: 44px;
}
.btn:active { transform: scale(.97); }
.btn-primary { background: var(--gold); color: #273665; box-shadow: 0 5px 0 rgba(8,16,55,.18); }
.btn-soft { background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.28); color: white; }
.tiny-note { text-align: center; color: #c8d8fa; font-size: .77rem; line-height: 1.55; margin: 17px 10px 0; }
footer { text-align: center; padding: 20px 8px 0; color: #c7d8ff; font-size: .78rem; line-height: 1.6; }
.toast {
position: fixed; z-index: 10; bottom: max(18px, env(safe-area-inset-bottom)); left: 50%;
transform: translate(-50%, 20px); opacity: 0; pointer-events: none;
background: #fff6d8; color: #24315d; border-radius: 16px; padding: 12px 16px;
box-shadow: 0 8px 28px rgba(0,0,0,.25); width: max-content; max-width: calc(100% - 32px);
text-align: center; font-size: .9rem; transition: all .25s ease;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.confetti { position: fixed; z-index: 9; pointer-events: none; animation: fall 1.5s ease-in forwards; }
@keyframes twinkle { 0%,100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.18); } }
@keyframes pop { 0% { transform: scale(.5) rotate(-15deg); } 70% { transform: scale(1.2) rotate(7deg); } 100% { transform: scale(1); } }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fall { to { transform: translate(var(--drift), 90vh) rotate(360deg); opacity: 0; } }
@media (min-width: 520px) {
main { padding-top: 32px; }
.panel { padding: 25px; }
.starfield { gap: 15px 10px; min-height: 240px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<div class="sky" aria-hidden="true">
<div class="moon"></div><div class="cloud one"></div><div class="cloud two"></div>
<span class="sparkle" style="left:8%;top:13%">âŠ</span>
<span class="sparkle" style="left:24%;top:8%">·</span>
<span class="sparkle" style="left:78%;top:20%">â§</span>
<span class="sparkle" style="left:88%;top:43%">âŠ</span>
<span class="sparkle" style="left:12%;top:49%">â§</span>
<span class="sparkle" style="left:65%;top:8%">·</span>
<span class="sparkle" style="left:48%;top:25%">âŠ</span>
<span class="sparkle" style="left:92%;top:67%">â§</span>
<span class="sparkle" style="left:31%;top:72%">âŠ</span>
<span class="sparkle" style="left:6%;top:83%">·</span>
<span class="sparkle" style="left:71%;top:88%">âŠ</span>
</div>
<main>
<header>
<div class="eyebrow">um cantinho sĂł seu âŠ</div>
<h1>A Colecionadora<br>de <span>Estrelinhas</span></h1>
<p class="subtitle" id="intro">Oi, denguinho. Tem um pedacinho do cĂ©u guardado pra vocĂȘ aqui. đ</p>
</header>
<section class="panel" aria-label="Coleção de estrelinhas">
<div class="topline">
<div class="day-label" id="dayLabel">Sua aventura começa hoje</div>
<div class="count" id="countLabel">0 / 7 estrelas</div>
</div>
<div class="progress-track" aria-label="Progresso da coleção">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="starfield" id="starfield" aria-label="Estrelas para coletar"></div>
<p class="hint" id="hint">Toque na estrelinha brilhante de hoje para descobrir sua mensagem. âš</p>
</section>
<section class="message-card" id="messageCard" aria-live="polite">
<div class="little" id="messageEmoji">đ</div>
<h2 id="messageTitle">Uma mensagem para vocĂȘ</h2>
<p id="messageText"></p>
<div class="signature" id="messageSignature">com carinho, sempre đ</div>
<div class="actions">
<button class="btn btn-primary" id="closeMessage">Guardar com carinho</button>
</div>
</section>
<section class="message-card secret-card" id="secretCard" aria-live="polite">
<div class="little">đđâ</div>
<h2>VocĂȘ encontrou o segredo!</h2>
<p id="secretText"></p>
<div class="signature">um abraço em forma de cĂ©u, sĂł pra vocĂȘ</div>
</section>
<div class="actions">
<button class="btn btn-soft" id="todayButton">Ver a estrelinha de hoje âš</button>
<button class="btn btn-soft" id="restartButton">Recomeçar a aventura â»</button>
</div>
<p class="tiny-note">NĂŁo precisa ter pressa, denguinho. As estrelinhas ficam aqui esperando por vocĂȘ. Nos dias difĂceis, pode sĂł olhar o cĂ©u. đ</p>
<footer>Feito com carinho, especialmente para vocĂȘ.<br>Uma estrelinha por dia, sem pressa e sem cobranças. đ</footer>
</main>
<div class="toast" id="toast" role="status"></div>
<script>
/* ============================================================
PERSONALIZE AQUI
- Troque o nome/apelido e escreva suas prĂłprias mensagens.
- SĂŁo 7 estrelinhas nesta versĂŁo inicial.
- A surpresa secreta aparece depois de coletar as 7.
============================================================ */
const CONFIG = {
nome: "Susu / Yan",
apelido: "denguinho",
introducao: "Oi, denguinho. Tem um pedacinho do cĂ©u guardado pra vocĂȘ aqui. đ",
assinatura: "com carinho, sempre đ",
mensagens: [
{
titulo: "Uma estrelinha sĂł sua",
emoji: "đ",
texto: "Oi, meu denguinho. Esta estrelinha Ă© sĂł pra te lembrar de uma coisa: vocĂȘ nĂŁo precisa resolver tudo hoje. Pode respirar, descansar e ir devagar. VocĂȘ merece carinho em todos os dias, inclusive nos difĂceis."
},
{
titulo: "Um lembrete pequenininho",
emoji: "âïž",
texto: "VocĂȘ nĂŁo precisa estar feliz o tempo todo para ser importante. NĂŁo precisa fingir que estĂĄ tudo bem comigo. Se hoje sĂł der para existir e descansar, isso tambĂ©m jĂĄ Ă© bastante."
},
{
titulo: "Uma missĂŁo bem fĂĄcil",
emoji: "đ",
texto: "MissĂŁo de hoje: encontrar uma coisinha que seja confortĂĄvel. Pode ser um cobertor, uma mĂșsica, ĂĄgua geladinha ou um vĂdeo bobo. NĂŁo Ă© uma prova, tĂĄ? Ă sĂł um convite para cuidar um pouquinho de vocĂȘ."
},
{
titulo: "Uma coisa que eu queria te dizer",
emoji: "đ",
texto: "Mesmo quando vocĂȘ nĂŁo consegue enxergar suas qualidades, elas nĂŁo desaparecem. VocĂȘ Ă© muito mais do que os dias ruins que estĂĄ enfrentando. NĂŁo precisa me provar nada para merecer cuidado."
},
{
titulo: "Pausa para um sorriso",
emoji: "đŁ",
texto: "Comunicado oficial do ObservatĂłrio das Coisas Fofas: foi detectado um denguinho extremamente especial olhando para esta tela. A equipe recomenda ĂĄgua, descanso e receber carinho quando quiser. RelatĂłrio encerrado. đ«Ąđ"
},
{
titulo: "VocĂȘ pode escolher",
emoji: "đ",
texto: "Se hoje estiver pesado, vocĂȘ pode escolher o que precisa: companhia, distração, conversar ou ficar quietinha. NĂŁo tem resposta errada. VocĂȘ nĂŁo precisa se explicar perfeitamente para merecer acolhimento."
},
{
titulo: "A Ășltima estrelinha",
emoji: "âš",
texto: "VocĂȘ chegou atĂ© aqui, estrelinha. Espero que este pequeno cĂ©u tenha te feito companhia por alguns instantes. NĂŁo precisa guardar tudo o que sente sozinha. VocĂȘ merece cuidado, apoio e dias mais leves, um passinho de cada vez."
}
],
surpresaSecreta: "Surpresa, meu denguinho! đ Se vocĂȘ chegou atĂ© aqui, encontrou todas as estrelinhas deste pequeno cĂ©u. Eu queria criar um lugar onde vocĂȘ pudesse receber um pouquinho de carinho sem precisar fazer nada em troca. NĂŁo existe uma versĂŁo sua que precise estar sempre bem para merecer amor. Espero que, quando abrir este cantinho, vocĂȘ se lembre de ser gentil consigo mesma. VocĂȘ merece cuidado nos dias de sol e tambĂ©m nos dias nublados. Com todo o carinho que cabe nesta telinha. đâ"
};
/* O progresso Ă© salvo apenas neste navegador/dispositivo. */
const STORAGE_KEY = "colecionadora_estrelas_v1";
const $ = (id) => document.getElementById(id);
const starfield = $("starfield");
let toastTimer;
function localDateKey(date = new Date()) {
return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,"0")}-${String(date.getDate()).padStart(2,"0")}`;
}
function dayNumber(date = new Date()) {
const start = new Date(date.getFullYear(), date.getMonth(), date.getDate());
return Math.floor(start.getTime() / 86400000);
}
function loadState() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
return {
collected: Array.isArray(saved.collected) ? saved.collected.filter(n => Number.isInteger(n) && n >= 0 && n < CONFIG.mensagens.length) : [],
firstDay: Number.isInteger(saved.firstDay) ? saved.firstDay : null,
lastCollectedDate: typeof saved.lastCollectedDate === "string" ? saved.lastCollectedDate : null
};
} catch {
return { collected: [], firstDay: null, lastCollectedDate: null };
}
}
let state = loadState();
function saveState() {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); }
catch { showToast("NĂŁo consegui salvar neste navegador. VocĂȘ ainda pode brincar por aqui! đ"); }
}
function hasCollected(index) { return state.collected.includes(index); }
function unlockedIndex() {
if (state.firstDay === null) return 0;
const elapsed = Math.max(0, dayNumber() - state.firstDay);
return Math.min(CONFIG.mensagens.length - 1, elapsed);
}
function getTodayIndex() {
return Math.min(unlockedIndex(), CONFIG.mensagens.length - 1);
}
function showToast(message) {
const toast = $("toast");
toast.textContent = message;
toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove("show"), 2600);
}
function makeStars() {
starfield.innerHTML = "";
const available = getTodayIndex();
CONFIG.mensagens.forEach((message, index) => {
const isCollected = hasCollected(index);
const isUnlocked = index <= available;
const button = document.createElement("button");
button.type = "button";
button.className = "star-button" + (isCollected ? " collected" : "") + (!isUnlocked ? " locked" : "");
button.disabled = !isUnlocked;
button.setAttribute("aria-label", isCollected ? `Estrelinha ${index+1}, jĂĄ coletada` : isUnlocked ? `Coletar estrelinha ${index+1}` : `Estrelinha ${index+1}, ainda bloqueada`);
const icon = document.createElement("span");
icon.className = "star-icon";
icon.textContent = isCollected ? "â" : isUnlocked ? "âŠ" : "â§";
const day = document.createElement("span");
day.className = "star-day";
day.textContent = isCollected ? "guardada" : isUnlocked ? `dia ${index+1}` : "em breve";
button.append(icon, day);
button.addEventListener("click", () => collectStar(index));
starfield.appendChild(button);
});
const count = state.collected.length;
$("countLabel").textContent = `${count} / ${CONFIG.mensagens.length} estrelas`;
$("progressFill").style.width = `${(count / CONFIG.mensagens.length) * 100}%`;
if (count === CONFIG.mensagens.length) {
$("dayLabel").textContent = "Coleção completa! Que cĂ©u bonito đ";
$("hint").textContent = "VocĂȘ encontrou todas as estrelinhas. A surpresa secreta estĂĄ logo abaixo. đ";
$("secretCard").classList.add("show");
$("secretText").textContent = CONFIG.surpresaSecreta;
} else {
$("dayLabel").textContent = count === 0 ? "Sua aventura começa hoje" : `${count} estrelinha${count === 1 ? "" : "s"} guardada${count === 1 ? "" : "s"} com carinho`;
$("hint").textContent = hasCollected(available)
? "A estrelinha de hoje jĂĄ estĂĄ guardada. Volte amanhĂŁ para descobrir outra. đ"
: "Toque na estrelinha brilhante de hoje para descobrir sua mensagem. âš";
$("secretCard").classList.remove("show");
}
}
function collectStar(index) {
if (index > getTodayIndex()) {
showToast("Essa estrelinha ainda estĂĄ descansando. Volte outro dia! đ");
return;
}
if (hasCollected(index)) {
showMessage(index);
return;
}
const today = localDateKey();
if (state.lastCollectedDate === today) {
showToast("A estrelinha de hoje jĂĄ foi sua. Guarde esse carinho e volte amanhĂŁ. đ");
return;
}
if (state.firstDay === null) state.firstDay = dayNumber();
state.collected.push(index);
state.lastCollectedDate = today;
state.collected.sort((a,b) => a-b);
saveState();
makeStars();
showMessage(index);
sprinkle();
}
function showMessage(index) {
const message = CONFIG.mensagens[index];
$("messageEmoji").textContent = message.emoji || "â";
$("messageTitle").textContent = message.titulo;
$("messageText").textContent = message.texto;
$("messageSignature").textContent = CONFIG.assinatura;
$("messageCard").classList.add("show");
$("messageCard").scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function sprinkle() {
const symbols = ["âŠ", "âš", "đ", "â"];
for (let i = 0; i < 16; i++) {
const piece = document.createElement("span");
piece.className = "confetti";
piece.textContent = symbols[Math.floor(Math.random() * symbols.length)];
piece.style.left = `${Math.random() * 100}vw`;
piece.style.top = `${Math.random() * 25 + 5}vh`;
piece.style.fontSize = `${Math.random() * 14 + 12}px`;
piece.style.setProperty("--drift", `${Math.random() * 150 - 75}px`);
document.body.appendChild(piece);
setTimeout(() => piece.remove(), 1700);
}
}
$("closeMessage").addEventListener("click", () => $("messageCard").classList.remove("show"));
$("todayButton").addEventListener("click", () => {
const index = getTodayIndex();
if (hasCollected(index)) showMessage(index);
else collectStar(index);
});
$("restartButton").addEventListener("click", () => {
const confirmed = window.confirm("Quer mesmo recomeçar a aventura neste dispositivo? As estrelinhas guardadas serão apagadas.");
if (!confirmed) return;
state = { collected: [], firstDay: null, lastCollectedDate: null };
try { localStorage.removeItem(STORAGE_KEY); } catch {}
$("messageCard").classList.remove("show");
$("secretCard").classList.remove("show");
makeStars();
showToast("Um novo cĂ©u esperando por vocĂȘ. đ");
});
$("intro").textContent = CONFIG.introducao.replace("Susu", CONFIG.nome).replace("Yan", CONFIG.nome);
makeStars();
</script>
</body>
</html>
Game Source: Colecionadora de Estrelinhas đ
Creator: TurboMaker55
Libraries: none
Complexity: complex (420 lines, 20.4 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: colecionadora-de-estrelinhas-turbomaker55" to link back to the original. Then publish at arcadelab.ai/publish.