1625 lines
53 KiB
HTML
1625 lines
53 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,600;0,700;1,400&family=Fira+Code:wght@400;500&display=swap');
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
html, body { min-height: 100vh; }
|
|
|
|
body {
|
|
font-family: 'Nunito', -apple-system, sans-serif;
|
|
font-size: 16px;
|
|
line-height: 1.65;
|
|
color: #e0d8c8;
|
|
background: #1b1040;
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='3' fill='none' stroke='%23c8a860' stroke-width='1.5'/%3E%3Ccircle cx='10' cy='10' r='1' fill='%23c8a860'/%3E%3C/svg%3E") 10 10, default;
|
|
}
|
|
|
|
a, button, input[type="submit"], summary, label {
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='6' fill='none' stroke='%23f0d878' stroke-width='1' opacity='0.6'/%3E%3Ccircle cx='10' cy='10' r='1.5' fill='%23f0d878'/%3E%3C/svg%3E") 10 10, pointer;
|
|
}
|
|
|
|
input, textarea {
|
|
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cline x1='10' y1='3' x2='10' y2='17' stroke='%23c8a860' stroke-width='1.5'/%3E%3C/svg%3E") 10 10, text;
|
|
}
|
|
|
|
/* warm vignette */
|
|
body::after {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
background: radial-gradient(ellipse at center top, transparent 40%, rgba(10, 5, 30, 0.6) 100%);
|
|
pointer-events: none;
|
|
z-index: 999;
|
|
}
|
|
|
|
/* background scene canvas — sky, stars, hills, hut */
|
|
#scene {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
/* firefly particles canvas */
|
|
#particles {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* cursor trail canvas */
|
|
#trail {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 998;
|
|
}
|
|
|
|
/* junimo spirits */
|
|
#junimos {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
|
|
/* Stardew HUD overlay */
|
|
.stardew-hud {
|
|
position: fixed;
|
|
top: 10px;
|
|
right: 10px;
|
|
z-index: 900;
|
|
pointer-events: none;
|
|
font-family: 'Fira Code', monospace;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 4px;
|
|
}
|
|
|
|
/* wooden plank frame mixin — light wood border, beige interior */
|
|
.hud-frame {
|
|
background: #e8d8b0;
|
|
border: 3px solid #b88840;
|
|
border-radius: 2px;
|
|
box-shadow:
|
|
inset 0 0 0 1px #d0b878,
|
|
inset 0 0 0 2px rgba(0,0,0,0.08),
|
|
0 2px 6px rgba(0,0,0,0.5),
|
|
0 0 0 1px rgba(0,0,0,0.3);
|
|
image-rendering: pixelated;
|
|
}
|
|
|
|
/* clock/date box */
|
|
.hud-clock {
|
|
padding: 5px 10px 6px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
min-width: 110px;
|
|
}
|
|
|
|
.hud-date-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 4px;
|
|
width: 100%;
|
|
justify-content: center;
|
|
border-bottom: 1px solid #c8b080;
|
|
padding-bottom: 4px;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.hud-day {
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
color: #3a2a10;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.hud-season {
|
|
font-size: 0.55rem;
|
|
color: #7a6a40;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.hud-time-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hud-sky {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
border: 2px solid #a08040;
|
|
overflow: hidden;
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.hud-sky-inner {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.hud-sun {
|
|
position: absolute;
|
|
width: 8px;
|
|
height: 8px;
|
|
background: #f8e040;
|
|
border-radius: 50%;
|
|
box-shadow: 0 0 4px #f8e040;
|
|
}
|
|
|
|
.hud-time {
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
color: #3a2a10;
|
|
}
|
|
|
|
.hud-weather-icon {
|
|
font-size: 0.6rem;
|
|
color: #7a6a40;
|
|
}
|
|
|
|
/* gold box */
|
|
.hud-gold {
|
|
padding: 3px 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.hud-g-letter {
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
color: #8a7030;
|
|
}
|
|
|
|
.hud-gold-bars {
|
|
display: flex;
|
|
gap: 1px;
|
|
height: 10px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.hud-gold-bar {
|
|
width: 6px;
|
|
background: #58a830;
|
|
border: 1px solid #408020;
|
|
border-radius: 1px;
|
|
}
|
|
|
|
.hud-gold-bar.empty {
|
|
background: #c8b888;
|
|
border-color: #a89868;
|
|
}
|
|
|
|
.hud-gold-amount {
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
color: #3a2a10;
|
|
min-width: 30px;
|
|
text-align: right;
|
|
}
|
|
|
|
/* energy bar */
|
|
.hud-energy {
|
|
padding: 3px 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.hud-energy-bar {
|
|
width: 10px;
|
|
height: 50px;
|
|
background: #c8b888;
|
|
border: 2px solid #a08040;
|
|
border-radius: 1px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.hud-energy-fill {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: 100%;
|
|
height: 75%;
|
|
background: linear-gradient(to top, #38a828, #68d838);
|
|
}
|
|
|
|
.hud-energy-right {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.hud-energy-label {
|
|
font-size: 0.55rem;
|
|
font-weight: 700;
|
|
color: #8a7030;
|
|
}
|
|
|
|
.hud-energy-num {
|
|
font-size: 0.5rem;
|
|
color: #3a2a10;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.stardew-hud { top: 6px; right: 6px; }
|
|
.hud-clock { min-width: 90px; padding: 4px 6px; }
|
|
.hud-sky { width: 22px; height: 22px; }
|
|
}
|
|
|
|
.shell {
|
|
max-width: 660px;
|
|
margin: 0 auto;
|
|
padding: 0 1.5rem;
|
|
position: relative;
|
|
z-index: 3;
|
|
}
|
|
|
|
/* nav */
|
|
nav {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
padding: 1.5rem 0 1.2rem;
|
|
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
nav .site {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
color: #f0d878;
|
|
text-decoration: none;
|
|
letter-spacing: 0.06em;
|
|
border-bottom: none;
|
|
transition: text-shadow 0.3s;
|
|
}
|
|
|
|
nav .site:hover {
|
|
text-shadow: 0 0 10px rgba(240, 216, 120, 0.5);
|
|
}
|
|
|
|
nav .links { display: flex; gap: 1.2rem; }
|
|
|
|
nav .links a {
|
|
font-size: 0.82rem;
|
|
color: #8878a0;
|
|
text-decoration: none;
|
|
border-bottom: none;
|
|
transition: color 0.2s, text-shadow 0.3s;
|
|
}
|
|
|
|
nav .links a:hover {
|
|
color: #c8b8e0;
|
|
text-shadow: 0 0 6px rgba(180, 160, 220, 0.3);
|
|
}
|
|
|
|
/* greeting */
|
|
#greeting {
|
|
padding: 1.5rem 0 0;
|
|
font-size: 0.85rem;
|
|
color: #6a5a80;
|
|
font-style: italic;
|
|
opacity: 0;
|
|
animation: fadeIn 1.5s ease forwards 0.3s;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
/* content */
|
|
.content { padding: 1.8rem 0 3rem; }
|
|
|
|
/* headings */
|
|
h1 {
|
|
font-size: 1.4rem;
|
|
font-weight: 700;
|
|
color: #f0d878;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
h1 a { color: #f0d878; text-decoration: none; border-bottom: none; }
|
|
h1 a:hover { color: #f8e8a0; }
|
|
|
|
h2 {
|
|
font-size: 1.05rem;
|
|
font-weight: 600;
|
|
color: #d0c0a0;
|
|
margin: 1.8rem 0 0.5rem;
|
|
}
|
|
|
|
p { margin: 0.5rem 0; color: #a098b0; }
|
|
|
|
a {
|
|
color: #c8b8e0;
|
|
text-decoration: none;
|
|
border-bottom: 1px solid rgba(200, 184, 224, 0.2);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
a:hover {
|
|
color: #e0d0f8;
|
|
border-bottom-color: rgba(200, 184, 224, 0.5);
|
|
}
|
|
|
|
em { color: #8a80a0; }
|
|
|
|
/* inputs */
|
|
input[type="text"],
|
|
input[type="url"],
|
|
input[name="q"],
|
|
input[name="url"],
|
|
input[name="note"],
|
|
input[name="tags"],
|
|
input[name="site_name"],
|
|
input[name="dest_hash"] {
|
|
background: rgba(20, 15, 50, 0.6);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 4px;
|
|
padding: 0.6rem 0.85rem;
|
|
color: #d0c8b8;
|
|
font-family: 'Nunito', sans-serif;
|
|
font-size: 0.95rem;
|
|
transition: border-color 0.2s, box-shadow 0.3s;
|
|
}
|
|
|
|
input:focus, textarea:focus {
|
|
outline: none;
|
|
border-color: rgba(240, 216, 120, 0.4);
|
|
box-shadow: 0 0 12px rgba(240, 216, 120, 0.08);
|
|
}
|
|
|
|
button, input[type="submit"] {
|
|
background: rgba(30, 20, 60, 0.7);
|
|
border: 1px solid rgba(255,255,255,0.12);
|
|
border-radius: 4px;
|
|
padding: 0.6rem 1.1rem;
|
|
color: #a098b0;
|
|
font-family: 'Nunito', sans-serif;
|
|
font-size: 0.88rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
button:hover, input[type="submit"]:hover {
|
|
background: rgba(50, 35, 90, 0.7);
|
|
color: #d0c0e0;
|
|
border-color: rgba(240, 216, 120, 0.3);
|
|
box-shadow: 0 0 10px rgba(240, 216, 120, 0.06);
|
|
}
|
|
|
|
/* search results */
|
|
.result {
|
|
padding: 1rem 0;
|
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.result:hover {
|
|
background: rgba(255,255,255,0.02);
|
|
}
|
|
|
|
.result:last-child { border-bottom: none; }
|
|
|
|
.result > a:first-child {
|
|
font-size: 1.02rem;
|
|
font-weight: 600;
|
|
color: #d8c890;
|
|
border-bottom: none;
|
|
}
|
|
|
|
.result > a:first-child:hover { color: #f0e0a8; }
|
|
|
|
.note {
|
|
margin-top: 0.3rem;
|
|
font-size: 0.9rem;
|
|
color: #7a7090;
|
|
}
|
|
|
|
.tags { margin-top: 0.3rem; }
|
|
|
|
.tag, .tags a {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.7rem;
|
|
color: #8878a0;
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 3px;
|
|
padding: 0.1rem 0.35rem;
|
|
margin-right: 0.25rem;
|
|
}
|
|
|
|
.tag:hover, .tags a:hover {
|
|
color: #c8b8e0;
|
|
border-color: rgba(255,255,255,0.18);
|
|
}
|
|
|
|
/* trusted / remote results */
|
|
details {
|
|
margin: 1rem 0;
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 4px;
|
|
padding: 0.7rem 0.9rem;
|
|
background: rgba(20, 15, 50, 0.4);
|
|
}
|
|
|
|
summary {
|
|
font-size: 0.85rem;
|
|
color: #7a7090;
|
|
font-weight: 500;
|
|
}
|
|
|
|
summary:hover { color: #c8b8e0; }
|
|
|
|
details ul { margin-top: 0.5rem; padding-left: 1.2rem; }
|
|
details li { margin: 0.35rem 0; font-size: 0.9rem; }
|
|
|
|
/* lists */
|
|
ul, ol { padding-left: 1.2rem; margin: 0.5rem 0; }
|
|
|
|
li { margin: 0.45rem 0; color: #a098b0; }
|
|
li a { border-bottom: none; }
|
|
li a:hover { border-bottom: 1px solid rgba(200,184,224,0.4); }
|
|
|
|
/* code */
|
|
pre {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.8rem;
|
|
background: rgba(15, 10, 40, 0.5);
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 4px;
|
|
padding: 0.9rem;
|
|
overflow-x: auto;
|
|
color: #9088a8;
|
|
margin: 0.8rem 0;
|
|
}
|
|
|
|
code {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.82rem;
|
|
background: rgba(15, 10, 40, 0.4);
|
|
border-radius: 3px;
|
|
padding: 0.1rem 0.35rem;
|
|
color: #a898c0;
|
|
}
|
|
|
|
/* textarea */
|
|
textarea {
|
|
background: rgba(20, 15, 50, 0.6);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 4px;
|
|
padding: 0.7rem 0.9rem;
|
|
color: #d0c8b8;
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.8rem;
|
|
line-height: 1.6;
|
|
resize: vertical;
|
|
width: 100%;
|
|
}
|
|
|
|
/* tables */
|
|
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
|
|
|
th {
|
|
text-align: left;
|
|
font-size: 0.72rem;
|
|
font-weight: 500;
|
|
color: #6a6080;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
padding: 0.5rem 0.7rem;
|
|
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
}
|
|
|
|
td {
|
|
padding: 0.5rem 0.7rem;
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* misc */
|
|
label { color: #a098b0; }
|
|
input[type="checkbox"] { accent-color: #8878a0; }
|
|
|
|
hr { border: none; border-top: 1px solid rgba(255,255,255,0.06); margin: 1rem 0; }
|
|
|
|
small {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.7rem;
|
|
color: #5a5070;
|
|
}
|
|
|
|
/* footer */
|
|
footer {
|
|
border-top: 1px solid rgba(255,255,255,0.06);
|
|
padding: 1.5rem 0 2rem;
|
|
text-align: center;
|
|
color: #4a4060;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
footer .clock {
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 0.72rem;
|
|
color: #3a3050;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
::selection { background: #3a2870; color: #f0e8d0; }
|
|
|
|
::-webkit-scrollbar { width: 5px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: #2a2050; border-radius: 3px; }
|
|
|
|
@media (max-width: 600px) {
|
|
nav { flex-direction: column; gap: 0.5rem; }
|
|
nav .links { gap: 0.8rem; flex-wrap: wrap; }
|
|
h1 { font-size: 1.2rem; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="scene"></canvas>
|
|
<canvas id="particles"></canvas>
|
|
<canvas id="trail"></canvas>
|
|
<canvas id="junimos"></canvas>
|
|
<div class="stardew-hud">
|
|
<div class="hud-clock hud-frame">
|
|
<div class="hud-date-row">
|
|
<span class="hud-day" id="hud-day"></span>
|
|
</div>
|
|
<div class="hud-time-row">
|
|
<div class="hud-sky" id="hud-sky">
|
|
<div class="hud-sky-inner" id="hud-sky-inner"></div>
|
|
<div class="hud-sun" id="hud-sun"></div>
|
|
</div>
|
|
<span class="hud-time" id="hud-time"></span>
|
|
</div>
|
|
</div>
|
|
<div class="hud-gold hud-frame">
|
|
<span class="hud-g-letter">G</span>
|
|
<div class="hud-gold-bars" id="hud-gold-bars"></div>
|
|
<span class="hud-gold-amount" id="hud-gold"></span>
|
|
</div>
|
|
<div class="hud-energy hud-frame">
|
|
<div class="hud-energy-bar"><div class="hud-energy-fill" id="hud-energy-fill"></div></div>
|
|
<div class="hud-energy-right">
|
|
<span class="hud-energy-label">E</span>
|
|
<span class="hud-energy-num" id="hud-energy-num"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="shell">
|
|
<nav>
|
|
<a class="site" href="/">tinyweb</a>
|
|
<div class="links">
|
|
<a href="/pages">browse</a>
|
|
<a href="/tags">tags</a>
|
|
<a href="/subscriptions">network</a>
|
|
<a href="/style">customize</a>
|
|
<a href="/about">about</a>
|
|
</div>
|
|
</nav>
|
|
<div id="greeting"></div>
|
|
<div class="content">
|
|
{{content}}
|
|
</div>
|
|
<footer>
|
|
<div>curated by hand · shared over mesh</div>
|
|
<div class="clock" id="clock"></div>
|
|
</footer>
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
// greeting — stardew-style
|
|
var h = new Date().getHours();
|
|
var g = h < 5 ? "the junimos are sleeping... but the forest hums." :
|
|
h < 12 ? "good morning! the junimos are ready to help." :
|
|
h < 17 ? "a warm afternoon. the valley is peaceful." :
|
|
h < 21 ? "evening glow. the junimos dance in the twilight." :
|
|
"starlight over the valley. good browsing ahead.";
|
|
document.getElementById('greeting').textContent = g;
|
|
|
|
// clock
|
|
function tick() {
|
|
var d = new Date();
|
|
var el = document.getElementById('clock');
|
|
if (el) el.textContent = d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
|
|
}
|
|
tick();
|
|
setInterval(tick, 30000);
|
|
|
|
// Stardew HUD
|
|
var daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
var now = new Date();
|
|
var stardewDay = ((now.getDate() - 1) % 28) + 1;
|
|
var dayOfWeek = daysOfWeek[now.getDay() === 0 ? 6 : now.getDay() - 1];
|
|
|
|
document.getElementById('hud-day').textContent = dayOfWeek + '. ' + stardewDay;
|
|
|
|
// gold bars + amount
|
|
var goldSeed = now.getFullYear() * 366 + now.getMonth() * 31 + now.getDate();
|
|
var gold = (goldSeed * 127 + 2389) % 99000 + 1000;
|
|
var goldBarsEl = document.getElementById('hud-gold-bars');
|
|
var filledBars = Math.min(7, Math.floor(gold / 10000) + 1);
|
|
for (var gi = 0; gi < 7; gi++) {
|
|
var bar = document.createElement('div');
|
|
bar.className = 'hud-gold-bar' + (gi < filledBars ? '' : ' empty');
|
|
goldBarsEl.appendChild(bar);
|
|
}
|
|
document.getElementById('hud-gold').textContent = gold;
|
|
|
|
function hudTick() {
|
|
var d = new Date();
|
|
var hrs = d.getHours();
|
|
var mins = d.getMinutes();
|
|
var ampm = hrs >= 12 ? 'pm' : 'am';
|
|
var h12 = hrs % 12 || 12;
|
|
document.getElementById('hud-time').textContent = h12 + ':' + (mins < 10 ? '0' : '') + mins + ' ' + ampm;
|
|
|
|
// sky circle — gradient changes with time of day
|
|
var skyInner = document.getElementById('hud-sky-inner');
|
|
var sunEl = document.getElementById('hud-sun');
|
|
var dayProgress = (hrs * 60 + mins) / (24 * 60);
|
|
var skyColor1, skyColor2;
|
|
if (hrs >= 6 && hrs < 8) {
|
|
skyColor1 = '#f8a860'; skyColor2 = '#80b8e8'; // sunrise
|
|
} else if (hrs >= 8 && hrs < 17) {
|
|
skyColor1 = '#60a8e8'; skyColor2 = '#a0d0f8'; // day
|
|
} else if (hrs >= 17 && hrs < 20) {
|
|
skyColor1 = '#e88040'; skyColor2 = '#4060a0'; // sunset
|
|
} else {
|
|
skyColor1 = '#101838'; skyColor2 = '#182848'; // night
|
|
}
|
|
skyInner.style.background = 'linear-gradient(to bottom, ' + skyColor1 + ', ' + skyColor2 + ')';
|
|
|
|
// sun/moon position — arc across the circle
|
|
var angle = (dayProgress - 0.25) * Math.PI * 2; // 6am = left, noon = top, 6pm = right
|
|
var sunX = 10 + Math.cos(angle) * 8;
|
|
var sunY = 10 - Math.sin(angle) * 8;
|
|
sunEl.style.left = sunX + 'px';
|
|
sunEl.style.top = sunY + 'px';
|
|
if (hrs >= 20 || hrs < 6) {
|
|
sunEl.style.background = '#e8e0c0';
|
|
sunEl.style.boxShadow = '0 0 3px #e8e0c0';
|
|
} else {
|
|
sunEl.style.background = '#f8e040';
|
|
sunEl.style.boxShadow = '0 0 4px #f8e040';
|
|
}
|
|
|
|
// energy bar
|
|
var maxEnergy = 270;
|
|
var energy = Math.max(20, Math.round(maxEnergy - dayProgress * maxEnergy * 0.75));
|
|
document.getElementById('hud-energy-fill').style.height = (energy / maxEnergy * 100) + '%';
|
|
document.getElementById('hud-energy-num').textContent = energy + '/' + maxEnergy;
|
|
if (energy < maxEnergy * 0.25) {
|
|
document.getElementById('hud-energy-fill').style.background = 'linear-gradient(to top, #c83030, #e06040)';
|
|
}
|
|
}
|
|
hudTick();
|
|
setInterval(hudTick, 30000);
|
|
|
|
// ===== BACKGROUND SCENE — starry sky + rolling hills + junimo hut =====
|
|
var sc = document.getElementById('scene');
|
|
var sctx = sc.getContext('2d');
|
|
|
|
function resizeScene() {
|
|
sc.width = window.innerWidth;
|
|
sc.height = window.innerHeight;
|
|
drawScene();
|
|
}
|
|
|
|
function drawScene() {
|
|
var w = sc.width, h = sc.height;
|
|
|
|
// night sky gradient
|
|
var sky = sctx.createLinearGradient(0, 0, 0, h);
|
|
sky.addColorStop(0, '#0a0820');
|
|
sky.addColorStop(0.3, '#161050');
|
|
sky.addColorStop(0.6, '#1b1040');
|
|
sky.addColorStop(0.85, '#1a2848');
|
|
sky.addColorStop(1, '#1a3028');
|
|
sctx.fillStyle = sky;
|
|
sctx.fillRect(0, 0, w, h);
|
|
|
|
// pixel stars — draw as small squares for pixel-art feel
|
|
var starSeed = 42;
|
|
function seededRandom() {
|
|
starSeed = (starSeed * 16807 + 0) % 2147483647;
|
|
return (starSeed - 1) / 2147483646;
|
|
}
|
|
for (var i = 0; i < 120; i++) {
|
|
var sx = seededRandom() * w;
|
|
var sy = seededRandom() * h * 0.7;
|
|
var ss = seededRandom() > 0.85 ? 2 : 1;
|
|
var sa = 0.3 + seededRandom() * 0.7;
|
|
sctx.fillStyle = 'rgba(255, 255, 240, ' + sa + ')';
|
|
sctx.fillRect(Math.floor(sx), Math.floor(sy), ss, ss);
|
|
}
|
|
|
|
// a few colored stars
|
|
for (var i = 0; i < 8; i++) {
|
|
var sx = seededRandom() * w;
|
|
var sy = seededRandom() * h * 0.5;
|
|
var colors = ['rgba(255,200,150,0.6)', 'rgba(150,200,255,0.5)', 'rgba(255,255,180,0.7)'];
|
|
sctx.fillStyle = colors[i % 3];
|
|
sctx.fillRect(Math.floor(sx), Math.floor(sy), 2, 2);
|
|
}
|
|
|
|
// ===== PIXEL MOON =====
|
|
var moonX = w * 0.15;
|
|
var moonY = h * 0.12;
|
|
var mp = 3; // moon pixel size
|
|
// moon glow
|
|
var mgrd = sctx.createRadialGradient(moonX, moonY, mp * 2, moonX, moonY, mp * 14);
|
|
mgrd.addColorStop(0, 'rgba(255, 250, 200, 0.12)');
|
|
mgrd.addColorStop(0.5, 'rgba(255, 250, 200, 0.04)');
|
|
mgrd.addColorStop(1, 'rgba(255, 250, 200, 0)');
|
|
sctx.fillStyle = mgrd;
|
|
sctx.beginPath();
|
|
sctx.arc(moonX, moonY, mp * 14, 0, Math.PI * 2);
|
|
sctx.fill();
|
|
// moon body — crescent shape drawn as pixels
|
|
var moonPx = [
|
|
// full circle pixels (8x8 ish)
|
|
[0,-3],[1,-3],
|
|
[-2,-2],[-1,-2],[0,-2],[1,-2],[2,-2],
|
|
[-3,-1],[-2,-1],[-1,-1],[0,-1],[1,-1],[2,-1],
|
|
[-3,0],[-2,0],[-1,0],[0,0],[1,0],[2,0],
|
|
[-3,1],[-2,1],[-1,1],[0,1],[1,1],[2,1],
|
|
[-2,2],[-1,2],[0,2],[1,2],[2,2],
|
|
[0,3],[1,3]
|
|
];
|
|
// shadow pixels to carve crescent (offset to upper-right)
|
|
var moonShadow = [
|
|
[1,-2],[2,-2],
|
|
[1,-1],[2,-1],[3,-1],
|
|
[2,0],[3,0],
|
|
[1,1],[2,1],[3,1],
|
|
[1,2],[2,2],
|
|
];
|
|
// draw moon base
|
|
sctx.fillStyle = '#f0e8c8';
|
|
for (var mi = 0; mi < moonPx.length; mi++) {
|
|
sctx.fillRect(moonX + moonPx[mi][0] * mp, moonY + moonPx[mi][1] * mp, mp, mp);
|
|
}
|
|
// lighter highlight
|
|
sctx.fillStyle = '#f8f0d8';
|
|
sctx.fillRect(moonX - 2 * mp, moonY - 1 * mp, mp, mp);
|
|
sctx.fillRect(moonX - 1 * mp, moonY - 2 * mp, mp, mp);
|
|
// carve out shadow for crescent
|
|
sctx.fillStyle = '#0a0820';
|
|
for (var mi = 0; mi < moonShadow.length; mi++) {
|
|
sctx.fillRect(moonX + moonShadow[mi][0] * mp, moonY + moonShadow[mi][1] * mp, mp, mp);
|
|
}
|
|
|
|
// distant hills (dark blue-green)
|
|
var hillBase = h * 0.82;
|
|
sctx.fillStyle = '#142828';
|
|
sctx.beginPath();
|
|
sctx.moveTo(0, hillBase);
|
|
for (var x = 0; x <= w; x += 3) {
|
|
var y = hillBase - Math.sin(x * 0.003) * 40 - Math.sin(x * 0.008 + 1) * 20 - Math.sin(x * 0.001 + 2) * 30;
|
|
sctx.lineTo(x, y);
|
|
}
|
|
sctx.lineTo(w, h);
|
|
sctx.lineTo(0, h);
|
|
sctx.closePath();
|
|
sctx.fill();
|
|
|
|
// ===== TOWN SILHOUETTE on distant hills =====
|
|
var tp = 2; // town pixel size
|
|
var townBase = function(tx) {
|
|
return hillBase - Math.sin(tx * 0.003) * 40 - Math.sin(tx * 0.008 + 1) * 20 - Math.sin(tx * 0.001 + 2) * 30;
|
|
};
|
|
|
|
// clock tower
|
|
var ctX = w * 0.3;
|
|
var ctY = townBase(ctX);
|
|
sctx.fillStyle = '#1a3030';
|
|
sctx.fillRect(ctX - tp * 2, ctY - tp * 10, tp * 4, tp * 10);
|
|
sctx.fillRect(ctX - tp * 3, ctY - tp * 8, tp * 6, tp * 2);
|
|
// pointed roof
|
|
sctx.fillRect(ctX - tp, ctY - tp * 12, tp * 2, tp * 2);
|
|
sctx.fillRect(ctX, ctY - tp * 13, tp, tp);
|
|
// clock face glow
|
|
sctx.fillStyle = 'rgba(240, 220, 120, 0.25)';
|
|
sctx.fillRect(ctX - tp, ctY - tp * 7, tp * 2, tp * 2);
|
|
|
|
// Pierre's shop
|
|
var psX = w * 0.35;
|
|
var psY = townBase(psX);
|
|
sctx.fillStyle = '#1a3030';
|
|
sctx.fillRect(psX - tp * 4, psY - tp * 6, tp * 8, tp * 6);
|
|
// roof
|
|
sctx.fillStyle = '#1a2828';
|
|
sctx.fillRect(psX - tp * 5, psY - tp * 7, tp * 10, tp);
|
|
sctx.fillRect(psX - tp * 4, psY - tp * 8, tp * 8, tp);
|
|
// windows
|
|
sctx.fillStyle = 'rgba(240, 200, 80, 0.15)';
|
|
sctx.fillRect(psX - tp * 2, psY - tp * 4, tp * 2, tp * 2);
|
|
sctx.fillRect(psX + tp, psY - tp * 4, tp * 2, tp * 2);
|
|
|
|
// community center (larger, to the left)
|
|
var ccX = w * 0.18;
|
|
var ccY = townBase(ccX);
|
|
sctx.fillStyle = '#162828';
|
|
sctx.fillRect(ccX - tp * 6, ccY - tp * 7, tp * 12, tp * 7);
|
|
sctx.fillRect(ccX - tp * 4, ccY - tp * 9, tp * 8, tp * 2);
|
|
sctx.fillRect(ccX - tp * 2, ccY - tp * 10, tp * 4, tp);
|
|
// chimney
|
|
sctx.fillRect(ccX + tp * 3, ccY - tp * 11, tp * 2, tp * 3);
|
|
// warm window glow
|
|
sctx.fillStyle = 'rgba(120, 220, 100, 0.12)';
|
|
sctx.fillRect(ccX - tp * 4, ccY - tp * 5, tp * 3, tp * 3);
|
|
sctx.fillStyle = 'rgba(240, 200, 80, 0.1)';
|
|
sctx.fillRect(ccX + tp * 2, ccY - tp * 5, tp * 3, tp * 3);
|
|
|
|
// small houses
|
|
var shColors = ['#1a2e2e', '#1a2a30', '#182a2a'];
|
|
for (var shi = 0; shi < 3; shi++) {
|
|
var shX = w * (0.4 + shi * 0.06);
|
|
var shY = townBase(shX);
|
|
sctx.fillStyle = shColors[shi];
|
|
sctx.fillRect(shX - tp * 2, shY - tp * 4, tp * 4, tp * 4);
|
|
sctx.fillRect(shX - tp * 3, shY - tp * 5, tp * 6, tp);
|
|
// tiny window
|
|
sctx.fillStyle = 'rgba(240, 200, 80, 0.12)';
|
|
sctx.fillRect(shX - tp, shY - tp * 3, tp, tp);
|
|
}
|
|
|
|
// mid hills (darker green)
|
|
var hill2Base = h * 0.88;
|
|
sctx.fillStyle = '#0f2218';
|
|
sctx.beginPath();
|
|
sctx.moveTo(0, hill2Base);
|
|
for (var x = 0; x <= w; x += 3) {
|
|
var y = hill2Base - Math.sin(x * 0.005 + 3) * 25 - Math.sin(x * 0.012 + 1) * 12;
|
|
sctx.lineTo(x, y);
|
|
}
|
|
sctx.lineTo(w, h);
|
|
sctx.lineTo(0, h);
|
|
sctx.closePath();
|
|
sctx.fill();
|
|
|
|
// foreground hill (deep dark)
|
|
var hill3Base = h * 0.93;
|
|
sctx.fillStyle = '#0a1810';
|
|
sctx.beginPath();
|
|
sctx.moveTo(0, hill3Base);
|
|
for (var x = 0; x <= w; x += 3) {
|
|
var y = hill3Base - Math.sin(x * 0.007 + 5) * 15 - Math.sin(x * 0.015) * 8;
|
|
sctx.lineTo(x, y);
|
|
}
|
|
sctx.lineTo(w, h);
|
|
sctx.lineTo(0, h);
|
|
sctx.closePath();
|
|
sctx.fill();
|
|
|
|
// ===== JUNIMO HUT — pixel art style =====
|
|
var hutX = w * 0.85;
|
|
var hutY = h * 0.82 - Math.sin(w * 0.85 * 0.005 + 3) * 25 - Math.sin(w * 0.85 * 0.012 + 1) * 12;
|
|
var p = 3; // pixel size
|
|
|
|
// hut body (brownish wood)
|
|
sctx.fillStyle = '#5a3a20';
|
|
for (var bx = -4; bx <= 4; bx++) {
|
|
for (var by = 0; by <= 5; by++) {
|
|
sctx.fillRect(hutX + bx * p, hutY - by * p, p, p);
|
|
}
|
|
}
|
|
|
|
// darker wood planks
|
|
sctx.fillStyle = '#4a2e18';
|
|
for (var bx = -4; bx <= 4; bx += 2) {
|
|
sctx.fillRect(hutX + bx * p, hutY - 1 * p, p, p);
|
|
sctx.fillRect(hutX + (bx+1) * p, hutY - 3 * p, p, p);
|
|
}
|
|
|
|
// door
|
|
sctx.fillStyle = '#2a1a10';
|
|
for (var by = 0; by <= 2; by++) {
|
|
sctx.fillRect(hutX - p, hutY - by * p, p, p);
|
|
sctx.fillRect(hutX, hutY - by * p, p, p);
|
|
}
|
|
|
|
// roof (thatched / straw colored)
|
|
sctx.fillStyle = '#8a7030';
|
|
for (var row = 0; row < 4; row++) {
|
|
var roofW = 6 - row;
|
|
for (var rx = -roofW; rx <= roofW; rx++) {
|
|
sctx.fillRect(hutX + rx * p, hutY - (6 + row) * p, p, p);
|
|
}
|
|
}
|
|
// roof highlight
|
|
sctx.fillStyle = '#a88838';
|
|
for (var rx = -4; rx <= 3; rx++) {
|
|
sctx.fillRect(hutX + rx * p, hutY - 7 * p, p, p);
|
|
}
|
|
|
|
// chimney
|
|
sctx.fillStyle = '#6a4428';
|
|
sctx.fillRect(hutX + 3 * p, hutY - 9 * p, p, p);
|
|
sctx.fillRect(hutX + 3 * p, hutY - 10 * p, p, p);
|
|
sctx.fillRect(hutX + 4 * p, hutY - 9 * p, p, p);
|
|
sctx.fillRect(hutX + 4 * p, hutY - 10 * p, p, p);
|
|
|
|
// window glow
|
|
sctx.fillStyle = 'rgba(240, 200, 80, 0.5)';
|
|
sctx.fillRect(hutX + 2 * p, hutY - 3 * p, p * 2, p * 2);
|
|
sctx.fillStyle = 'rgba(240, 200, 80, 0.15)';
|
|
sctx.beginPath();
|
|
sctx.arc(hutX + 3 * p, hutY - 2 * p, p * 6, 0, Math.PI * 2);
|
|
sctx.fill();
|
|
|
|
// a few pixel trees near the hut
|
|
function drawPixelTree(tx, ty, treeH) {
|
|
// trunk
|
|
sctx.fillStyle = '#3a2a18';
|
|
sctx.fillRect(tx, ty - treeH * p, p, treeH * p);
|
|
// canopy layers
|
|
var greens = ['#1a4a20', '#1e5a28', '#226830'];
|
|
for (var layer = 0; layer < 3; layer++) {
|
|
sctx.fillStyle = greens[layer];
|
|
var layerW = 3 - layer;
|
|
var layerY = ty - (treeH + layer * 2) * p;
|
|
for (var lx = -layerW; lx <= layerW; lx++) {
|
|
sctx.fillRect(tx + lx * p, layerY, p, p * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
drawPixelTree(hutX - 10 * p, hutY + p, 4);
|
|
drawPixelTree(hutX + 10 * p, hutY + p, 3);
|
|
drawPixelTree(hutX - 14 * p, hutY + 2 * p, 5);
|
|
|
|
// ===== FARM BUILDINGS =====
|
|
// silo (tall, thin, to the right of hut)
|
|
var siloX = hutX + 18 * p;
|
|
var siloY = hutY + 2 * p;
|
|
sctx.fillStyle = '#5a4a38';
|
|
sctx.fillRect(siloX - p * 2, siloY - p * 10, p * 4, p * 10);
|
|
// silo cap
|
|
sctx.fillStyle = '#7a6040';
|
|
sctx.fillRect(siloX - p * 3, siloY - p * 11, p * 6, p);
|
|
sctx.fillRect(siloX - p * 2, siloY - p * 12, p * 4, p);
|
|
// silo bands
|
|
sctx.fillStyle = '#4a3828';
|
|
sctx.fillRect(siloX - p * 2, siloY - p * 4, p * 4, p);
|
|
sctx.fillRect(siloX - p * 2, siloY - p * 8, p * 4, p);
|
|
|
|
// barn (wide, left of hut)
|
|
var barnX = hutX - 22 * p;
|
|
var barnY = hutY + 3 * p;
|
|
// barn walls
|
|
sctx.fillStyle = '#6a3028';
|
|
for (var bxi = -5; bxi <= 5; bxi++) {
|
|
for (var byi = 0; byi <= 6; byi++) {
|
|
sctx.fillRect(barnX + bxi * p, barnY - byi * p, p, p);
|
|
}
|
|
}
|
|
// barn roof
|
|
sctx.fillStyle = '#4a4a4a';
|
|
for (var row = 0; row < 3; row++) {
|
|
var roofW = 7 - row;
|
|
for (var rx = -roofW; rx <= roofW; rx++) {
|
|
sctx.fillRect(barnX + rx * p, barnY - (7 + row) * p, p, p);
|
|
}
|
|
}
|
|
// barn door
|
|
sctx.fillStyle = '#3a1a10';
|
|
sctx.fillRect(barnX - p, barnY - p * 3, p * 3, p * 3);
|
|
// hay window
|
|
sctx.fillStyle = 'rgba(200, 180, 80, 0.2)';
|
|
sctx.fillRect(barnX + p * 2, barnY - p * 5, p * 2, p * 2);
|
|
|
|
// shipping bin (small box near hut)
|
|
var sbX = hutX - 7 * p;
|
|
var sbY = hutY + p;
|
|
sctx.fillStyle = '#5a4a30';
|
|
sctx.fillRect(sbX - p * 2, sbY - p * 2, p * 4, p * 2);
|
|
sctx.fillStyle = '#6a5838';
|
|
sctx.fillRect(sbX - p * 2, sbY - p * 3, p * 4, p);
|
|
// lid highlight
|
|
sctx.fillStyle = '#7a6840';
|
|
sctx.fillRect(sbX - p, sbY - p * 3, p * 2, p);
|
|
|
|
// ===== FOREGROUND DETAILS — fence, flowers, mushrooms =====
|
|
var fgY = h * 0.93;
|
|
var fp = 2; // foreground pixel size
|
|
|
|
// wooden fence posts across the bottom
|
|
function drawFencePost(fx, fy) {
|
|
// post
|
|
sctx.fillStyle = '#4a3520';
|
|
sctx.fillRect(fx, fy - fp * 5, fp, fp * 5);
|
|
sctx.fillRect(fx + fp, fy - fp * 5, fp, fp * 5);
|
|
// cap
|
|
sctx.fillStyle = '#5a4228';
|
|
sctx.fillRect(fx - fp, fy - fp * 6, fp * 4, fp);
|
|
// highlight
|
|
sctx.fillStyle = '#6a5238';
|
|
sctx.fillRect(fx, fy - fp * 5, fp, fp);
|
|
}
|
|
|
|
// rail between posts
|
|
function drawFenceRail(fx1, fx2, fy) {
|
|
sctx.fillStyle = '#4a3520';
|
|
sctx.fillRect(fx1 + fp * 2, fy - fp * 3, fx2 - fx1 - fp * 2, fp);
|
|
sctx.fillStyle = '#3a2818';
|
|
sctx.fillRect(fx1 + fp * 2, fy - fp * 2, fx2 - fx1 - fp * 2, fp);
|
|
}
|
|
|
|
// place fence posts
|
|
var fencePosts = [];
|
|
for (var fi = 0; fi < 6; fi++) {
|
|
var fx = w * 0.05 + fi * w * 0.12;
|
|
var fy = fgY - Math.sin(fx * 0.007 + 5) * 15 - Math.sin(fx * 0.015) * 8 + fp * 2;
|
|
fencePosts.push({ x: fx, y: fy });
|
|
drawFencePost(fx, fy);
|
|
}
|
|
for (var fi = 0; fi < fencePosts.length - 1; fi++) {
|
|
drawFenceRail(fencePosts[fi].x, fencePosts[fi + 1].x, fencePosts[fi].y);
|
|
}
|
|
|
|
// pixel flowers
|
|
function drawFlower(fx, fy, petalColor, centerColor) {
|
|
// stem
|
|
sctx.fillStyle = '#2a5a1a';
|
|
sctx.fillRect(fx, fy - fp * 2, fp, fp * 2);
|
|
// petals
|
|
sctx.fillStyle = petalColor;
|
|
sctx.fillRect(fx - fp, fy - fp * 3, fp, fp);
|
|
sctx.fillRect(fx + fp, fy - fp * 3, fp, fp);
|
|
sctx.fillRect(fx, fy - fp * 4, fp, fp);
|
|
sctx.fillRect(fx, fy - fp * 2, fp, fp);
|
|
// center
|
|
sctx.fillStyle = centerColor;
|
|
sctx.fillRect(fx, fy - fp * 3, fp, fp);
|
|
}
|
|
|
|
// pixel mushroom
|
|
function drawMushroom(mx, my, capColor) {
|
|
// stem
|
|
sctx.fillStyle = '#e8e0d0';
|
|
sctx.fillRect(mx, my - fp * 2, fp, fp * 2);
|
|
sctx.fillRect(mx + fp, my - fp * 2, fp, fp * 2);
|
|
// cap
|
|
sctx.fillStyle = capColor;
|
|
sctx.fillRect(mx - fp, my - fp * 4, fp * 4, fp);
|
|
sctx.fillRect(mx - fp * 2, my - fp * 3, fp * 6, fp);
|
|
// spots
|
|
sctx.fillStyle = '#f8f0e0';
|
|
sctx.fillRect(mx - fp, my - fp * 3, fp, fp);
|
|
sctx.fillRect(mx + fp * 2, my - fp * 3, fp, fp);
|
|
}
|
|
|
|
// pixel parsnip/turnip
|
|
function drawCrop(cx, cy) {
|
|
// leaves
|
|
sctx.fillStyle = '#3a8a2a';
|
|
sctx.fillRect(cx, cy - fp * 4, fp, fp * 2);
|
|
sctx.fillRect(cx - fp, cy - fp * 3, fp, fp);
|
|
sctx.fillRect(cx + fp, cy - fp * 3, fp, fp);
|
|
// root
|
|
sctx.fillStyle = '#e8d8a0';
|
|
sctx.fillRect(cx, cy - fp * 2, fp, fp * 2);
|
|
sctx.fillStyle = '#d0c080';
|
|
sctx.fillRect(cx, cy - fp, fp, fp);
|
|
}
|
|
|
|
// scatter flowers, mushrooms, crops along the foreground
|
|
var fgSeed = 77;
|
|
function fgRand() {
|
|
fgSeed = (fgSeed * 16807) % 2147483647;
|
|
return (fgSeed - 1) / 2147483646;
|
|
}
|
|
|
|
var flowerColors = [
|
|
['#e06080', '#f0d060'], // pink/yellow
|
|
['#6080e0', '#f0e080'], // blue/yellow
|
|
['#e0e060', '#e08040'], // yellow/orange
|
|
['#d060d0', '#f0d060'], // purple/yellow
|
|
['#f08080', '#f0e080'], // red/yellow
|
|
];
|
|
|
|
for (var di = 0; di < 14; di++) {
|
|
var dx = fgRand() * w * 0.95 + w * 0.02;
|
|
var dy = fgY - Math.sin(dx * 0.007 + 5) * 15 - Math.sin(dx * 0.015) * 8;
|
|
var what = fgRand();
|
|
if (what < 0.45) {
|
|
var fc = flowerColors[Math.floor(fgRand() * flowerColors.length)];
|
|
drawFlower(dx, dy, fc[0], fc[1]);
|
|
} else if (what < 0.7) {
|
|
var capCol = fgRand() > 0.5 ? '#c83030' : '#d08030';
|
|
drawMushroom(dx, dy, capCol);
|
|
} else {
|
|
drawCrop(dx, dy);
|
|
}
|
|
}
|
|
}
|
|
|
|
resizeScene();
|
|
window.addEventListener('resize', resizeScene);
|
|
|
|
// ===== TWINKLING STARS (animated) =====
|
|
var twinkleStars = [];
|
|
var starSeed2 = 99;
|
|
function sr2() {
|
|
starSeed2 = (starSeed2 * 16807) % 2147483647;
|
|
return (starSeed2 - 1) / 2147483646;
|
|
}
|
|
for (var i = 0; i < 20; i++) {
|
|
twinkleStars.push({
|
|
x: sr2(), y: sr2() * 0.65,
|
|
phase: sr2() * Math.PI * 2,
|
|
speed: 0.5 + sr2() * 1.5,
|
|
size: sr2() > 0.7 ? 2 : 1
|
|
});
|
|
}
|
|
|
|
// ===== FIREFLY PARTICLES =====
|
|
var pc = document.getElementById('particles');
|
|
var pctx = pc.getContext('2d');
|
|
var dots = [];
|
|
|
|
function resizeParticles() {
|
|
pc.width = window.innerWidth;
|
|
pc.height = window.innerHeight;
|
|
}
|
|
resizeParticles();
|
|
window.addEventListener('resize', resizeParticles);
|
|
|
|
for (var i = 0; i < 30; i++) {
|
|
dots.push({
|
|
x: Math.random() * pc.width,
|
|
y: Math.random() * pc.height,
|
|
vy: (Math.random() - 0.5) * 0.1,
|
|
vx: (Math.random() - 0.5) * 0.15,
|
|
r: Math.random() * 1.2 + 0.5,
|
|
o: Math.random() * 0.4 + 0.1,
|
|
drift: Math.random() * Math.PI * 2,
|
|
hue: Math.random() > 0.6 ? '200, 230, 80' : '240, 210, 60'
|
|
});
|
|
}
|
|
|
|
// ===== SHOOTING STARS =====
|
|
var shootingStars = [];
|
|
|
|
function spawnShootingStar() {
|
|
shootingStars.push({
|
|
x: Math.random() * pc.width * 0.7,
|
|
y: Math.random() * pc.height * 0.3,
|
|
vx: 3 + Math.random() * 4,
|
|
vy: 1.5 + Math.random() * 2,
|
|
life: 0,
|
|
maxLife: 30 + Math.random() * 30,
|
|
size: 1 + (Math.random() > 0.7 ? 1 : 0),
|
|
trail: []
|
|
});
|
|
}
|
|
|
|
function drawParticles() {
|
|
pctx.clearRect(0, 0, pc.width, pc.height);
|
|
var t = Date.now() * 0.001;
|
|
|
|
// twinkling stars
|
|
for (var i = 0; i < twinkleStars.length; i++) {
|
|
var ts = twinkleStars[i];
|
|
var brightness = 0.3 + 0.7 * Math.pow((Math.sin(t * ts.speed + ts.phase) + 1) / 2, 2);
|
|
pctx.fillStyle = 'rgba(255, 255, 240, ' + brightness + ')';
|
|
pctx.fillRect(
|
|
Math.floor(ts.x * pc.width),
|
|
Math.floor(ts.y * pc.height),
|
|
ts.size, ts.size
|
|
);
|
|
}
|
|
|
|
// shooting stars
|
|
if (Math.random() < 0.003) spawnShootingStar();
|
|
for (var si = shootingStars.length - 1; si >= 0; si--) {
|
|
var ss = shootingStars[si];
|
|
ss.life++;
|
|
ss.x += ss.vx;
|
|
ss.y += ss.vy;
|
|
ss.trail.push({ x: ss.x, y: ss.y });
|
|
if (ss.trail.length > 12) ss.trail.shift();
|
|
var ssa = Math.max(0, 1 - ss.life / ss.maxLife);
|
|
// draw trail
|
|
for (var ti = 0; ti < ss.trail.length; ti++) {
|
|
var ta = (ti / ss.trail.length) * ssa * 0.8;
|
|
pctx.fillStyle = 'rgba(255, 255, 220, ' + ta + ')';
|
|
pctx.fillRect(Math.floor(ss.trail[ti].x), Math.floor(ss.trail[ti].y), ss.size, ss.size);
|
|
}
|
|
// bright head
|
|
pctx.fillStyle = 'rgba(255, 255, 240, ' + ssa + ')';
|
|
pctx.fillRect(Math.floor(ss.x), Math.floor(ss.y), ss.size + 1, ss.size + 1);
|
|
// sparkle glow around head
|
|
pctx.beginPath();
|
|
pctx.arc(ss.x, ss.y, ss.size * 3, 0, Math.PI * 2);
|
|
pctx.fillStyle = 'rgba(255, 255, 200, ' + (ssa * 0.15) + ')';
|
|
pctx.fill();
|
|
if (ss.life > ss.maxLife) shootingStars.splice(si, 1);
|
|
}
|
|
|
|
// fireflies
|
|
for (var i = 0; i < dots.length; i++) {
|
|
var d = dots[i];
|
|
d.x += d.vx + Math.sin(t * 0.7 + d.drift) * 0.1;
|
|
d.y += d.vy + Math.cos(t * 0.5 + d.drift) * 0.08;
|
|
if (d.y < -5) d.y = pc.height + 5;
|
|
if (d.y > pc.height + 5) d.y = -5;
|
|
if (d.x < -5) d.x = pc.width + 5;
|
|
if (d.x > pc.width + 5) d.x = -5;
|
|
var flicker = d.o * (0.3 + 0.7 * Math.pow((Math.sin(t * 2.5 + d.drift) + 1) / 2, 3));
|
|
// outer glow
|
|
pctx.beginPath();
|
|
pctx.arc(d.x, d.y, d.r * 4, 0, Math.PI * 2);
|
|
pctx.fillStyle = 'rgba(' + d.hue + ', ' + (flicker * 0.15) + ')';
|
|
pctx.fill();
|
|
// core
|
|
pctx.beginPath();
|
|
pctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);
|
|
pctx.fillStyle = 'rgba(' + d.hue + ', ' + flicker + ')';
|
|
pctx.fill();
|
|
}
|
|
requestAnimationFrame(drawParticles);
|
|
}
|
|
drawParticles();
|
|
|
|
// ===== CURSOR TRAIL — golden sparkle =====
|
|
var tc = document.getElementById('trail');
|
|
var tctx = tc.getContext('2d');
|
|
var points = [];
|
|
var mx = 0, my = 0;
|
|
|
|
function resizeTrail() {
|
|
tc.width = window.innerWidth;
|
|
tc.height = window.innerHeight;
|
|
}
|
|
resizeTrail();
|
|
window.addEventListener('resize', resizeTrail);
|
|
|
|
document.addEventListener('mousemove', function(e) {
|
|
mx = e.clientX;
|
|
my = e.clientY;
|
|
points.push({ x: e.clientX, y: e.clientY, t: Date.now() });
|
|
if (points.length > 25) points.shift();
|
|
});
|
|
|
|
function drawTrail() {
|
|
tctx.clearRect(0, 0, tc.width, tc.height);
|
|
var now = Date.now();
|
|
while (points.length && now - points[0].t > 350) points.shift();
|
|
if (points.length > 1) {
|
|
for (var i = 1; i < points.length; i++) {
|
|
var age = (now - points[i].t) / 350;
|
|
var alpha = (1 - age) * 0.35;
|
|
var width = (1 - age) * 2;
|
|
tctx.beginPath();
|
|
tctx.moveTo(points[i-1].x, points[i-1].y);
|
|
tctx.lineTo(points[i].x, points[i].y);
|
|
tctx.strokeStyle = 'rgba(240, 216, 120, ' + alpha + ')';
|
|
tctx.lineWidth = width;
|
|
tctx.lineCap = 'round';
|
|
tctx.stroke();
|
|
}
|
|
}
|
|
requestAnimationFrame(drawTrail);
|
|
}
|
|
drawTrail();
|
|
|
|
// ===== JUNIMO SPIRITS — pixel-art style =====
|
|
var jc = document.getElementById('junimos');
|
|
var jctx = jc.getContext('2d');
|
|
var spirits = [];
|
|
var numSpirits = 8;
|
|
|
|
// junimo colors matching stardew valley
|
|
var junimoColors = [
|
|
{ body: [76, 200, 60], light: [110, 230, 90], tip: [180, 80, 200] }, // green w/ purple tip
|
|
{ body: [140, 80, 200], light: [170, 120, 230], tip: [200, 80, 180] }, // purple w/ pink tip
|
|
{ body: [220, 200, 50], light: [240, 225, 90], tip: [240, 180, 50] }, // yellow w/ orange tip
|
|
{ body: [80, 120, 220], light: [110, 150, 240], tip: [80, 60, 200] }, // blue w/ indigo tip
|
|
{ body: [230, 140, 60], light: [245, 170, 90], tip: [230, 100, 50] }, // orange
|
|
{ body: [80, 210, 190], light: [120, 235, 215], tip: [60, 180, 160] }, // teal
|
|
{ body: [200, 60, 70], light: [230, 90, 100], tip: [170, 40, 50] }, // red
|
|
{ body: [230, 120, 170],light: [245, 155, 200], tip: [200, 80, 140] } // pink
|
|
];
|
|
|
|
function resizeJunimos() {
|
|
jc.width = window.innerWidth;
|
|
jc.height = window.innerHeight;
|
|
}
|
|
resizeJunimos();
|
|
window.addEventListener('resize', resizeJunimos);
|
|
|
|
// hop sparkle particles
|
|
var sparkles = [];
|
|
|
|
function spawnSparkles(sx, sy, col) {
|
|
for (var k = 0; k < 6; k++) {
|
|
sparkles.push({
|
|
x: sx + (Math.random() - 0.5) * 8,
|
|
y: sy,
|
|
vx: (Math.random() - 0.5) * 2,
|
|
vy: -(Math.random() * 2.5 + 1),
|
|
life: 0,
|
|
maxLife: 15 + Math.random() * 15,
|
|
size: 1 + (Math.random() > 0.5 ? 1 : 0),
|
|
color: col
|
|
});
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < numSpirits; i++) {
|
|
spirits.push({
|
|
x: 0.08 + (i / numSpirits) * 0.84 + (Math.random() - 0.5) * 0.08,
|
|
baseY: 0.78 + Math.random() * 0.15,
|
|
size: 14 + Math.random() * 8,
|
|
phase: Math.random() * Math.PI * 2,
|
|
bounceSpeed: 2.5 + Math.random() * 1.5,
|
|
wobbleSpeed: 1.2 + Math.random() * 0.8,
|
|
opacity: 0.5 + Math.random() * 0.3,
|
|
targetOpacity: 0.85 + Math.random() * 0.15,
|
|
fadeSpeed: 0.01 + Math.random() * 0.005,
|
|
appearing: true,
|
|
timer: 0,
|
|
lifespan: 800 + Math.random() * 600,
|
|
hopTime: 0,
|
|
hopping: false,
|
|
colorIdx: i % junimoColors.length,
|
|
armWave: Math.random() * Math.PI * 2,
|
|
lookAngle: 0,
|
|
excited: false,
|
|
excitedTimer: 0
|
|
});
|
|
}
|
|
|
|
function drawPixelRect(x, y, w, h, color) {
|
|
jctx.fillStyle = color;
|
|
jctx.fillRect(Math.round(x), Math.round(y), Math.round(w), Math.round(h));
|
|
}
|
|
|
|
function drawJunimoSpirit(x, y, size, bounce, wobble, opacity, sp) {
|
|
jctx.save();
|
|
jctx.translate(Math.round(x), Math.round(y));
|
|
jctx.globalAlpha = opacity;
|
|
|
|
var col = junimoColors[sp.colorIdx];
|
|
var p = Math.max(1, Math.round(size / 7)); // pixel unit size
|
|
var t = Date.now() * 0.001;
|
|
|
|
// soft colored glow underneath
|
|
var grd = jctx.createRadialGradient(0, 0, p, 0, 0, size * 1.2);
|
|
grd.addColorStop(0, 'rgba(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ', 0.15)');
|
|
grd.addColorStop(1, 'rgba(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ', 0)');
|
|
jctx.fillStyle = grd;
|
|
jctx.beginPath();
|
|
jctx.arc(0, 0, size * 1.2, 0, Math.PI * 2);
|
|
jctx.fill();
|
|
|
|
jctx.save();
|
|
jctx.rotate(wobble);
|
|
|
|
// body dimensions: rounded square, ~5p wide x 5p tall
|
|
var bw = p * 5; // body width
|
|
var bh = p * 5; // body height
|
|
var bx = -bw / 2;
|
|
var by = -bh / 2 - p;
|
|
|
|
// === BLACK OUTLINE (drawn first, slightly larger) ===
|
|
var o = Math.max(1, Math.round(p * 0.4)); // outline thickness
|
|
|
|
// outline - top row (narrower)
|
|
drawPixelRect(bx + p, by - o, bw - p * 2, o, 'rgba(0,0,0,0.9)');
|
|
// outline - bottom row (narrower)
|
|
drawPixelRect(bx + p, by + bh, bw - p * 2, o, 'rgba(0,0,0,0.9)');
|
|
// outline - left column
|
|
drawPixelRect(bx - o, by + p, o, bh - p * 2, 'rgba(0,0,0,0.9)');
|
|
// outline - right column
|
|
drawPixelRect(bx + bw, by + p, o, bh - p * 2, 'rgba(0,0,0,0.9)');
|
|
// corner pixels for rounding
|
|
drawPixelRect(bx, by, p, p, 'rgba(0,0,0,0.9)');
|
|
drawPixelRect(bx + bw - p, by, p, p, 'rgba(0,0,0,0.9)');
|
|
drawPixelRect(bx, by + bh - p, p, p, 'rgba(0,0,0,0.9)');
|
|
drawPixelRect(bx + bw - p, by + bh - p, p, p, 'rgba(0,0,0,0.9)');
|
|
|
|
// === MAIN BODY FILL ===
|
|
var bodyColor = 'rgb(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ')';
|
|
var lightColor = 'rgb(' + col.light[0] + ',' + col.light[1] + ',' + col.light[2] + ')';
|
|
|
|
// fill body (rounded square — skip corners)
|
|
// top row (narrower)
|
|
drawPixelRect(bx + p, by, bw - p * 2, p, bodyColor);
|
|
// middle rows (full width)
|
|
drawPixelRect(bx, by + p, bw, bh - p * 2, bodyColor);
|
|
// bottom row (narrower)
|
|
drawPixelRect(bx + p, by + bh - p, bw - p * 2, p, bodyColor);
|
|
|
|
// highlight on upper-left area
|
|
drawPixelRect(bx + p, by + p, p * 2, p, lightColor);
|
|
drawPixelRect(bx + p, by + p * 2, p, p, lightColor);
|
|
|
|
// === ANTENNA / STEM ===
|
|
// dark stem line going up from top center
|
|
drawPixelRect(-p / 2, by - p * 3, p, p * 3, 'rgba(0,0,0,0.85)');
|
|
// colored tip at top of stem
|
|
var tipColor = 'rgb(' + col.tip[0] + ',' + col.tip[1] + ',' + col.tip[2] + ')';
|
|
drawPixelRect(-p, by - p * 4, p * 2, p, tipColor);
|
|
|
|
// === ARMS — tiny black sticks on sides ===
|
|
var armBob = Math.sin(t * 3 + sp.armWave) * p * 0.5;
|
|
// left arm
|
|
drawPixelRect(bx - p * 2, by + p * 2 + armBob, p * 2, p, 'rgba(0,0,0,0.85)');
|
|
// right arm
|
|
drawPixelRect(bx + bw, by + p * 2 - armBob, p * 2, p, 'rgba(0,0,0,0.85)');
|
|
|
|
// === FEET — two small black bumps at bottom ===
|
|
drawPixelRect(bx + p, by + bh, p, p, 'rgba(0,0,0,0.85)');
|
|
drawPixelRect(bx + bw - p * 2, by + bh, p, p, 'rgba(0,0,0,0.85)');
|
|
|
|
// === EYES — two tiny dark dots ===
|
|
drawPixelRect(bx + p, by + p * 2, p, p, 'rgba(10,10,20,0.9)');
|
|
drawPixelRect(bx + p * 3, by + p * 2, p, p, 'rgba(10,10,20,0.9)');
|
|
|
|
// === CHEEKS — pink marks ===
|
|
drawPixelRect(bx, by + p * 3, p, p, 'rgba(240,130,160,0.7)');
|
|
drawPixelRect(bx + bw - p, by + p * 3, p, p, 'rgba(240,130,160,0.7)');
|
|
|
|
jctx.restore(); // wobble
|
|
jctx.restore(); // position
|
|
}
|
|
|
|
function drawJunimos() {
|
|
jctx.clearRect(0, 0, jc.width, jc.height);
|
|
var t = Date.now() * 0.001;
|
|
|
|
for (var i = 0; i < spirits.length; i++) {
|
|
var s = spirits[i];
|
|
s.timer++;
|
|
|
|
if (s.appearing) {
|
|
s.opacity += s.fadeSpeed;
|
|
if (s.opacity >= s.targetOpacity) s.opacity = s.targetOpacity;
|
|
if (s.timer > s.lifespan) s.appearing = false;
|
|
} else {
|
|
s.opacity -= s.fadeSpeed;
|
|
if (s.opacity <= 0) {
|
|
s.opacity = 0;
|
|
s.x = Math.random() * 0.84 + 0.08;
|
|
s.baseY = 0.78 + Math.random() * 0.15;
|
|
s.targetOpacity = 0.85 + Math.random() * 0.15;
|
|
s.timer = 0;
|
|
s.lifespan = 800 + Math.random() * 600;
|
|
s.appearing = true;
|
|
s.colorIdx = Math.floor(Math.random() * junimoColors.length);
|
|
s.hopTime = 0;
|
|
s.excited = false;
|
|
}
|
|
}
|
|
|
|
if (s.opacity <= 0) continue;
|
|
|
|
var px = s.x * jc.width;
|
|
var py = s.baseY * jc.height;
|
|
|
|
// cursor interaction — detect proximity
|
|
var dx = mx - px;
|
|
var dy = my - py;
|
|
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
var cursorNear = dist < 120;
|
|
|
|
// look toward cursor — tilt head slightly
|
|
var targetLook = 0;
|
|
if (dist < 300 && s.opacity > 0.2) {
|
|
targetLook = Math.atan2(dx, 100) * 0.15; // subtle lean toward cursor
|
|
}
|
|
s.lookAngle += (targetLook - s.lookAngle) * 0.08;
|
|
|
|
// get excited when cursor is close — start hopping rapidly
|
|
if (cursorNear && !s.excited && s.opacity > 0.3) {
|
|
s.excited = true;
|
|
s.excitedTimer = 0;
|
|
if (!s.hopping) {
|
|
s.hopping = true;
|
|
s.hopTime = 0;
|
|
// spawn sparkles on excited hop
|
|
var col = junimoColors[s.colorIdx];
|
|
spawnSparkles(px, py, 'rgba(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ',');
|
|
}
|
|
}
|
|
if (s.excited) {
|
|
s.excitedTimer++;
|
|
// keep hopping while excited (rapid little hops)
|
|
if (!s.hopping && s.excitedTimer % 20 === 0 && cursorNear) {
|
|
s.hopping = true;
|
|
s.hopTime = 0;
|
|
var col = junimoColors[s.colorIdx];
|
|
spawnSparkles(px, py, 'rgba(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ',');
|
|
}
|
|
if (!cursorNear && s.excitedTimer > 40) s.excited = false;
|
|
}
|
|
|
|
// random hopping (when not excited)
|
|
if (!s.hopping && !s.excited && Math.random() < 0.005) {
|
|
s.hopping = true;
|
|
s.hopTime = 0;
|
|
var col = junimoColors[s.colorIdx];
|
|
spawnSparkles(px, py, 'rgba(' + col.body[0] + ',' + col.body[1] + ',' + col.body[2] + ',');
|
|
}
|
|
|
|
var bounce = Math.abs(Math.sin(t * s.bounceSpeed + s.phase)) * 2;
|
|
if (s.hopping) {
|
|
s.hopTime++;
|
|
var hopHeight = s.excited ? 7 : 10;
|
|
var hopDuration = s.excited ? 20 : 35;
|
|
bounce = Math.sin(s.hopTime * 0.15) * hopHeight * Math.max(0, 1 - s.hopTime / hopDuration);
|
|
if (bounce < 0) bounce = 0;
|
|
if (s.hopTime > hopDuration) s.hopping = false;
|
|
}
|
|
|
|
var wobble = Math.sin(t * s.wobbleSpeed + s.phase) * 0.06 + s.lookAngle;
|
|
if (s.hopping) {
|
|
wobble = Math.sin(s.hopTime * 0.3) * 0.12 + s.lookAngle;
|
|
}
|
|
// extra wiggle when excited
|
|
if (s.excited && cursorNear) {
|
|
wobble += Math.sin(t * 12) * 0.05;
|
|
}
|
|
|
|
py -= bounce;
|
|
|
|
drawJunimoSpirit(px, py, s.size, bounce, wobble, s.opacity, s);
|
|
}
|
|
|
|
// draw sparkles
|
|
for (var si = sparkles.length - 1; si >= 0; si--) {
|
|
var sp = sparkles[si];
|
|
sp.life++;
|
|
sp.x += sp.vx;
|
|
sp.y += sp.vy;
|
|
sp.vy += 0.08; // gravity
|
|
var sa = Math.max(0, 1 - sp.life / sp.maxLife);
|
|
// pixel sparkle — tiny colored square
|
|
jctx.fillStyle = sp.color + sa + ')';
|
|
jctx.fillRect(Math.round(sp.x), Math.round(sp.y), sp.size, sp.size);
|
|
// white highlight sparkle
|
|
if (sp.life < sp.maxLife * 0.5) {
|
|
jctx.fillStyle = 'rgba(255,255,255,' + (sa * 0.6) + ')';
|
|
jctx.fillRect(Math.round(sp.x), Math.round(sp.y), 1, 1);
|
|
}
|
|
if (sp.life > sp.maxLife) sparkles.splice(si, 1);
|
|
}
|
|
|
|
requestAnimationFrame(drawJunimos);
|
|
}
|
|
drawJunimos();
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|