Compare commits
7 commits
0caf1fbbf8
...
2f4994918d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f4994918d | ||
|
|
0474a9ab83 | ||
|
|
54666b0fc2 | ||
|
|
1c1a854875 | ||
|
|
6415112f9a | ||
|
|
05e465e33f | ||
|
|
998de4a945 |
7 changed files with 3316 additions and 24 deletions
|
|
@ -33,6 +33,9 @@ plugin/
|
|||
contents/ui/
|
||||
main.qml # Plasma WallpaperItem — hosts everything
|
||||
dancer.html # Self-contained animation (HTML + CSS + JS)
|
||||
starcatalog.js # Generated star data (Yale BSC) — see tools/build_catalog.py
|
||||
tools/
|
||||
build_catalog.py # Regenerates starcatalog.js from the Yale Bright Star Catalog
|
||||
```
|
||||
|
||||
### main.qml — the Plasma host
|
||||
|
|
@ -47,7 +50,7 @@ Pure HTML/CSS/JS, no external dependencies. All visuals are self-contained:
|
|||
|
||||
| Layer | Technique |
|
||||
|---|---|
|
||||
| Starfield | `<canvas>` + `requestAnimationFrame` |
|
||||
| Realistic sky (stars + Milky Way) | `<canvas>`; real-time alt-az projection of `starcatalog.js` (2,887 Yale BSC stars) + seeded procedural Milky Way for the configured lat/long (default Toledo-PR). Static layer pre-rendered offscreen, bright stars twinkle per frame; reprojects every 30 s. Debug: `window.__skyTimeOffset` (ms) fast-forwards the sky |
|
||||
| Nebula blobs, grid floor, horizon | CSS animations |
|
||||
| DANCEBOT-9000 robot | Pure CSS (divs + `@keyframes`) |
|
||||
| Chest EQ bars & VU meter | JS-driven `requestAnimationFrame` |
|
||||
|
|
|
|||
|
|
@ -14,5 +14,11 @@
|
|||
<entry name="ChuvaMode" type="Bool">
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="Latitude" type="Double">
|
||||
<default>-24.72</default>
|
||||
</entry>
|
||||
<entry name="Longitude" type="Double">
|
||||
<default>-53.74</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ ColumnLayout {
|
|||
property alias cfg_ShowSkeleton: skeletonCheck.checked
|
||||
property alias cfg_BaladaMode: baladaCheck.checked
|
||||
property alias cfg_ChuvaMode: chuvaCheck.checked
|
||||
property double cfg_Latitude
|
||||
property double cfg_Longitude
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
|
|
@ -34,5 +36,28 @@ ColumnLayout {
|
|||
text: i18n("Lua + chuva + relâmpagos")
|
||||
onCheckedChanged: if (checked) baladaCheck.checked = false
|
||||
}
|
||||
|
||||
Kirigami.Separator { Kirigami.FormData.isSection: true }
|
||||
|
||||
// Observador do céu realista (aceita vírgula ou ponto decimal)
|
||||
TextField {
|
||||
id: latField
|
||||
Kirigami.FormData.label: i18n("Latitude:")
|
||||
Component.onCompleted: text = root.cfg_Latitude
|
||||
onTextChanged: {
|
||||
const v = parseFloat(text.replace(",", "."))
|
||||
if (!isNaN(v) && v >= -90 && v <= 90) root.cfg_Latitude = v
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: lonField
|
||||
Kirigami.FormData.label: i18n("Longitude:")
|
||||
Component.onCompleted: text = root.cfg_Longitude
|
||||
onTextChanged: {
|
||||
const v = parseFloat(text.replace(",", "."))
|
||||
if (!isNaN(v) && v >= -180 && v <= 180) root.cfg_Longitude = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ body.balada .nebula::after { opacity: 1; }
|
|||
.album-art {
|
||||
position: absolute;
|
||||
top: 4%;
|
||||
right: 2%;
|
||||
left: 2%;
|
||||
width: 330px;
|
||||
height: 330px;
|
||||
border-radius: 6px;
|
||||
|
|
@ -254,7 +254,7 @@ body.balada .music-ticker {
|
|||
============================================================ */
|
||||
.lyric-display {
|
||||
position: absolute;
|
||||
left: 2%;
|
||||
right: 2%;
|
||||
top: 4%;
|
||||
width: 38%;
|
||||
height: 150px;
|
||||
|
|
@ -273,6 +273,7 @@ body.balada .music-ticker {
|
|||
.lyric-ctx {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: right; /* letra ancorada à direita da tela */
|
||||
font-family: 'Courier New', monospace;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: normal;
|
||||
|
|
@ -1198,6 +1199,7 @@ body.chuva #sunChuvaFace { opacity: 1; transition: opacity 5s ease; }
|
|||
|
||||
</div><!-- .scene -->
|
||||
|
||||
<script src="starcatalog.js"></script>
|
||||
<script>
|
||||
/* ============================================================
|
||||
CONSTANTS
|
||||
|
|
@ -1207,47 +1209,287 @@ const BPM = 120;
|
|||
const BEAT_MS = 60000 / BPM; // 500ms
|
||||
|
||||
/* ============================================================
|
||||
STARFIELD
|
||||
CÉU REALISTA — Yale BSC (starcatalog.js) + Via Láctea
|
||||
Posições reais para o observador via tempo sideral local.
|
||||
O sol vaporwave é cenográfico — só a camada de estrelas é real.
|
||||
============================================================ */
|
||||
const canvas = document.getElementById('starfield');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const STAR_COUNT = 220;
|
||||
const stars = [];
|
||||
let obsLat = -24.72, obsLon = -53.74; // Toledo-PR; sobrescrito pela config
|
||||
const VIEW_AZ = 90; // centro da tela olha para o LESTE — a tela física do
|
||||
// usuário está voltada p/ leste, então o wallpaper age
|
||||
// como janela: estrelas nascem subindo dentro da cena
|
||||
const FOV_AZ = 140; // largura da tela em graus de azimute
|
||||
const ALT_TOP = 75; // altitude no topo da tela
|
||||
const HORIZON_F = 0.585; // fração da altura da tela até o horizonte
|
||||
const TWINKLE_MAG = 4.0; // até esta magnitude a estrela cintila; acima é estática
|
||||
const DEG = Math.PI / 180;
|
||||
window.__skyTimeOffset = 0; // debug: ms somados ao relógio p/ testar a rotação
|
||||
|
||||
const STAR_COLORS = [[255,221,170],[204,216,255],[238,238,255]];
|
||||
|
||||
function initStars() {
|
||||
stars.length = 0;
|
||||
for (let i = 0; i < STAR_COUNT; i++) {
|
||||
const c = STAR_COLORS[Math.random() < 0.15 ? 0 : Math.random() < 0.5 ? 1 : 2];
|
||||
stars.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height * 0.62,
|
||||
r: Math.random() * 1.6 + 0.2,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
freq: 0.4 + Math.random() * 1.2,
|
||||
cr: c[0], cg: c[1], cb: c[2],
|
||||
});
|
||||
/* Tempo sideral local em graus (GMST + longitude leste) */
|
||||
function localSiderealDeg(ms) {
|
||||
const d = ms / 86400000 - 10957.5; // dias desde J2000.0
|
||||
return ((280.46061837 + 360.98564736629 * d + obsLon) % 360 + 360) % 360;
|
||||
}
|
||||
|
||||
/* RA/Dec (graus) -> altitude/azimute (graus, az a partir do norte p/ leste) */
|
||||
function altAz(raDeg, decDeg, lstDeg) {
|
||||
const H = (lstDeg - raDeg) * DEG;
|
||||
const cd = Math.cos(decDeg * DEG), sd = Math.sin(decDeg * DEG);
|
||||
const cl = Math.cos(obsLat * DEG), sl = Math.sin(obsLat * DEG);
|
||||
const xh = -cd * Math.cos(H) * sl + sd * cl; // norte
|
||||
const yh = -cd * Math.sin(H); // leste
|
||||
const zh = cd * Math.cos(H) * cl + sd * sl; // zênite
|
||||
return { alt: Math.asin(zh) / DEG, az: Math.atan2(yh, xh) / DEG };
|
||||
}
|
||||
|
||||
/* Projeção cilíndrica: azimute -> x, altitude -> y. null = fora da tela */
|
||||
function skyToScreen(alt, az, W, horizonPx) {
|
||||
const daz = ((az - VIEW_AZ) % 360 + 540) % 360 - 180;
|
||||
if (Math.abs(daz) > FOV_AZ / 2 || alt < -3 || alt > ALT_TOP) return null;
|
||||
return { x: (0.5 + daz / FOV_AZ) * W, y: horizonPx * (1 - alt / ALT_TOP) };
|
||||
}
|
||||
|
||||
/* Extinção atmosférica: estrelas somem suavemente perto do horizonte */
|
||||
function extinction(alt) {
|
||||
return Math.max(0, Math.min(1, (alt + 1) / 14));
|
||||
}
|
||||
|
||||
/* Clarão ao redor do sol retrowave (só no modo padrão — nos outros o sol
|
||||
sobe/encolhe e o próprio elemento cobre o que está atrás) */
|
||||
function glareFactor(x, y, W, H, on) {
|
||||
if (!on) return 1;
|
||||
const d = Math.hypot(x - W / 2, y - H * HORIZON_F);
|
||||
return Math.max(0, Math.min(1, (d - 150) / 240));
|
||||
}
|
||||
|
||||
/* ── Via Láctea: partículas geradas em coords galácticas (RNG com semente,
|
||||
estável entre reloads) e convertidas p/ equatorial J2000 uma única vez ── */
|
||||
function mulberry32(seed) {
|
||||
return function () {
|
||||
seed |= 0; seed = seed + 0x6D2B79F5 | 0;
|
||||
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
|
||||
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
||||
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
/* Transposta da matriz equatorial->galáctica (Hipparcos, J2000) */
|
||||
const G2E = [
|
||||
[-0.0548755604, 0.4941094279, -0.8676661490],
|
||||
[-0.8734370902, -0.4448296300, -0.1980763734],
|
||||
[-0.4838350155, 0.7469822445, 0.4559837762],
|
||||
];
|
||||
|
||||
/* Visual de astrofoto de longa exposição: grão estelar fino + brilho difuso
|
||||
com gradiente térmico (núcleo âmbar -> anticentro azul) + faixas de poeira */
|
||||
function galToEq(l, b) {
|
||||
const cb = Math.cos(b * DEG);
|
||||
const gx = cb * Math.cos(l * DEG), gy = cb * Math.sin(l * DEG), gz = Math.sin(b * DEG);
|
||||
return {
|
||||
ra: (Math.atan2(
|
||||
G2E[1][0] * gx + G2E[1][1] * gy + G2E[1][2] * gz,
|
||||
G2E[0][0] * gx + G2E[0][1] * gy + G2E[0][2] * gz) / DEG + 360) % 360,
|
||||
dec: Math.asin(G2E[2][0] * gx + G2E[2][1] * gy + G2E[2][2] * gz) / DEG,
|
||||
};
|
||||
}
|
||||
|
||||
/* Cor por distância angular ao centro galáctico: creme-âmbar -> neutro -> azul */
|
||||
function mwColor(dl) {
|
||||
const t = Math.min(1, dl / 140);
|
||||
const mix = (a, b2, c) => Math.round(t < 0.5 ? a + (b2 - a) * t * 2 : b2 + (c - b2) * (t - 0.5) * 2);
|
||||
return [mix(255, 236, 186), mix(226, 224, 205), mix(188, 214, 242)];
|
||||
}
|
||||
|
||||
/* Faixas de poeira (Grande Fenda + secundária): 1 = livre, ~0.1 = dentro da poeira */
|
||||
function mwDust(l, b, dl) {
|
||||
if (dl > 100) return 1;
|
||||
const fade = dl > 70 ? (100 - dl) / 30 : 1; // fenda some longe do centro
|
||||
const lane1 = Math.abs(b - 1.6 * Math.sin(l * DEG * 2.2)) <
|
||||
(2.2 + 1.2 * Math.sin(l * 0.7)) * fade;
|
||||
const lane2 = Math.abs(b + 2.6 - 1.2 * Math.sin(l * DEG * 1.4 + 1.7)) <
|
||||
(1.3 + 0.8 * Math.sin(l * 1.3 + 0.5)) * fade;
|
||||
return (lane1 || lane2) ? 0.1 : 1;
|
||||
}
|
||||
|
||||
/* Nuvens estelares (Scutum, Sagitário, Carina...) — adensamentos fixos na banda */
|
||||
const MW_CLOUDS = [];
|
||||
const mwGlow = []; // blobs difusos {ra,dec,size,alpha,ci}
|
||||
const mwGrain = []; // grão estelar fino {ra,dec,alpha,rgb}
|
||||
(function buildMilkyWay() {
|
||||
const rand = mulberry32(0x5EEDED);
|
||||
const gauss = () => {
|
||||
const u = Math.max(rand(), 1e-9), v = rand();
|
||||
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
||||
};
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const l = (rand() * 160 - 80 + 360) % 360; // nuvens no lado do centro
|
||||
MW_CLOUDS.push({ l, b: gauss() * 2.5, r: 2 + rand() * 4 });
|
||||
}
|
||||
const cloudBoost = (l, b) => {
|
||||
let boost = 0;
|
||||
for (const c of MW_CLOUDS) {
|
||||
const dlc = Math.min(Math.abs(l - c.l), 360 - Math.abs(l - c.l));
|
||||
const d2 = (dlc / c.r) ** 2 + ((b - c.b) / c.r) ** 2;
|
||||
if (d2 < 1) boost = Math.max(boost, 1 - d2);
|
||||
}
|
||||
return boost;
|
||||
};
|
||||
|
||||
let guard = 0;
|
||||
while (mwGlow.length < 3600 && guard++ < 300000) {
|
||||
const l = rand() * 360;
|
||||
const dl = Math.min(l, 360 - l);
|
||||
const central = Math.exp(-((dl / 75) ** 2));
|
||||
if (rand() > 0.28 + 0.72 * central) continue;
|
||||
/* bojo central: banda mais gorda perto do núcleo */
|
||||
const b = gauss() * (5 + 3 * central + 4 * Math.exp(-((dl / 22) ** 2)));
|
||||
const dust = mwDust(l, b, dl);
|
||||
const boost = cloudBoost(l, b);
|
||||
const alpha = (0.35 + 0.65 * central) * dust * (1 + 1.2 * boost) *
|
||||
(0.028 + rand() * 0.05);
|
||||
if (alpha < 0.005) continue;
|
||||
const eq = galToEq(l, b);
|
||||
mwGlow.push({ ra: eq.ra, dec: eq.dec, size: 14 + rand() * 30,
|
||||
alpha, ci: mwColor(dl) });
|
||||
}
|
||||
|
||||
guard = 0;
|
||||
while (mwGrain.length < 9500 && guard++ < 500000) {
|
||||
const l = rand() * 360;
|
||||
const dl = Math.min(l, 360 - l);
|
||||
const central = Math.exp(-((dl / 80) ** 2));
|
||||
if (rand() > 0.22 + 0.78 * central) continue;
|
||||
const b = gauss() * (4.5 + 2.5 * central + 3.5 * Math.exp(-((dl / 22) ** 2)));
|
||||
const dust = mwDust(l, b, dl);
|
||||
if (rand() > dust + 0.06) continue; // poeira esconde o grão
|
||||
const boost = cloudBoost(l, b);
|
||||
if (boost < 0.3 && rand() < 0.25 * central) continue;
|
||||
const eq = galToEq(l, b);
|
||||
mwGrain.push({ ra: eq.ra, dec: eq.dec,
|
||||
alpha: 0.10 + rand() * 0.30 + 0.2 * boost,
|
||||
ci: mwColor(dl) });
|
||||
}
|
||||
})();
|
||||
|
||||
/* Sprites do brilho difuso: quente / neutro / frio (escolhido por cor) */
|
||||
function makeMwSprite(r, g, b) {
|
||||
const c = document.createElement('canvas');
|
||||
c.width = c.height = 64;
|
||||
const s = c.getContext('2d');
|
||||
const grad = s.createRadialGradient(32, 32, 0, 32, 32, 32);
|
||||
grad.addColorStop(0, `rgba(${r},${g},${b},1)`);
|
||||
grad.addColorStop(0.5, `rgba(${r},${g},${b},0.4)`);
|
||||
grad.addColorStop(1, `rgba(${r},${g},${b},0)`);
|
||||
s.fillStyle = grad;
|
||||
s.fillRect(0, 0, 64, 64);
|
||||
return c;
|
||||
}
|
||||
const mwSprites = [makeMwSprite(255, 226, 188), // quente (núcleo)
|
||||
makeMwSprite(236, 224, 214), // neutro
|
||||
makeMwSprite(188, 214, 242)]; // frio (anticentro)
|
||||
function spriteFor(ci) { return mwSprites[ci[2] > ci[0] ? 2 : (ci[0] > 240 ? 0 : 1)]; }
|
||||
|
||||
/* ── Passo de projeção: recalcula posições de tela (a cada 30s o céu gira
|
||||
~0.125°, imperceptível). Camada estática (Via Láctea + estrelas fracas)
|
||||
é pré-renderizada em baseCanvas; só as brilhantes cintilam por frame ── */
|
||||
const baseCanvas = document.createElement('canvas');
|
||||
let twinkList = [];
|
||||
let lastProjMs = -Infinity;
|
||||
|
||||
function projectSky() {
|
||||
const W = canvas.width, H = canvas.height;
|
||||
if (!W || !H) return;
|
||||
baseCanvas.width = W;
|
||||
baseCanvas.height = H;
|
||||
const bctx = baseCanvas.getContext('2d');
|
||||
const horizonPx = H * HORIZON_F;
|
||||
const lst = localSiderealDeg(Date.now() + window.__skyTimeOffset);
|
||||
const glareOn = !document.body.classList.contains('balada') &&
|
||||
!document.body.classList.contains('chuva');
|
||||
twinkList = [];
|
||||
|
||||
bctx.globalCompositeOperation = 'lighter';
|
||||
for (const p of mwGlow) {
|
||||
const aa = altAz(p.ra, p.dec, lst);
|
||||
const sc = skyToScreen(aa.alt, aa.az, W, horizonPx);
|
||||
if (!sc) continue;
|
||||
const a = p.alpha * extinction(aa.alt) * glareFactor(sc.x, sc.y, W, H, glareOn);
|
||||
if (a < 0.002) continue;
|
||||
bctx.globalAlpha = a;
|
||||
bctx.drawImage(spriteFor(p.ci), sc.x - p.size / 2, sc.y - p.size / 2, p.size, p.size);
|
||||
}
|
||||
bctx.globalAlpha = 1;
|
||||
for (const p of mwGrain) {
|
||||
const aa = altAz(p.ra, p.dec, lst);
|
||||
const sc = skyToScreen(aa.alt, aa.az, W, horizonPx);
|
||||
if (!sc) continue;
|
||||
const a = p.alpha * extinction(aa.alt) * glareFactor(sc.x, sc.y, W, H, glareOn);
|
||||
if (a < 0.01) continue;
|
||||
bctx.fillStyle = `rgba(${p.ci[0]},${p.ci[1]},${p.ci[2]},${a.toFixed(3)})`;
|
||||
bctx.fillRect(sc.x - 0.5, sc.y - 0.5, 1, 1);
|
||||
}
|
||||
bctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
for (let i = 0; i < STAR_DATA.length; i++) {
|
||||
const [ra, dec, mag, ci] = STAR_DATA[i];
|
||||
const aa = altAz(ra, dec, lst);
|
||||
const sc = skyToScreen(aa.alt, aa.az, W, horizonPx);
|
||||
if (!sc) continue;
|
||||
const a = Math.max(0.25, Math.min(1, 1.15 - 0.15 * mag)) *
|
||||
extinction(aa.alt) * glareFactor(sc.x, sc.y, W, H, glareOn);
|
||||
if (a < 0.01) continue;
|
||||
const r = Math.max(0.35, 2.7 - 0.42 * mag);
|
||||
const c = STAR_PALETTE[ci];
|
||||
if (mag <= TWINKLE_MAG) {
|
||||
/* fase/freq determinísticas por índice — cintilação estável */
|
||||
twinkList.push({
|
||||
x: sc.x, y: sc.y, r, a,
|
||||
cr: c[0], cg: c[1], cb: c[2],
|
||||
phase: i * 2.3999,
|
||||
freq: 0.35 + ((i * 0.61803) % 1) * 1.1,
|
||||
amp: mag < 1.5 ? 0.18 : 0.45,
|
||||
});
|
||||
} else if (r > 0.8) {
|
||||
bctx.beginPath();
|
||||
bctx.arc(sc.x, sc.y, r, 0, Math.PI * 2);
|
||||
bctx.fillStyle = `rgba(${c[0]},${c[1]},${c[2]},${a.toFixed(3)})`;
|
||||
bctx.fill();
|
||||
} else {
|
||||
bctx.fillStyle = `rgba(${c[0]},${c[1]},${c[2]},${a.toFixed(3)})`;
|
||||
bctx.fillRect(sc.x - r, sc.y - r, r * 2, r * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setObserverLocation(lat, lon) {
|
||||
lat = parseFloat(lat); lon = parseFloat(lon);
|
||||
if (!isFinite(lat) || !isFinite(lon)) return;
|
||||
if (lat === obsLat && lon === obsLon) return;
|
||||
obsLat = lat;
|
||||
obsLon = lon;
|
||||
projectSky();
|
||||
}
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
initStars();
|
||||
projectSky();
|
||||
}
|
||||
|
||||
function drawStars(ts) {
|
||||
if (ts - lastProjMs > 30000) { lastProjMs = ts; projectSky(); }
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(baseCanvas, 0, 0);
|
||||
const t = ts * 0.001;
|
||||
stars.forEach(s => {
|
||||
const alpha = (0.25 + 0.75 * (0.5 + 0.5 * Math.sin(t * s.freq + s.phase))).toFixed(2);
|
||||
for (const s of twinkList) {
|
||||
const tw = 1 - s.amp * (0.5 + 0.5 * Math.sin(t * s.freq + s.phase));
|
||||
ctx.beginPath();
|
||||
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(${s.cr},${s.cg},${s.cb},${alpha})`;
|
||||
ctx.fillStyle = `rgba(${s.cr},${s.cg},${s.cb},${(s.a * tw).toFixed(3)})`;
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
requestAnimationFrame(drawStars);
|
||||
}
|
||||
|
||||
|
|
@ -1984,6 +2226,7 @@ function setBaladaMode(on) {
|
|||
if (on) { setChuvaMode(false); startGlobeLoop(); }
|
||||
document.body.classList.toggle('balada', balada);
|
||||
/* opacidade do globo é controlada por drawGlobe via baladaAmt */
|
||||
projectSky(); // rebake do clarão solar no céu
|
||||
}
|
||||
function toggleBalada() { setBaladaMode(!balada); }
|
||||
|
||||
|
|
@ -2137,6 +2380,7 @@ function setChuvaMode(on) {
|
|||
} else if (lastThemeRgb) {
|
||||
applyTheme(lastThemeRgb); // devolve a cor do álbum ao fog/horizonte
|
||||
}
|
||||
projectSky(); // rebake do clarão solar no céu
|
||||
}
|
||||
function toggleChuva() { setChuvaMode(!chuva); }
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ WallpaperItem {
|
|||
webView.runJavaScript("setShowSkeleton(" + Plasmoid.configuration.ShowSkeleton + ")")
|
||||
webView.runJavaScript("setBaladaMode(" + Plasmoid.configuration.BaladaMode + ")")
|
||||
webView.runJavaScript("setChuvaMode(" + Plasmoid.configuration.ChuvaMode + ")")
|
||||
webView.runJavaScript("setObserverLocation(" + Plasmoid.configuration.Latitude + ","
|
||||
+ Plasmoid.configuration.Longitude + ")")
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
@ -18,6 +20,8 @@ WallpaperItem {
|
|||
function onShowSkeletonChanged() { applyConfig() }
|
||||
function onBaladaModeChanged() { applyConfig() }
|
||||
function onChuvaModeChanged() { applyConfig() }
|
||||
function onLatitudeChanged() { applyConfig() }
|
||||
function onLongitudeChanged() { applyConfig() }
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
|
|
|
|||
2893
plugin/contents/ui/starcatalog.js
Normal file
2893
plugin/contents/ui/starcatalog.js
Normal file
File diff suppressed because it is too large
Load diff
117
tools/build_catalog.py
Normal file
117
tools/build_catalog.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Gera plugin/contents/ui/starcatalog.js a partir do Yale Bright Star Catalog.
|
||||
|
||||
Baixa bsc5-short.json (RA/Dec/V/K), filtra V <= MAG_LIMIT, converte a
|
||||
temperatura de cor em RGB (aprox. corpo negro, quantizada numa paleta pequena)
|
||||
e emite um JS compacto:
|
||||
|
||||
const STAR_PALETTE = [[r,g,b], ...];
|
||||
const STAR_DATA = [[raDeg, decDeg, vmag, colorIdx], ...]; // ordenado por mag
|
||||
|
||||
Uso: python3 tools/build_catalog.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
URL = "https://raw.githubusercontent.com/brettonw/YaleBrightStarCatalog/master/bsc5-short.json"
|
||||
OUT = Path(__file__).resolve().parent.parent / "plugin/contents/ui/starcatalog.js"
|
||||
MAG_LIMIT = 5.5
|
||||
|
||||
# Limites (K) e centro representativo de cada faixa de temperatura
|
||||
TEMP_BINS = [
|
||||
(0, 3500, 3000), # M — alaranjado-avermelhado
|
||||
(3500, 4500, 4000), # K — laranja
|
||||
(4500, 5500, 5000), # G — amarelo-branco
|
||||
(5500, 6500, 6000), # F — branco-amarelado
|
||||
(6500, 8000, 7200), # A — branco
|
||||
(8000, 11000, 9500), # B tardio — branco-azulado
|
||||
(11000, 16000, 13000), # B — azul-branco
|
||||
(16000, 10**9, 20000), # O/B quente — azul
|
||||
]
|
||||
SATURATION = 0.6 # estrelas reais parecem quase brancas; 1.0 = cor plena
|
||||
|
||||
|
||||
def blackbody_rgb(kelvin: float) -> tuple[int, int, int]:
|
||||
"""Aproximação de Tanner Helland (válida ~1000K-40000K)."""
|
||||
t = kelvin / 100.0
|
||||
if t <= 66:
|
||||
r = 255.0
|
||||
g = 99.4708025861 * math.log(t) - 161.1195681661
|
||||
b = 0.0 if t <= 19 else 138.5177312231 * math.log(t - 10) - 305.0447927307
|
||||
else:
|
||||
r = 329.698727446 * (t - 60) ** -0.1332047592
|
||||
g = 288.1221695283 * (t - 60) ** -0.0755148492
|
||||
b = 255.0
|
||||
def clamp(x):
|
||||
return max(0, min(255, round(x)))
|
||||
return clamp(r), clamp(g), clamp(b)
|
||||
|
||||
|
||||
def desaturate(rgb, amount):
|
||||
r, g, b = rgb
|
||||
lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||
mix = lambda c: round(lum + (c - lum) * amount)
|
||||
return mix(r), mix(g), mix(b)
|
||||
|
||||
|
||||
def parse_ra(s: str) -> float:
|
||||
m = re.match(r"(\d+)h\s*(\d+)m\s*([\d.]+)s", s)
|
||||
h, mi, se = float(m.group(1)), float(m.group(2)), float(m.group(3))
|
||||
return (h + mi / 60 + se / 3600) * 15.0
|
||||
|
||||
|
||||
def parse_dec(s: str) -> float:
|
||||
m = re.match(r"([+-])(\d+)°\s*(\d+)′\s*([\d.]+)″", s)
|
||||
sign = -1.0 if m.group(1) == "-" else 1.0
|
||||
d, mi, se = float(m.group(2)), float(m.group(3)), float(m.group(4))
|
||||
return sign * (d + mi / 60 + se / 3600)
|
||||
|
||||
|
||||
def temp_index(kelvin: float) -> int:
|
||||
for i, (lo, hi, _) in enumerate(TEMP_BINS):
|
||||
if lo <= kelvin < hi:
|
||||
return i
|
||||
return len(TEMP_BINS) - 1
|
||||
|
||||
|
||||
def main():
|
||||
raw = json.loads(urllib.request.urlopen(URL, timeout=30).read())
|
||||
stars = []
|
||||
for entry in raw:
|
||||
if "V" not in entry or "RA" not in entry or "Dec" not in entry:
|
||||
continue
|
||||
vmag = float(entry["V"])
|
||||
if vmag > MAG_LIMIT:
|
||||
continue
|
||||
kelvin = float(entry.get("K", 6000))
|
||||
stars.append((
|
||||
round(parse_ra(entry["RA"]), 2),
|
||||
round(parse_dec(entry["Dec"]), 2),
|
||||
round(vmag, 2),
|
||||
temp_index(kelvin),
|
||||
))
|
||||
|
||||
stars.sort(key=lambda s: s[2]) # mais brilhantes primeiro
|
||||
|
||||
palette = [desaturate(blackbody_rgb(center), SATURATION)
|
||||
for _, _, center in TEMP_BINS]
|
||||
|
||||
rows = ",\n".join(
|
||||
f"[{ra},{dec},{mag},{ci}]" for ra, dec, mag, ci in stars
|
||||
)
|
||||
OUT.write_text(
|
||||
"/* Gerado por tools/build_catalog.py — NÃO editar à mão.\n"
|
||||
f" Yale Bright Star Catalog, V <= {MAG_LIMIT}, {len(stars)} estrelas.\n"
|
||||
" Formato: [raGraus, decGraus, magV, idxPaleta] ordenado por magnitude. */\n"
|
||||
f"const STAR_PALETTE = {json.dumps([list(p) for p in palette])};\n"
|
||||
f"const STAR_DATA = [\n{rows}\n];\n"
|
||||
)
|
||||
print(f"{len(stars)} estrelas -> {OUT} ({OUT.stat().st_size / 1024:.0f} KB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue