|
@@ -1,206 +1,151 @@
|
|
|
---
|
|
---
|
|
|
|
|
+import Win98Window from "./win98window.astro";
|
|
|
import duolingoImg from "../assets/dualingo.png";
|
|
import duolingoImg from "../assets/dualingo.png";
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-<div id="duolingo" class="window duolingo-popup" style="display: none; position: fixed; top: 20px; right: 20px; width: 320px; z-index: 1000;">
|
|
|
|
|
- <div class="title-bar" id="duolingoheader">
|
|
|
|
|
- <div class="title-bar-text">Duolingo Stats</div>
|
|
|
|
|
- <div class="title-bar-controls">
|
|
|
|
|
- <button class="close" aria-label="Minimize"></button>
|
|
|
|
|
- <button class="close" aria-label="Close"></button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="window-body">
|
|
|
|
|
- <div id="loading-message" style="text-align: center; padding: 20px;">
|
|
|
|
|
- <p>⏳ Loading stats...</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div id="duolingo-content" style="display: none;">
|
|
|
|
|
- <div class="card">
|
|
|
|
|
- <div class="card-body">
|
|
|
|
|
- <div class="streak-display">
|
|
|
|
|
- <div class="streak-info">
|
|
|
|
|
- <div class="streak-number" id="streak-number">0</div>
|
|
|
|
|
- <div class="streak-label">Day Streak</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="mascot">
|
|
|
|
|
- <img src={duolingoImg.src} alt="Duo" />
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div id="error-message" style="display: none; text-align: center; padding: 20px; color: #800000;">
|
|
|
|
|
- <p>⚠ Failed to load stats</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-</div>
|
|
|
|
|
|
|
+<Win98Window
|
|
|
|
|
+ title="Duolingo Stats"
|
|
|
|
|
+ layout={{
|
|
|
|
|
+ mobile: { left: '2%', top: '5%', width: '96%', height: 'auto' },
|
|
|
|
|
+ desktop: { left: 'auto', top: 'auto', width: '320px', height: 'auto' }
|
|
|
|
|
+ }}
|
|
|
|
|
+>
|
|
|
|
|
+ <div id="loading-message" style="text-align: center; padding: 20px;">
|
|
|
|
|
+ <p>⏳ Loading stats...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="duolingo-content" style="display: none;">
|
|
|
|
|
+ <div class="card">
|
|
|
|
|
+ <div class="card-body">
|
|
|
|
|
+ <div class="streak-display">
|
|
|
|
|
+ <div class="streak-info">
|
|
|
|
|
+ <div class="streak-number" id="streak-number">0</div>
|
|
|
|
|
+ <div class="streak-label">Day Streak</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="mascot">
|
|
|
|
|
+ <img src={duolingoImg.src} alt="Duo" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="error-message" style="display: none; text-align: center; padding: 20px; color: #800000;">
|
|
|
|
|
+ <p>⚠ Failed to load stats</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</Win98Window>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
- const styleElement = document.createElement('style');
|
|
|
|
|
- styleElement.textContent = `
|
|
|
|
|
- #duolingo.window {
|
|
|
|
|
- font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
|
|
|
|
|
- background-color: #c0c0c0;
|
|
|
|
|
- border: 2px solid;
|
|
|
|
|
- border-color: #ffffff #808080 #808080 #ffffff;
|
|
|
|
|
- box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .title-bar {
|
|
|
|
|
- background: linear-gradient(90deg, #000080, #1084d0);
|
|
|
|
|
- padding: 3px 5px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- cursor: move;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .title-bar-text {
|
|
|
|
|
- color: white;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .close {
|
|
|
|
|
- width: 16px;
|
|
|
|
|
- height: 14px;
|
|
|
|
|
- background-color: #c0c0c0;
|
|
|
|
|
- border: 1px solid;
|
|
|
|
|
- border-color: #ffffff #000000 #000000 #ffffff;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- position: relative;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .close::before {
|
|
|
|
|
- content: "×";
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: -3px;
|
|
|
|
|
- left: 3px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #000000;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .close:active {
|
|
|
|
|
- border-color: #000000 #ffffff #ffffff #000000;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .window-body {
|
|
|
|
|
- background-color: #c0c0c0;
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo #loading-message p,
|
|
|
|
|
- #duolingo #error-message p {
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .card {
|
|
|
|
|
- background: linear-gradient(135deg, #2d2d2d 0%, #1a1a1a 100%);
|
|
|
|
|
- border: 2px solid;
|
|
|
|
|
- border-color: #808080 #ffffff #ffffff #808080;
|
|
|
|
|
- border-radius: 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .card-body {
|
|
|
|
|
- padding: 15px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .streak-display {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: space-around;
|
|
|
|
|
- gap: 20px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .streak-info {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 4px;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .streak-number {
|
|
|
|
|
- font-size: 36px;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- color: #ff69b4;
|
|
|
|
|
- line-height: 1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .streak-label {
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
- color: #ffffff;
|
|
|
|
|
- opacity: 0.9;
|
|
|
|
|
- text-transform: uppercase;
|
|
|
|
|
- letter-spacing: 1px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .mascot {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- width: 70px;
|
|
|
|
|
- height: 70px;
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
- transform: scale(1.3);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #duolingo .mascot img {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- object-fit: contain;
|
|
|
|
|
- image-rendering: pixelated;
|
|
|
|
|
- image-rendering: -moz-crisp-edges;
|
|
|
|
|
- image-rendering: crisp-edges;
|
|
|
|
|
- }
|
|
|
|
|
- `;
|
|
|
|
|
- document.head.appendChild(styleElement);
|
|
|
|
|
-
|
|
|
|
|
- async function loadDuolingoStats() {
|
|
|
|
|
- const username = 's1monlol';
|
|
|
|
|
- const content = document.getElementById('duolingo-content');
|
|
|
|
|
- const loadingMsg = document.getElementById('loading-message');
|
|
|
|
|
- const errorMsg = document.getElementById('error-message');
|
|
|
|
|
-
|
|
|
|
|
- if (loadingMsg) loadingMsg.style.display = 'block';
|
|
|
|
|
- if (content) content.style.display = 'none';
|
|
|
|
|
- if (errorMsg) errorMsg.style.display = 'none';
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const response = await fetch(`https://www.duolingo.com/2017-06-30/users?username=${username}`);
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
- const user = data.users[0];
|
|
|
|
|
-
|
|
|
|
|
- if (!user) throw new Error('User not found');
|
|
|
|
|
-
|
|
|
|
|
- // Update streak
|
|
|
|
|
- const streakEl = document.getElementById('streak-number');
|
|
|
|
|
- if (streakEl) streakEl.textContent = user.streak.toLocaleString();
|
|
|
|
|
-
|
|
|
|
|
- if (content) content.style.display = 'block';
|
|
|
|
|
- if (loadingMsg) loadingMsg.style.display = 'none';
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Error loading Duolingo stats:', error);
|
|
|
|
|
- if (errorMsg) errorMsg.style.display = 'block';
|
|
|
|
|
- if (loadingMsg) loadingMsg.style.display = 'none';
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const observer = new MutationObserver((mutations) => {
|
|
|
|
|
- mutations.forEach((mutation) => {
|
|
|
|
|
- // @ts-ignore
|
|
|
|
|
- if (mutation.target.style.display === 'block') {
|
|
|
|
|
- loadDuolingoStats();
|
|
|
|
|
- observer.disconnect();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const duolingoWindow = document.getElementById('duolingo');
|
|
|
|
|
- if (duolingoWindow) {
|
|
|
|
|
- observer.observe(duolingoWindow, { attributes: true, attributeFilter: ['style'] });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const styleElement = document.createElement('style');
|
|
|
|
|
+ styleElement.textContent = `
|
|
|
|
|
+ #Duolingo\ Stats .card {
|
|
|
|
|
+ background: linear-gradient(135deg, #2d2d2d 0%, #1a1a1a 100%);
|
|
|
|
|
+ border: 2px solid;
|
|
|
|
|
+ border-color: #808080 #ffffff #ffffff #808080;
|
|
|
|
|
+ border-radius: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .card-body {
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .streak-display {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-around;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .streak-info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .streak-number {
|
|
|
|
|
+ font-size: 36px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #ff69b4;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .streak-label {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ letter-spacing: 1px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .mascot {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 70px;
|
|
|
|
|
+ height: 70px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ transform: scale(1.3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats .mascot img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: contain;
|
|
|
|
|
+ image-rendering: pixelated;
|
|
|
|
|
+ image-rendering: -moz-crisp-edges;
|
|
|
|
|
+ image-rendering: crisp-edges;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #Duolingo\ Stats #loading-message p,
|
|
|
|
|
+ #Duolingo\ Stats #error-message p {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ `;
|
|
|
|
|
+ document.head.appendChild(styleElement);
|
|
|
|
|
+
|
|
|
|
|
+ async function loadDuolingoStats() {
|
|
|
|
|
+ const username = 's1monlol';
|
|
|
|
|
+ const content = document.getElementById('duolingo-content');
|
|
|
|
|
+ const loadingMsg = document.getElementById('loading-message');
|
|
|
|
|
+ const errorMsg = document.getElementById('error-message');
|
|
|
|
|
+
|
|
|
|
|
+ if (loadingMsg) loadingMsg.style.display = 'block';
|
|
|
|
|
+ if (content) content.style.display = 'none';
|
|
|
|
|
+ if (errorMsg) errorMsg.style.display = 'none';
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`https://www.duolingo.com/2017-06-30/users?username=${username}`);
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ const user = data.users[0];
|
|
|
|
|
+
|
|
|
|
|
+ if (!user) throw new Error('User not found');
|
|
|
|
|
+
|
|
|
|
|
+ // Update streak
|
|
|
|
|
+ const streakEl = document.getElementById('streak-number');
|
|
|
|
|
+ if (streakEl) streakEl.textContent = user.streak.toLocaleString();
|
|
|
|
|
+
|
|
|
|
|
+ if (content) content.style.display = 'block';
|
|
|
|
|
+ if (loadingMsg) loadingMsg.style.display = 'none';
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error loading Duolingo stats:', error);
|
|
|
|
|
+ if (errorMsg) errorMsg.style.display = 'block';
|
|
|
|
|
+ if (loadingMsg) loadingMsg.style.display = 'none';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const observer = new MutationObserver((mutations) => {
|
|
|
|
|
+ mutations.forEach((mutation) => {
|
|
|
|
|
+ // @ts-ignore
|
|
|
|
|
+ if (mutation.target.style.display === 'block') {
|
|
|
|
|
+ loadDuolingoStats();
|
|
|
|
|
+ observer.disconnect();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const duolingoWindow = document.getElementById('Duolingo Stats');
|
|
|
|
|
+ if (duolingoWindow) {
|
|
|
|
|
+ observer.observe(duolingoWindow, { attributes: true, attributeFilter: ['style'] });
|
|
|
|
|
+ }
|
|
|
</script>
|
|
</script>
|