sanjang 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/app.js +625 -120
- package/dashboard/index.html +7 -31
- package/dashboard/style.css +43 -290
- package/dist/bin/sanjang.js +43 -1
- package/package.json +1 -1
package/dashboard/app.js
CHANGED
|
@@ -1241,9 +1241,22 @@ function renderWorkspace(data) {
|
|
|
1241
1241
|
previewEl.querySelector('.ws-preview-fallback').style.display = 'flex';
|
|
1242
1242
|
});
|
|
1243
1243
|
} else {
|
|
1244
|
-
previewEl.innerHTML =
|
|
1245
|
-
|
|
1246
|
-
|
|
1244
|
+
previewEl.innerHTML = `
|
|
1245
|
+
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:16px;user-select:none;">
|
|
1246
|
+
<div style="width:4px;height:4px;image-rendering:pixelated;color:transparent;box-shadow:
|
|
1247
|
+
/* tent peak */
|
|
1248
|
+
12px 0 0 #6b7394,
|
|
1249
|
+
8px 4px 0 #6b7394, 12px 4px 0 #6b7394, 16px 4px 0 #6b7394,
|
|
1250
|
+
4px 8px 0 #6b7394, 8px 8px 0 #6b7394, 12px 8px 0 #6b7394, 16px 8px 0 #6b7394, 20px 8px 0 #6b7394,
|
|
1251
|
+
0px 12px 0 #4a5170, 4px 12px 0 #4a5170, 8px 12px 0 #4a5170, 12px 12px 0 #4a5170, 16px 12px 0 #4a5170, 20px 12px 0 #4a5170, 24px 12px 0 #4a5170,
|
|
1252
|
+
/* zzz */
|
|
1253
|
+
36px 0 0 #4a5170, 40px 4px 0 #4a5170, 36px 8px 0 #4a5170;
|
|
1254
|
+
transform:scale(2);margin-bottom:8px;
|
|
1255
|
+
"></div>
|
|
1256
|
+
<div style="color:var(--text-muted);font-size:14px;text-align:center;margin-top:24px;">
|
|
1257
|
+
캠프가 자고 있어유... zzZ
|
|
1258
|
+
</div>
|
|
1259
|
+
</div>`;
|
|
1247
1260
|
}
|
|
1248
1261
|
|
|
1249
1262
|
// Terminal button label
|
|
@@ -1737,150 +1750,643 @@ window.autoFix = async function autoFix(name) {
|
|
|
1737
1750
|
};
|
|
1738
1751
|
|
|
1739
1752
|
// ---------------------------------------------------------------------------
|
|
1740
|
-
//
|
|
1753
|
+
// Sherpa Guide Mode (replaces overlay onboarding)
|
|
1741
1754
|
// ---------------------------------------------------------------------------
|
|
1742
1755
|
|
|
1743
1756
|
const ONBOARDING_KEY = 'sanjang-onboarded';
|
|
1744
1757
|
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
},
|
|
1752
|
-
{
|
|
1753
|
-
target: '#ws-preview',
|
|
1754
|
-
title: '프리뷰 확인',
|
|
1755
|
-
text: '캠프에 들어가면 전체화면으로 프리뷰를 볼 수 있어요.',
|
|
1756
|
-
position: 'center',
|
|
1757
|
-
waitForWorkspace: true,
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
target: '#ws-save-btn',
|
|
1761
|
-
title: '세이브하기',
|
|
1762
|
-
text: '변경사항이 있으면 세이브 버튼으로 저장해요. 게임 세이브처럼요!',
|
|
1763
|
-
position: 'left',
|
|
1764
|
-
waitForWorkspace: true,
|
|
1765
|
-
},
|
|
1758
|
+
const SHERPA_GUIDE = [
|
|
1759
|
+
"여기에 하고 싶은 거 적으면 되유. AI가 캠프 만들어줄겨.",
|
|
1760
|
+
"캠프 들어가면 프리뷰 전체화면으로 보여유. 편하쥬?",
|
|
1761
|
+
"세이브는 게임 세이브처럼 저장이여유. 💾 버튼 누르면 되유.",
|
|
1762
|
+
"팀에 보내기 누르면 PR 만들어주유. 셰르파가 다 해줄겨.",
|
|
1763
|
+
"그럼 이제 시작해봐유. 화이팅이여유~ 🏔️",
|
|
1766
1764
|
];
|
|
1767
1765
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1766
|
+
// ---------------------------------------------------------------------------
|
|
1767
|
+
// Sherpa Mode System (guide ↔ grumpy toggle)
|
|
1768
|
+
// ---------------------------------------------------------------------------
|
|
1771
1769
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1770
|
+
let sherpaInterval = null;
|
|
1771
|
+
let sherpaMode = 'grumpy'; // 'guide' or 'grumpy'
|
|
1772
|
+
let sherpaQueue = [];
|
|
1773
|
+
let sherpaIdx = 0;
|
|
1775
1774
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1775
|
+
function shuffleArray(arr) {
|
|
1776
|
+
const a = [...arr];
|
|
1777
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
1778
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1779
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
1780
|
+
}
|
|
1781
|
+
return a;
|
|
1782
|
+
}
|
|
1780
1783
|
|
|
1781
|
-
|
|
1784
|
+
function setSherpaMode(mode) {
|
|
1785
|
+
sherpaMode = mode;
|
|
1786
|
+
sherpaIdx = 0;
|
|
1787
|
+
sherpaQueue = mode === 'guide' ? [...SHERPA_GUIDE] : shuffleArray(SHERPA_QUOTES);
|
|
1782
1788
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
show();
|
|
1787
|
-
return;
|
|
1788
|
-
}
|
|
1789
|
+
const el = document.getElementById('sherpa-quote');
|
|
1790
|
+
const speech = document.getElementById('sherpa-speech');
|
|
1791
|
+
if (!el || !speech) return;
|
|
1789
1792
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const overlay = document.createElement('div');
|
|
1794
|
-
overlay.className = 'onboarding-overlay';
|
|
1795
|
-
|
|
1796
|
-
const rect = el.getBoundingClientRect();
|
|
1797
|
-
const highlight = document.createElement('div');
|
|
1798
|
-
highlight.className = 'onboarding-highlight';
|
|
1799
|
-
highlight.style.top = `${rect.top - 4}px`;
|
|
1800
|
-
highlight.style.left = `${rect.left - 4}px`;
|
|
1801
|
-
highlight.style.width = `${rect.width + 8}px`;
|
|
1802
|
-
highlight.style.height = `${rect.height + 8}px`;
|
|
1803
|
-
overlay.appendChild(highlight);
|
|
1804
|
-
|
|
1805
|
-
const tooltip = document.createElement('div');
|
|
1806
|
-
tooltip.className = 'onboarding-tooltip';
|
|
1807
|
-
tooltip.innerHTML = `
|
|
1808
|
-
<div class="onboarding-title">${s.title}</div>
|
|
1809
|
-
<div class="onboarding-text">${s.text}</div>
|
|
1810
|
-
<div class="onboarding-actions">
|
|
1811
|
-
<span class="onboarding-step">${step + 1}/${onboardingSteps.length}</span>
|
|
1812
|
-
<button class="btn btn-ghost btn-sm" onclick="skipOnboarding()">건너뛰기</button>
|
|
1813
|
-
<button class="btn btn-primary btn-sm" onclick="nextOnboardingStep()">${step === onboardingSteps.length - 1 ? '완료' : '다음'}</button>
|
|
1814
|
-
</div>`;
|
|
1793
|
+
// Visual mode indicator
|
|
1794
|
+
speech.classList.toggle('guide-mode', mode === 'guide');
|
|
1815
1795
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1796
|
+
// Fade transition to first message
|
|
1797
|
+
el.style.opacity = '0';
|
|
1798
|
+
setTimeout(() => {
|
|
1799
|
+
el.textContent = sherpaQueue[0];
|
|
1800
|
+
el.style.opacity = '1';
|
|
1801
|
+
}, 300);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
function advanceSherpa() {
|
|
1805
|
+
const el = document.getElementById('sherpa-quote');
|
|
1806
|
+
if (!el) return;
|
|
1807
|
+
|
|
1808
|
+
el.style.opacity = '0';
|
|
1809
|
+
setTimeout(() => {
|
|
1810
|
+
sherpaIdx++;
|
|
1811
|
+
if (sherpaIdx >= sherpaQueue.length) {
|
|
1812
|
+
if (sherpaMode === 'guide') {
|
|
1813
|
+
// Guide done → switch to grumpy
|
|
1814
|
+
localStorage.setItem(ONBOARDING_KEY, '1');
|
|
1815
|
+
setSherpaMode('grumpy');
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
// Reshuffle grumpy quotes
|
|
1819
|
+
sherpaQueue = shuffleArray(SHERPA_QUOTES);
|
|
1820
|
+
sherpaIdx = 0;
|
|
1827
1821
|
}
|
|
1822
|
+
el.textContent = sherpaQueue[sherpaIdx];
|
|
1823
|
+
el.style.opacity = '1';
|
|
1824
|
+
}, 500);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// ---------------------------------------------------------------------------
|
|
1828
|
+
// Basecamp Scene — Time-based Himalaya SVG
|
|
1829
|
+
// ---------------------------------------------------------------------------
|
|
1830
|
+
|
|
1831
|
+
const SCENE_THEMES = {
|
|
1832
|
+
dawn: {
|
|
1833
|
+
skyGradient: [['0%','#050810'],['60%','#0a1028'],['100%','#141830']],
|
|
1834
|
+
farRange: '#1a2040',
|
|
1835
|
+
midRange: '#141a30',
|
|
1836
|
+
ground: '#12151e',
|
|
1837
|
+
snowColor: 'rgba(200,215,240,0.35)',
|
|
1838
|
+
snowHighlight: 'rgba(220,230,250,0.4)',
|
|
1839
|
+
},
|
|
1840
|
+
morning: {
|
|
1841
|
+
skyGradient: [['0%','#1a2540'],['40%','#2d3a5c'],['70%','#5c4a6e'],['100%','#c4785a']],
|
|
1842
|
+
farRange: '#2a3058',
|
|
1843
|
+
midRange: '#1e2444',
|
|
1844
|
+
ground: '#12151e',
|
|
1845
|
+
snowColor: 'rgba(255,220,180,0.45)',
|
|
1846
|
+
snowHighlight: 'rgba(255,200,150,0.55)',
|
|
1847
|
+
},
|
|
1848
|
+
day: {
|
|
1849
|
+
skyGradient: [['0%','#1a3050'],['50%','#2a4a6a'],['100%','#3a5a7a']],
|
|
1850
|
+
farRange: '#253a58',
|
|
1851
|
+
midRange: '#1a2e48',
|
|
1852
|
+
ground: '#12151e',
|
|
1853
|
+
snowColor: 'rgba(255,255,255,0.5)',
|
|
1854
|
+
snowHighlight: 'rgba(255,255,255,0.6)',
|
|
1855
|
+
},
|
|
1856
|
+
evening: {
|
|
1857
|
+
skyGradient: [['0%','#060810'],['40%','#0e1530'],['75%','#1a1535'],['100%','#12151e']],
|
|
1858
|
+
farRange: '#182040',
|
|
1859
|
+
midRange: '#121830',
|
|
1860
|
+
ground: '#12151e',
|
|
1861
|
+
snowColor: 'rgba(200,180,230,0.35)',
|
|
1862
|
+
snowHighlight: 'rgba(220,200,240,0.4)',
|
|
1863
|
+
},
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
function renderBasecampScene() {
|
|
1867
|
+
const container = document.getElementById('bc-scene-container');
|
|
1868
|
+
if (!container) return;
|
|
1828
1869
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1870
|
+
const P = 4; // pixel size (4px grid)
|
|
1871
|
+
const hour = new Date().getHours();
|
|
1872
|
+
let period;
|
|
1873
|
+
if (hour >= 0 && hour < 6) period = 'dawn';
|
|
1874
|
+
else if (hour >= 6 && hour < 12) period = 'morning';
|
|
1875
|
+
else if (hour >= 12 && hour < 18) period = 'day';
|
|
1876
|
+
else period = 'evening';
|
|
1877
|
+
|
|
1878
|
+
const theme = SCENE_THEMES[period];
|
|
1879
|
+
|
|
1880
|
+
// Build gradient stops
|
|
1881
|
+
const stops = theme.skyGradient.map(([offset, color]) =>
|
|
1882
|
+
`<stop offset="${offset}" stop-color="${color}"/>`
|
|
1883
|
+
).join('');
|
|
1884
|
+
|
|
1885
|
+
// --- Stars ---
|
|
1886
|
+
let starsHtml = '';
|
|
1887
|
+
if (period === 'dawn') {
|
|
1888
|
+
const starPositions = [
|
|
1889
|
+
[45,18],[120,30],[200,12],[280,25],[360,8],[440,22],[520,15],[590,28],[150,40],[400,35],[60,42],[500,38],[330,10]
|
|
1890
|
+
];
|
|
1891
|
+
starsHtml = starPositions.map(([x,y]) =>
|
|
1892
|
+
`<rect x="${x}" y="${y}" width="2" height="2" fill="#fff" opacity="${0.4 + Math.random()*0.4}"><animate attributeName="opacity" values="${0.3};${0.8};${0.3}" dur="${1.5 + Math.random()*2}s" repeatCount="indefinite"/></rect>`
|
|
1893
|
+
).join('');
|
|
1894
|
+
// Crescent moon (pixel)
|
|
1895
|
+
starsHtml += `
|
|
1896
|
+
<rect x="576" y="20" width="${P}" height="${P}" fill="#c8cee6" opacity="0.3"/>
|
|
1897
|
+
<rect x="580" y="16" width="${P}" height="${P}" fill="#c8cee6" opacity="0.3"/>
|
|
1898
|
+
<rect x="580" y="20" width="${P}" height="${P}" fill="#c8cee6" opacity="0.25"/>
|
|
1899
|
+
<rect x="584" y="16" width="${P}" height="${P}" fill="#c8cee6" opacity="0.3"/>
|
|
1900
|
+
<rect x="584" y="20" width="${P}" height="${P}" fill="#c8cee6" opacity="0.15"/>
|
|
1901
|
+
<rect x="588" y="20" width="${P}" height="${P}" fill="#c8cee6" opacity="0.3"/>
|
|
1902
|
+
<rect x="580" y="24" width="${P}" height="${P}" fill="#c8cee6" opacity="0.25"/>
|
|
1903
|
+
<rect x="584" y="24" width="${P}" height="${P}" fill="#c8cee6" opacity="0.3"/>
|
|
1904
|
+
`;
|
|
1905
|
+
} else if (period === 'morning') {
|
|
1906
|
+
const starPositions = [[120,20],[400,15],[550,30]];
|
|
1907
|
+
starsHtml = starPositions.map(([x,y]) =>
|
|
1908
|
+
`<rect x="${x}" y="${y}" width="2" height="2" fill="#fff" opacity="0.2"/>`
|
|
1909
|
+
).join('');
|
|
1910
|
+
} else if (period === 'day') {
|
|
1911
|
+
// Pixel clouds (4px grid)
|
|
1912
|
+
starsHtml = `
|
|
1913
|
+
<g opacity="0.12">
|
|
1914
|
+
<rect x="104" y="28" width="8" height="4" fill="#fff"/>
|
|
1915
|
+
<rect x="100" y="32" width="16" height="4" fill="#fff"/>
|
|
1916
|
+
<rect x="108" y="36" width="4" height="4" fill="#fff"/>
|
|
1917
|
+
<rect x="436" y="24" width="12" height="4" fill="#fff"/>
|
|
1918
|
+
<rect x="432" y="28" width="20" height="4" fill="#fff"/>
|
|
1919
|
+
<rect x="440" y="32" width="8" height="4" fill="#fff"/>
|
|
1920
|
+
</g>
|
|
1921
|
+
`;
|
|
1922
|
+
} else {
|
|
1923
|
+
const starPositions = [
|
|
1924
|
+
[80,15],[160,35],[250,10],[340,28],[430,18],[510,32],[590,12],[140,45],[380,40],[620,25]
|
|
1925
|
+
];
|
|
1926
|
+
starsHtml = starPositions.map(([x,y]) =>
|
|
1927
|
+
`<rect x="${x}" y="${y}" width="2" height="2" fill="#fff" opacity="${0.3 + Math.random()*0.5}"><animate attributeName="opacity" values="${0.2};${0.7};${0.2}" dur="${2 + Math.random()*2}s" repeatCount="indefinite"/></rect>`
|
|
1928
|
+
).join('');
|
|
1831
1929
|
}
|
|
1832
1930
|
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1931
|
+
// --- Mountains ---
|
|
1932
|
+
const farRangePoly = `0,220 0,150 20,150 20,140 40,140 40,125 55,125 55,115 70,115 70,105 85,105 85,95
|
|
1933
|
+
95,95 95,85 105,85 105,80 115,80 115,85 125,85 125,95 135,95 135,105
|
|
1934
|
+
150,105 150,115 165,115 165,125 180,125 180,135 200,135 200,145 220,145
|
|
1935
|
+
220,135 235,135 235,120 250,120 250,105 260,105 260,90 270,90 270,78 280,78
|
|
1936
|
+
280,70 288,70 288,62 295,62 295,56 302,56 302,50 308,50 308,45 314,45
|
|
1937
|
+
314,50 320,50 320,56 326,56 326,65 335,65 335,78 345,78 345,90
|
|
1938
|
+
355,90 355,105 370,105 370,120 385,120 385,135 400,135
|
|
1939
|
+
400,125 415,125 415,110 425,110 425,98 435,98 435,88 445,88 445,78
|
|
1940
|
+
450,78 450,70 456,70 456,64 462,64 462,58 466,58 466,54 470,54
|
|
1941
|
+
470,50 474,50 474,46 478,46 478,50 482,50 482,56 486,56
|
|
1942
|
+
486,64 492,64 492,72 498,72 498,82 508,82 508,95 518,95
|
|
1943
|
+
518,108 530,108 530,120 545,120 545,135 560,135
|
|
1944
|
+
560,125 570,125 570,112 580,112 580,100 590,100 590,88 598,88 598,78
|
|
1945
|
+
605,78 605,70 612,70 612,76 618,76 618,85 625,85 625,95
|
|
1946
|
+
635,95 635,108 645,108 645,120 658,120 658,135 680,135 680,220`;
|
|
1947
|
+
|
|
1948
|
+
const midRangePoly = `0,220 0,170 30,170 30,160 60,160 60,152 80,152 80,160 110,160 110,168
|
|
1949
|
+
140,168 140,158 160,158 160,148 175,148 175,140 188,140 188,135 198,135 198,140
|
|
1950
|
+
210,140 210,150 230,150 230,162 260,162 260,155 280,155 280,145 295,145 295,138
|
|
1951
|
+
310,138 310,145 330,145 330,155 350,155 350,165
|
|
1952
|
+
380,165 380,155 400,155 400,148 415,148 415,140 425,140 425,135 432,135 432,140
|
|
1953
|
+
440,140 440,150 460,150 460,160 480,160 480,168
|
|
1954
|
+
510,168 510,158 530,158 530,148 545,148 545,142 555,142 555,148
|
|
1955
|
+
565,148 565,158 585,158 585,165 610,165 610,158 630,158 630,165 660,165 660,170 680,170 680,220`;
|
|
1956
|
+
|
|
1957
|
+
// --- Snow caps (4px grid) ---
|
|
1958
|
+
const snowCaps = `
|
|
1959
|
+
<!-- Main peak snow -->
|
|
1960
|
+
<rect x="308" y="45" width="${P*2}" height="${P}" fill="${theme.snowHighlight}"/>
|
|
1961
|
+
<rect x="304" y="49" width="${P*4}" height="${P}" fill="${theme.snowColor}"/>
|
|
1962
|
+
<!-- Second peak snow -->
|
|
1963
|
+
<rect x="474" y="46" width="${P*2}" height="${P}" fill="${theme.snowHighlight}"/>
|
|
1964
|
+
<rect x="470" y="50" width="${P*4}" height="${P}" fill="${theme.snowColor}"/>
|
|
1965
|
+
<!-- Smaller peaks -->
|
|
1966
|
+
<rect x="104" y="80" width="${P*2}" height="${P}" fill="${theme.snowHighlight}"/>
|
|
1967
|
+
<rect x="604" y="70" width="${P*2}" height="${P}" fill="${theme.snowHighlight}"/>
|
|
1968
|
+
<!-- Mid-range snow -->
|
|
1969
|
+
<rect x="188" y="135" width="${P*2}" height="${P}" fill="${theme.snowColor}" opacity="0.5"/>
|
|
1970
|
+
<rect x="424" y="135" width="${P*2}" height="${P}" fill="${theme.snowColor}" opacity="0.5"/>
|
|
1971
|
+
<rect x="544" y="142" width="${P*2}" height="${P}" fill="${theme.snowColor}" opacity="0.5"/>
|
|
1972
|
+
`;
|
|
1973
|
+
|
|
1974
|
+
// --- Ground + texture ---
|
|
1975
|
+
const groundHtml = `
|
|
1976
|
+
<rect x="0" y="185" width="680" height="35" fill="${theme.ground}"/>
|
|
1977
|
+
<rect x="50" y="188" width="8" height="3" fill="#1a1d28" opacity="0.5"/>
|
|
1978
|
+
<rect x="200" y="190" width="6" height="2" fill="#1a1d28" opacity="0.4"/>
|
|
1979
|
+
<rect x="350" y="187" width="10" height="3" fill="#1a1d28" opacity="0.5"/>
|
|
1980
|
+
<rect x="500" y="191" width="7" height="2" fill="#1a1d28" opacity="0.4"/>
|
|
1981
|
+
<rect x="620" y="189" width="5" height="3" fill="#1a1d28" opacity="0.5"/>
|
|
1982
|
+
`;
|
|
1983
|
+
|
|
1984
|
+
// --- Shared basecamp elements (all 4px grid pixel art) ---
|
|
1985
|
+
|
|
1986
|
+
const tents = `
|
|
1987
|
+
<!-- Yellow expedition tent (pixel pyramid) -->
|
|
1988
|
+
<g>
|
|
1989
|
+
<rect x="88" y="172" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1990
|
+
<rect x="84" y="176" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1991
|
+
<rect x="88" y="176" width="${P}" height="${P}" fill="#a08818"/>
|
|
1992
|
+
<rect x="92" y="176" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1993
|
+
<rect x="80" y="180" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1994
|
+
<rect x="84" y="180" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1995
|
+
<rect x="88" y="180" width="${P}" height="${P}" fill="#2c2210"/>
|
|
1996
|
+
<rect x="92" y="180" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1997
|
+
<rect x="96" y="180" width="${P}" height="${P}" fill="#c8a820"/>
|
|
1998
|
+
</g>
|
|
1999
|
+
<!-- Blue dome tent (pixel) -->
|
|
2000
|
+
<g>
|
|
2001
|
+
<rect x="520" y="176" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2002
|
+
<rect x="524" y="176" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2003
|
+
<rect x="516" y="180" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2004
|
+
<rect x="520" y="180" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2005
|
+
<rect x="524" y="180" width="${P}" height="${P}" fill="#1a2040"/>
|
|
2006
|
+
<rect x="528" y="180" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2007
|
+
<rect x="532" y="180" width="${P}" height="${P}" fill="#2855a0"/>
|
|
2008
|
+
</g>
|
|
2009
|
+
<!-- Green small tent (pixel) -->
|
|
2010
|
+
<g>
|
|
2011
|
+
<rect x="580" y="176" width="${P}" height="${P}" fill="#1e8040"/>
|
|
2012
|
+
<rect x="576" y="180" width="${P}" height="${P}" fill="#1e8040"/>
|
|
2013
|
+
<rect x="580" y="180" width="${P}" height="${P}" fill="#166030"/>
|
|
2014
|
+
<rect x="584" y="180" width="${P}" height="${P}" fill="#1e8040"/>
|
|
2015
|
+
</g>
|
|
2016
|
+
<!-- Red expedition tent (pixel) -->
|
|
2017
|
+
<g>
|
|
2018
|
+
<rect x="448" y="172" width="${P}" height="${P}" fill="#b83030"/>
|
|
2019
|
+
<rect x="444" y="176" width="${P}" height="${P}" fill="#b83030"/>
|
|
2020
|
+
<rect x="448" y="176" width="${P}" height="${P}" fill="#902020"/>
|
|
2021
|
+
<rect x="452" y="176" width="${P}" height="${P}" fill="#b83030"/>
|
|
2022
|
+
<rect x="440" y="180" width="${P}" height="${P}" fill="#b83030"/>
|
|
2023
|
+
<rect x="444" y="180" width="${P}" height="${P}" fill="#b83030"/>
|
|
2024
|
+
<rect x="448" y="180" width="${P}" height="${P}" fill="#401010"/>
|
|
2025
|
+
<rect x="452" y="180" width="${P}" height="${P}" fill="#b83030"/>
|
|
2026
|
+
<rect x="456" y="180" width="${P}" height="${P}" fill="#b83030"/>
|
|
2027
|
+
</g>
|
|
2028
|
+
`;
|
|
2029
|
+
|
|
2030
|
+
const flagColors = ['#e74c3c','#f39c12','#fff','#2ecc71','#3498db'];
|
|
2031
|
+
const prayerFlags1 = flagColors.map((c, i) =>
|
|
2032
|
+
`<rect x="${156 + i*8}" y="172" width="${P}" height="${P}" fill="${c}" opacity="0.7"/>`
|
|
2033
|
+
).join('') + flagColors.map((c, i) =>
|
|
2034
|
+
`<rect x="${156 + i*8}" y="168" width="${P}" height="1" fill="#4a5170" opacity="0.5"/>`
|
|
2035
|
+
).join('');
|
|
2036
|
+
|
|
2037
|
+
const prayerFlags2 = flagColors.map((c, i) =>
|
|
2038
|
+
`<rect x="${420 + i*8}" y="168" width="${P}" height="${P}" fill="${c}" opacity="0.6"/>`
|
|
2039
|
+
).join('') + flagColors.map((c, i) =>
|
|
2040
|
+
`<rect x="${420 + i*8}" y="164" width="${P}" height="1" fill="#4a5170" opacity="0.4"/>`
|
|
2041
|
+
).join('');
|
|
2042
|
+
|
|
2043
|
+
const supplies = `
|
|
2044
|
+
<!-- Supply crates (pixel) -->
|
|
2045
|
+
<rect x="128" y="180" width="${P*2}" height="${P}" fill="#6b4a28"/>
|
|
2046
|
+
<rect x="128" y="180" width="${P*2}" height="1" fill="#8b6a38"/>
|
|
2047
|
+
<rect x="128" y="176" width="${P*2}" height="${P}" fill="#5a3a20"/>
|
|
2048
|
+
<rect x="128" y="176" width="${P*2}" height="1" fill="#7a5a30"/>
|
|
2049
|
+
<rect x="136" y="180" width="${P}" height="${P}" fill="#5a3a20"/>
|
|
2050
|
+
<!-- Oxygen tanks (pixel) -->
|
|
2051
|
+
<rect x="472" y="180" width="${P}" height="${P}" fill="#4a6a8a"/>
|
|
2052
|
+
<rect x="472" y="176" width="${P}" height="${P}" fill="#6a8aaa"/>
|
|
2053
|
+
<rect x="476" y="180" width="${P}" height="${P}" fill="#4a6a8a"/>
|
|
2054
|
+
<rect x="476" y="176" width="${P}" height="${P}" fill="#6a8aaa"/>
|
|
2055
|
+
<!-- Signpost (pixel) -->
|
|
2056
|
+
<rect x="300" y="172" width="${P}" height="${P*3}" fill="#5a4a30"/>
|
|
2057
|
+
<rect x="296" y="172" width="${P*3}" height="${P}" fill="#6b5a38"/>
|
|
2058
|
+
<rect x="308" y="173" width="${P}" height="2" fill="#6b5a38"/>
|
|
2059
|
+
<!-- Rope coil (pixel) -->
|
|
2060
|
+
<rect x="600" y="176" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2061
|
+
<rect x="604" y="176" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2062
|
+
<rect x="596" y="180" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2063
|
+
<rect x="608" y="180" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2064
|
+
<rect x="600" y="184" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2065
|
+
<rect x="604" y="184" width="${P}" height="${P}" fill="#8b7a50"/>
|
|
2066
|
+
<!-- Ice axe (pixel) -->
|
|
2067
|
+
<rect x="144" y="168" width="${P}" height="${P}" fill="#8090b0"/>
|
|
2068
|
+
<rect x="144" y="172" width="${P}" height="${P}" fill="#6b5a38"/>
|
|
2069
|
+
<rect x="144" y="176" width="${P}" height="${P}" fill="#6b5a38"/>
|
|
2070
|
+
<rect x="140" y="168" width="${P}" height="${P}" fill="#aab0c0"/>
|
|
2071
|
+
`;
|
|
2072
|
+
|
|
2073
|
+
// --- Stone ring around campfire (4px grid) ---
|
|
2074
|
+
const stoneRing = `
|
|
2075
|
+
<rect x="320" y="184" width="${P}" height="${P}" fill="#3a3a40"/>
|
|
2076
|
+
<rect x="324" y="184" width="${P}" height="${P}" fill="#454550"/>
|
|
2077
|
+
<rect x="336" y="184" width="${P}" height="${P}" fill="#454550"/>
|
|
2078
|
+
<rect x="340" y="184" width="${P}" height="${P}" fill="#3a3a40"/>
|
|
2079
|
+
<rect x="318" y="180" width="${P}" height="${P}" fill="#454550"/>
|
|
2080
|
+
<rect x="342" y="180" width="${P}" height="${P}" fill="#3a3a40"/>
|
|
2081
|
+
`;
|
|
2082
|
+
|
|
2083
|
+
// --- Campfire (period-specific) ---
|
|
2084
|
+
let campfireHtml = '';
|
|
2085
|
+
if (period === 'dawn') {
|
|
2086
|
+
// Dim embers (pixel)
|
|
2087
|
+
campfireHtml = `
|
|
2088
|
+
${stoneRing}
|
|
2089
|
+
<rect x="328" y="180" width="${P}" height="${P}" fill="#8b2200" opacity="0.5"/>
|
|
2090
|
+
<rect x="332" y="180" width="${P}" height="${P}" fill="#a03000" opacity="0.4"/>
|
|
2091
|
+
`;
|
|
2092
|
+
} else if (period === 'morning') {
|
|
2093
|
+
// Smoke only (pixel)
|
|
2094
|
+
campfireHtml = `
|
|
2095
|
+
${stoneRing}
|
|
2096
|
+
<rect x="328" y="180" width="${P}" height="${P}" fill="#5a4a30"/>
|
|
2097
|
+
<rect x="332" y="180" width="${P}" height="${P}" fill="#5a4a30"/>
|
|
2098
|
+
<rect x="328" y="176" width="${P}" height="${P}" fill="#4a5170" opacity="0.2"/>
|
|
2099
|
+
<rect x="332" y="172" width="${P}" height="${P}" fill="#4a5170" opacity="0.15"/>
|
|
2100
|
+
<rect x="328" y="168" width="${P}" height="${P}" fill="#4a5170" opacity="0.1"/>
|
|
2101
|
+
`;
|
|
2102
|
+
} else if (period === 'day') {
|
|
2103
|
+
// No fire, just logs (pixel)
|
|
2104
|
+
campfireHtml = `
|
|
2105
|
+
${stoneRing}
|
|
2106
|
+
<rect x="324" y="180" width="${P*3}" height="${P}" fill="#5a4a30"/>
|
|
2107
|
+
<rect x="328" y="176" width="${P*2}" height="${P}" fill="#6b4a28"/>
|
|
2108
|
+
`;
|
|
2109
|
+
} else {
|
|
2110
|
+
// Full fire with glow (all 4px pixel)
|
|
2111
|
+
campfireHtml = `
|
|
2112
|
+
${stoneRing}
|
|
2113
|
+
<!-- Glow (rect-based) -->
|
|
2114
|
+
<rect x="316" y="168" width="${P*7}" height="${P*4}" fill="#ff8c32" opacity="0.04"/>
|
|
2115
|
+
<rect x="320" y="172" width="${P*5}" height="${P*3}" fill="#ff6600" opacity="0.06"/>
|
|
2116
|
+
<!-- Logs -->
|
|
2117
|
+
<rect x="324" y="180" width="${P*3}" height="${P}" fill="#5a3a20"/>
|
|
2118
|
+
<rect x="326" y="180" width="${P*3}" height="${P}" fill="#6b4a28"/>
|
|
2119
|
+
<!-- Flames (pixel, animated with steps) -->
|
|
2120
|
+
<rect x="328" y="176" width="${P}" height="${P}" fill="#ff6600">
|
|
2121
|
+
<animate attributeName="opacity" values="1;0.6;1" dur="0.4s" steps="2" repeatCount="indefinite"/>
|
|
2122
|
+
</rect>
|
|
2123
|
+
<rect x="332" y="172" width="${P}" height="${P}" fill="#ffcc00">
|
|
2124
|
+
<animate attributeName="opacity" values="0.8;1;0.6" dur="0.5s" steps="2" repeatCount="indefinite"/>
|
|
2125
|
+
</rect>
|
|
2126
|
+
<rect x="332" y="176" width="${P}" height="${P}" fill="#ff8800"/>
|
|
2127
|
+
<rect x="328" y="172" width="${P}" height="${P}" fill="#ff9900" opacity="0.7">
|
|
2128
|
+
<animate attributeName="opacity" values="0.7;0.3;0.7" dur="0.35s" steps="2" repeatCount="indefinite"/>
|
|
2129
|
+
</rect>
|
|
2130
|
+
<rect x="336" y="176" width="${P}" height="${P}" fill="#ff6600" opacity="0.6"/>
|
|
2131
|
+
<rect x="330" y="168" width="${P}" height="${P}" fill="#ff6600" opacity="0.4">
|
|
2132
|
+
<animate attributeName="opacity" values="0.4;0.1;0.4" dur="0.6s" steps="2" repeatCount="indefinite"/>
|
|
2133
|
+
</rect>
|
|
2134
|
+
<!-- Smoke (pixel) -->
|
|
2135
|
+
<rect x="332" y="164" width="${P}" height="${P}" fill="#4a5170" opacity="0.15">
|
|
2136
|
+
<animate attributeName="opacity" values="0.15;0.05;0.15" dur="2s" repeatCount="indefinite"/>
|
|
2137
|
+
</rect>
|
|
2138
|
+
<rect x="328" y="160" width="${P}" height="${P}" fill="#4a5170" opacity="0.08"/>
|
|
2139
|
+
`;
|
|
2140
|
+
}
|
|
1837
2141
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2142
|
+
// --- Sherpa (period-specific) ---
|
|
2143
|
+
let sherpaHtml = '';
|
|
2144
|
+
if (period === 'dawn') {
|
|
2145
|
+
// Sleeping horizontally
|
|
2146
|
+
sherpaHtml = `
|
|
2147
|
+
<g transform="translate(345, 178)">
|
|
2148
|
+
<!-- Body lying flat -->
|
|
2149
|
+
<rect x="0" y="0" width="4" height="4" fill="#3498db"/>
|
|
2150
|
+
<rect x="4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2151
|
+
<rect x="8" y="0" width="4" height="4" fill="#3498db"/>
|
|
2152
|
+
<rect x="12" y="0" width="4" height="4" fill="#2c3e50"/>
|
|
2153
|
+
<rect x="16" y="0" width="4" height="4" fill="#2c3e50"/>
|
|
2154
|
+
<!-- Head -->
|
|
2155
|
+
<rect x="-4" y="-1" width="4" height="4" fill="#f5c6a0"/>
|
|
2156
|
+
<!-- Hat flat -->
|
|
2157
|
+
<rect x="-8" y="-2" width="4" height="4" fill="#e74c3c"/>
|
|
2158
|
+
<!-- Zzz (pixel) -->
|
|
2159
|
+
<rect x="8" y="-8" width="${P}" height="${P}" fill="#8888aa" opacity="0.5"/>
|
|
2160
|
+
<rect x="12" y="-12" width="${P}" height="${P}" fill="#8888aa" opacity="0.4"/>
|
|
2161
|
+
<rect x="14" y="-16" width="${P}" height="${P}" fill="#8888aa" opacity="0.3"/>
|
|
2162
|
+
<rect x="16" y="-20" width="${P}" height="${P}" fill="#8888aa" opacity="0.2"/>
|
|
2163
|
+
</g>
|
|
2164
|
+
`;
|
|
2165
|
+
} else if (period === 'morning') {
|
|
2166
|
+
// Stretching (arms raised)
|
|
2167
|
+
sherpaHtml = `
|
|
2168
|
+
<g transform="translate(345, 170)">
|
|
2169
|
+
<!-- Hat -->
|
|
2170
|
+
<rect x="0" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2171
|
+
<rect x="-4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2172
|
+
<rect x="4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2173
|
+
<!-- Face -->
|
|
2174
|
+
<rect x="-4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2175
|
+
<rect x="0" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2176
|
+
<rect x="4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2177
|
+
<!-- Body -->
|
|
2178
|
+
<rect x="-4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2179
|
+
<rect x="0" y="0" width="4" height="4" fill="#3498db"/>
|
|
2180
|
+
<rect x="4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2181
|
+
<!-- Arms raised -->
|
|
2182
|
+
<rect x="-8" y="-8" width="4" height="4" fill="#3498db"/>
|
|
2183
|
+
<rect x="8" y="-4" width="4" height="4" fill="#3498db"/>
|
|
2184
|
+
<!-- Lower body -->
|
|
2185
|
+
<rect x="-4" y="4" width="4" height="4" fill="#3498db"/>
|
|
2186
|
+
<rect x="0" y="4" width="4" height="4" fill="#3498db"/>
|
|
2187
|
+
<rect x="4" y="4" width="4" height="4" fill="#3498db"/>
|
|
2188
|
+
<!-- Legs -->
|
|
2189
|
+
<rect x="-4" y="8" width="4" height="4" fill="#2c3e50"/>
|
|
2190
|
+
<rect x="4" y="8" width="4" height="4" fill="#2c3e50"/>
|
|
2191
|
+
</g>
|
|
2192
|
+
`;
|
|
2193
|
+
} else if (period === 'day') {
|
|
2194
|
+
// Walking pose with backpack
|
|
2195
|
+
sherpaHtml = `
|
|
2196
|
+
<g transform="translate(355, 166)">
|
|
2197
|
+
<!-- Hat -->
|
|
2198
|
+
<rect x="0" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2199
|
+
<rect x="-4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2200
|
+
<rect x="4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2201
|
+
<!-- Face -->
|
|
2202
|
+
<rect x="-4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2203
|
+
<rect x="0" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2204
|
+
<rect x="4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2205
|
+
<!-- Body -->
|
|
2206
|
+
<rect x="-4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2207
|
+
<rect x="0" y="0" width="4" height="4" fill="#3498db"/>
|
|
2208
|
+
<rect x="4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2209
|
+
<rect x="-4" y="4" width="4" height="4" fill="#3498db"/>
|
|
2210
|
+
<rect x="0" y="4" width="4" height="4" fill="#3498db"/>
|
|
2211
|
+
<rect x="4" y="4" width="4" height="4" fill="#3498db"/>
|
|
2212
|
+
<!-- Backpack -->
|
|
2213
|
+
<rect x="8" y="0" width="4" height="4" fill="#8b6914"/>
|
|
2214
|
+
<rect x="8" y="4" width="4" height="4" fill="#8b6914"/>
|
|
2215
|
+
<!-- Legs (walking) -->
|
|
2216
|
+
<rect x="-4" y="8" width="4" height="4" fill="#2c3e50"/>
|
|
2217
|
+
<rect x="0" y="8" width="4" height="4" fill="#2c3e50"/>
|
|
2218
|
+
<rect x="4" y="12" width="4" height="4" fill="#2c3e50"/>
|
|
2219
|
+
<rect x="-4" y="12" width="4" height="4" fill="#2c3e50"/>
|
|
2220
|
+
</g>
|
|
2221
|
+
`;
|
|
2222
|
+
} else {
|
|
2223
|
+
// Sitting with mug
|
|
2224
|
+
sherpaHtml = `
|
|
2225
|
+
<g transform="translate(345, 170)">
|
|
2226
|
+
<!-- Hat -->
|
|
2227
|
+
<rect x="0" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2228
|
+
<rect x="-4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2229
|
+
<rect x="4" y="-8" width="4" height="4" fill="#e74c3c"/>
|
|
2230
|
+
<!-- Face -->
|
|
2231
|
+
<rect x="-4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2232
|
+
<rect x="0" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2233
|
+
<rect x="4" y="-4" width="4" height="4" fill="#f5c6a0"/>
|
|
2234
|
+
<!-- Body -->
|
|
2235
|
+
<rect x="-4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2236
|
+
<rect x="0" y="0" width="4" height="4" fill="#3498db"/>
|
|
2237
|
+
<rect x="4" y="0" width="4" height="4" fill="#3498db"/>
|
|
2238
|
+
<!-- Backpack -->
|
|
2239
|
+
<rect x="8" y="0" width="4" height="4" fill="#8b6914"/>
|
|
2240
|
+
<!-- Sitting legs -->
|
|
2241
|
+
<rect x="-4" y="4" width="4" height="4" fill="#2c3e50"/>
|
|
2242
|
+
<rect x="0" y="4" width="4" height="4" fill="#2c3e50"/>
|
|
2243
|
+
<!-- Mug (pixel) -->
|
|
2244
|
+
<rect x="-8" y="0" width="${P}" height="${P}" fill="#ddd"/>
|
|
2245
|
+
<rect x="-12" y="0" width="${P}" height="${P}" fill="#ddd" opacity="0.5"/>
|
|
2246
|
+
<!-- Steam (pixel) -->
|
|
2247
|
+
<rect x="-8" y="-4" width="${P}" height="${P}" fill="#4a5170" opacity="0.2">
|
|
2248
|
+
<animate attributeName="opacity" values="0.2;0.05;0.2" dur="2s" repeatCount="indefinite"/>
|
|
2249
|
+
</rect>
|
|
2250
|
+
</g>
|
|
2251
|
+
`;
|
|
2252
|
+
}
|
|
1842
2253
|
|
|
1843
|
-
//
|
|
1844
|
-
|
|
1845
|
-
|
|
2254
|
+
// --- Distant climber (day and evening only) ---
|
|
2255
|
+
let distantClimber = '';
|
|
2256
|
+
if (period === 'day' || period === 'evening') {
|
|
2257
|
+
const climbOpacity = period === 'day' ? 0.6 : 0.5;
|
|
2258
|
+
distantClimber = `
|
|
2259
|
+
<g transform="translate(612, 148)" opacity="${climbOpacity}">
|
|
2260
|
+
<rect x="0" y="0" width="${P}" height="${P}" fill="#f5c6a0"/>
|
|
2261
|
+
<rect x="0" y="4" width="${P}" height="${P}" fill="#c84040"/>
|
|
2262
|
+
<rect x="0" y="8" width="${P}" height="${P}" fill="#2c3e50"/>
|
|
2263
|
+
<rect x="4" y="0" width="${P}" height="${P}" fill="#8090b0" opacity="0.5"/>
|
|
2264
|
+
<rect x="4" y="4" width="${P}" height="${P}" fill="#8090b0" opacity="0.4"/>
|
|
2265
|
+
</g>
|
|
2266
|
+
`;
|
|
2267
|
+
}
|
|
1846
2268
|
|
|
1847
|
-
//
|
|
1848
|
-
|
|
1849
|
-
|
|
2269
|
+
// --- Tent glow (dawn only) ---
|
|
2270
|
+
let tentGlow = '';
|
|
2271
|
+
if (period === 'dawn') {
|
|
2272
|
+
tentGlow = `<rect x="88" y="180" width="4" height="4" fill="#ffcc44" opacity="0.3"/>`;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// --- Sunrise glow (morning only) ---
|
|
2276
|
+
let sunriseGlow = '';
|
|
2277
|
+
if (period === 'morning') {
|
|
2278
|
+
sunriseGlow = `<rect x="360" y="140" width="280" height="60" fill="#e8834a" opacity="0.06"/>
|
|
2279
|
+
<rect x="400" y="160" width="200" height="40" fill="#f5a060" opacity="0.05"/>`;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// --- Assemble SVG ---
|
|
2283
|
+
const svgString = `
|
|
2284
|
+
<svg viewBox="0 0 680 220" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" style="image-rendering:pixelated">
|
|
2285
|
+
<defs>
|
|
2286
|
+
<linearGradient id="bc-sky" x1="0" y1="0" x2="0" y2="1">
|
|
2287
|
+
${stops}
|
|
2288
|
+
</linearGradient>
|
|
2289
|
+
</defs>
|
|
2290
|
+
|
|
2291
|
+
<!-- Sky -->
|
|
2292
|
+
<rect width="680" height="220" fill="url(#bc-sky)"/>
|
|
2293
|
+
|
|
2294
|
+
${sunriseGlow}
|
|
2295
|
+
|
|
2296
|
+
<!-- Stars / clouds -->
|
|
2297
|
+
${starsHtml}
|
|
2298
|
+
|
|
2299
|
+
<!-- Far range mountains -->
|
|
2300
|
+
<polygon points="${farRangePoly}" fill="${theme.farRange}"/>
|
|
2301
|
+
|
|
2302
|
+
<!-- Snow caps -->
|
|
2303
|
+
${snowCaps}
|
|
2304
|
+
|
|
2305
|
+
<!-- Mid range mountains -->
|
|
2306
|
+
<polygon points="${midRangePoly}" fill="${theme.midRange}"/>
|
|
2307
|
+
|
|
2308
|
+
<!-- Ground -->
|
|
2309
|
+
${groundHtml}
|
|
2310
|
+
|
|
2311
|
+
<!-- Tent glow -->
|
|
2312
|
+
${tentGlow}
|
|
2313
|
+
|
|
2314
|
+
<!-- Tents -->
|
|
2315
|
+
${tents}
|
|
2316
|
+
|
|
2317
|
+
<!-- Prayer flags -->
|
|
2318
|
+
${prayerFlags1}
|
|
2319
|
+
${prayerFlags2}
|
|
2320
|
+
|
|
2321
|
+
<!-- Supplies & gear -->
|
|
2322
|
+
${supplies}
|
|
2323
|
+
|
|
2324
|
+
<!-- Campfire -->
|
|
2325
|
+
${campfireHtml}
|
|
2326
|
+
|
|
2327
|
+
<!-- Sherpa -->
|
|
2328
|
+
${sherpaHtml}
|
|
2329
|
+
|
|
2330
|
+
<!-- Distant climber -->
|
|
2331
|
+
${distantClimber}
|
|
2332
|
+
</svg>
|
|
2333
|
+
`;
|
|
2334
|
+
|
|
2335
|
+
container.innerHTML = svgString;
|
|
2336
|
+
}
|
|
1850
2337
|
|
|
1851
2338
|
function startSherpaQuotes() {
|
|
1852
2339
|
const el = document.getElementById('sherpa-quote');
|
|
1853
2340
|
if (!el) return;
|
|
1854
2341
|
|
|
1855
|
-
|
|
1856
|
-
let quotes = [...SHERPA_QUOTES];
|
|
1857
|
-
for (let i = quotes.length - 1; i > 0; i--) {
|
|
1858
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
1859
|
-
[quotes[i], quotes[j]] = [quotes[j], quotes[i]];
|
|
1860
|
-
}
|
|
2342
|
+
const isFirstVisit = !localStorage.getItem(ONBOARDING_KEY);
|
|
1861
2343
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2344
|
+
if (isFirstVisit) {
|
|
2345
|
+
// First visit: start with tap prompt, then guide on click
|
|
2346
|
+
sherpaMode = 'intro';
|
|
2347
|
+
el.textContent = '나를 눌러보세유~';
|
|
2348
|
+
} else {
|
|
2349
|
+
setSherpaMode('grumpy');
|
|
2350
|
+
}
|
|
1865
2351
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
// Reshuffle
|
|
1872
|
-
for (let i = quotes.length - 1; i > 0; i--) {
|
|
1873
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
1874
|
-
[quotes[i], quotes[j]] = [quotes[j], quotes[i]];
|
|
1875
|
-
}
|
|
1876
|
-
idx = 0;
|
|
1877
|
-
}
|
|
1878
|
-
el.textContent = quotes[idx];
|
|
1879
|
-
el.style.opacity = '1';
|
|
1880
|
-
}, 500);
|
|
2352
|
+
// Start rotation timer
|
|
2353
|
+
if (sherpaInterval) clearInterval(sherpaInterval);
|
|
2354
|
+
sherpaInterval = setInterval(() => {
|
|
2355
|
+
if (sherpaMode === 'intro') return; // Don't rotate during intro
|
|
2356
|
+
advanceSherpa();
|
|
1881
2357
|
}, 8000);
|
|
1882
2358
|
}
|
|
1883
2359
|
|
|
2360
|
+
window.toggleSherpaMode = function() {
|
|
2361
|
+
const speech = document.getElementById('sherpa-speech');
|
|
2362
|
+
const el = document.getElementById('sherpa-quote');
|
|
2363
|
+
if (!el || !speech) return;
|
|
2364
|
+
|
|
2365
|
+
if (sherpaMode === 'intro') {
|
|
2366
|
+
// First click ever: enter guide mode
|
|
2367
|
+
setSherpaMode('guide');
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// Toggle between guide and grumpy
|
|
2372
|
+
const newMode = sherpaMode === 'guide' ? 'grumpy' : 'guide';
|
|
2373
|
+
|
|
2374
|
+
// Brief mode-switch message
|
|
2375
|
+
el.style.opacity = '0';
|
|
2376
|
+
setTimeout(() => {
|
|
2377
|
+
if (newMode === 'guide') {
|
|
2378
|
+
el.textContent = '가이드 모드여유~ 사용법 알려줄겨 📋';
|
|
2379
|
+
} else {
|
|
2380
|
+
el.textContent = '다시 푸념 모드여유... 😮💨';
|
|
2381
|
+
}
|
|
2382
|
+
el.style.opacity = '1';
|
|
2383
|
+
|
|
2384
|
+
setTimeout(() => {
|
|
2385
|
+
setSherpaMode(newMode);
|
|
2386
|
+
}, 2000);
|
|
2387
|
+
}, 300);
|
|
2388
|
+
};
|
|
2389
|
+
|
|
1884
2390
|
// ---------------------------------------------------------------------------
|
|
1885
2391
|
// Activity Trail
|
|
1886
2392
|
// ---------------------------------------------------------------------------
|
|
@@ -2137,12 +2643,11 @@ async function init() {
|
|
|
2137
2643
|
}
|
|
2138
2644
|
renderAll();
|
|
2139
2645
|
loadPortal();
|
|
2646
|
+
renderBasecampScene();
|
|
2140
2647
|
startSherpaQuotes();
|
|
2141
2648
|
loadActivityTrail();
|
|
2142
2649
|
connectWs();
|
|
2143
2650
|
|
|
2144
|
-
// Show onboarding for first-time users
|
|
2145
|
-
setTimeout(showOnboarding, 500);
|
|
2146
2651
|
}
|
|
2147
2652
|
|
|
2148
2653
|
document.addEventListener('DOMContentLoaded', init);
|
package/dashboard/index.html
CHANGED
|
@@ -19,38 +19,14 @@
|
|
|
19
19
|
<div id="portal">
|
|
20
20
|
<!-- 베이스캠프 씬 -->
|
|
21
21
|
<div class="portal-section" id="basecamp-scene">
|
|
22
|
-
<div class="bc-scene">
|
|
23
|
-
<!--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<div class="bc-
|
|
28
|
-
<div class="bc-star twinkle" style="top:10%;left:80%"></div>
|
|
29
|
-
<div class="bc-star twinkle-slow" style="top:25%;left:92%"></div>
|
|
30
|
-
<div class="bc-star twinkle" style="top:18%;left:5%"></div>
|
|
31
|
-
<div class="bc-star twinkle-slow" style="top:6%;left:72%"></div>
|
|
32
|
-
<div class="bc-star twinkle" style="top:22%;left:42%"></div>
|
|
33
|
-
<div class="bc-star twinkle-slow" style="top:12%;left:55%"></div>
|
|
34
|
-
|
|
35
|
-
<!-- Mountains -->
|
|
36
|
-
<div class="bc-mountain bc-mountain-back"></div>
|
|
37
|
-
<div class="bc-mountain bc-mountain-front"></div>
|
|
38
|
-
|
|
39
|
-
<!-- Snow caps -->
|
|
40
|
-
<div class="bc-snow" style="bottom:64px;left:48%;width:12px"></div>
|
|
41
|
-
<div class="bc-snow" style="bottom:60px;left:50%;width:8px"></div>
|
|
42
|
-
<div class="bc-snow" style="bottom:56px;left:46%;width:4px"></div>
|
|
43
|
-
|
|
44
|
-
<!-- Campfire glow + campfire -->
|
|
45
|
-
<div class="bc-glow"></div>
|
|
46
|
-
<div class="bc-campfire"></div>
|
|
47
|
-
|
|
48
|
-
<!-- Sherpa -->
|
|
49
|
-
<div class="bc-sherpa"></div>
|
|
50
|
-
|
|
51
|
-
<!-- Speech bubble -->
|
|
22
|
+
<div class="bc-scene" id="bc-scene-container">
|
|
23
|
+
<!-- JS renders time-based SVG scene here -->
|
|
24
|
+
</div>
|
|
25
|
+
<!-- Speech bubble stays outside SVG for text rendering -->
|
|
26
|
+
<div class="bc-speech-wrap">
|
|
27
|
+
<div class="bc-sherpa-hitbox" onclick="toggleSherpaMode()" title="클릭해서 모드 변경"></div>
|
|
52
28
|
<div class="bc-speech fade-in" id="sherpa-speech">
|
|
53
|
-
<span id="sherpa-quote"
|
|
29
|
+
<span id="sherpa-quote"></span>
|
|
54
30
|
</div>
|
|
55
31
|
</div>
|
|
56
32
|
</div>
|
package/dashboard/style.css
CHANGED
|
@@ -2046,331 +2046,70 @@ header.hidden {
|
|
|
2046
2046
|
Onboarding Tutorial
|
|
2047
2047
|
============================================================ */
|
|
2048
2048
|
|
|
2049
|
-
.onboarding-overlay {
|
|
2050
|
-
position: fixed;
|
|
2051
|
-
top: 0; left: 0; right: 0; bottom: 0;
|
|
2052
|
-
background: rgba(0, 0, 0, 0.6);
|
|
2053
|
-
z-index: 10000;
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
.onboarding-highlight {
|
|
2057
|
-
position: fixed;
|
|
2058
|
-
border: 2px solid var(--accent, #6366f1);
|
|
2059
|
-
border-radius: 8px;
|
|
2060
|
-
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 20px rgba(99, 102, 241, 0.4);
|
|
2061
|
-
z-index: 10001;
|
|
2062
|
-
pointer-events: none;
|
|
2063
|
-
animation: onboard-pulse 1.5s ease-in-out infinite;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
@keyframes onboard-pulse {
|
|
2067
|
-
0%, 100% { box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 20px rgba(99, 102, 241, 0.3); }
|
|
2068
|
-
50% { box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5), 0 0 30px rgba(99, 102, 241, 0.6); }
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
.onboarding-tooltip {
|
|
2072
|
-
position: fixed;
|
|
2073
|
-
background: var(--bg-elevated, #1e1e2e);
|
|
2074
|
-
border: 1px solid var(--border, #333);
|
|
2075
|
-
border-radius: 12px;
|
|
2076
|
-
padding: 16px 20px;
|
|
2077
|
-
max-width: 320px;
|
|
2078
|
-
z-index: 10002;
|
|
2079
|
-
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
|
2080
|
-
animation: onboard-fade-in 0.3s ease-out;
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
@keyframes onboard-fade-in {
|
|
2084
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
2085
|
-
to { opacity: 1; transform: translateY(0); }
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
.onboarding-title {
|
|
2089
|
-
font-size: 15px;
|
|
2090
|
-
font-weight: 700;
|
|
2091
|
-
color: var(--text-primary, #fff);
|
|
2092
|
-
margin-bottom: 6px;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
.onboarding-text {
|
|
2096
|
-
font-size: 13px;
|
|
2097
|
-
color: var(--text-secondary, #aaa);
|
|
2098
|
-
line-height: 1.5;
|
|
2099
|
-
margin-bottom: 12px;
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
.onboarding-actions {
|
|
2103
|
-
display: flex;
|
|
2104
|
-
align-items: center;
|
|
2105
|
-
gap: 8px;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
.onboarding-step {
|
|
2109
|
-
font-size: 11px;
|
|
2110
|
-
color: var(--text-muted, #666);
|
|
2111
|
-
flex: 1;
|
|
2112
|
-
}
|
|
2113
2049
|
|
|
2114
2050
|
/* ============================================================
|
|
2115
|
-
Basecamp Scene
|
|
2051
|
+
Basecamp Scene — Himalaya
|
|
2116
2052
|
============================================================ */
|
|
2117
2053
|
|
|
2118
2054
|
.bc-scene {
|
|
2119
2055
|
position: relative;
|
|
2120
|
-
height:
|
|
2121
|
-
background: linear-gradient(180deg, #080a10 0%, #0c0e14 60%, #12151e 100%);
|
|
2056
|
+
height: 220px;
|
|
2122
2057
|
border-radius: 12px;
|
|
2123
2058
|
overflow: hidden;
|
|
2124
2059
|
border: 1px solid #1c2030;
|
|
2125
2060
|
}
|
|
2126
2061
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
height: 4px;
|
|
2132
|
-
background: #fff;
|
|
2133
|
-
image-rendering: pixelated;
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
.bc-star.twinkle {
|
|
2137
|
-
animation: twinkle 2s steps(2) infinite;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
.bc-star.twinkle-slow {
|
|
2141
|
-
animation: twinkle 3.5s steps(2) infinite 0.8s;
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
@keyframes twinkle {
|
|
2145
|
-
0%, 100% { opacity: 0.6; }
|
|
2146
|
-
50% { opacity: 0.1; }
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
/* Mountains */
|
|
2150
|
-
.bc-mountain {
|
|
2151
|
-
position: absolute;
|
|
2152
|
-
bottom: 0;
|
|
2153
|
-
left: 0;
|
|
2154
|
-
right: 0;
|
|
2155
|
-
image-rendering: pixelated;
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
.bc-mountain-back {
|
|
2159
|
-
height: 100px;
|
|
2160
|
-
background: #1a1d2a;
|
|
2161
|
-
clip-path: polygon(
|
|
2162
|
-
0% 100%, 0% 70%, 5% 70%, 5% 60%, 10% 60%, 10% 50%, 15% 50%, 15% 40%,
|
|
2163
|
-
20% 40%, 20% 35%, 25% 35%, 25% 45%, 30% 45%, 30% 55%, 35% 55%, 35% 50%,
|
|
2164
|
-
40% 50%, 40% 35%, 45% 35%, 45% 25%, 50% 25%, 50% 20%, 55% 20%, 55% 30%,
|
|
2165
|
-
60% 30%, 60% 40%, 65% 40%, 65% 50%, 70% 50%, 70% 40%, 75% 40%, 75% 50%,
|
|
2166
|
-
80% 50%, 80% 60%, 85% 60%, 85% 70%, 90% 70%, 90% 80%, 95% 80%, 100% 80%,
|
|
2167
|
-
100% 100%
|
|
2168
|
-
);
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
.bc-mountain-front {
|
|
2172
|
-
height: 80px;
|
|
2173
|
-
background: #12151e;
|
|
2174
|
-
clip-path: polygon(
|
|
2175
|
-
0% 100%, 0% 80%, 5% 80%, 5% 70%, 10% 70%, 10% 55%, 15% 55%, 15% 45%,
|
|
2176
|
-
20% 45%, 20% 40%, 25% 40%, 25% 50%, 30% 50%, 30% 60%, 35% 60%, 35% 55%,
|
|
2177
|
-
40% 55%, 40% 40%, 45% 40%, 45% 30%, 48% 30%, 48% 25%, 52% 25%, 52% 20%,
|
|
2178
|
-
55% 20%, 55% 30%, 58% 30%, 58% 40%, 62% 40%, 62% 50%, 65% 50%, 65% 45%,
|
|
2179
|
-
70% 45%, 70% 35%, 75% 35%, 75% 45%, 80% 45%, 80% 55%, 85% 55%, 85% 65%,
|
|
2180
|
-
90% 65%, 90% 75%, 95% 75%, 95% 85%, 100% 85%, 100% 100%
|
|
2181
|
-
);
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
/* Snow caps */
|
|
2185
|
-
.bc-snow {
|
|
2186
|
-
position: absolute;
|
|
2187
|
-
height: 4px;
|
|
2188
|
-
background: rgba(255, 255, 255, 0.25);
|
|
2189
|
-
image-rendering: pixelated;
|
|
2062
|
+
.bc-scene svg {
|
|
2063
|
+
display: block;
|
|
2064
|
+
width: 100%;
|
|
2065
|
+
height: 100%;
|
|
2190
2066
|
}
|
|
2191
2067
|
|
|
2192
|
-
/*
|
|
2193
|
-
.bc-
|
|
2068
|
+
/* Speech bubble positioning */
|
|
2069
|
+
.bc-speech-wrap {
|
|
2194
2070
|
position: absolute;
|
|
2195
|
-
bottom:
|
|
2196
|
-
left: 50
|
|
2197
|
-
|
|
2198
|
-
width: 80px;
|
|
2199
|
-
height: 40px;
|
|
2200
|
-
background: radial-gradient(ellipse at center bottom, rgba(255, 140, 50, 0.15) 0%, transparent 70%);
|
|
2201
|
-
animation: glow-pulse 2s ease infinite;
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
@keyframes glow-pulse {
|
|
2205
|
-
0%, 100% { opacity: 1; }
|
|
2206
|
-
50% { opacity: 0.6; }
|
|
2071
|
+
bottom: 56px;
|
|
2072
|
+
left: calc(50% + 16px);
|
|
2073
|
+
z-index: 5;
|
|
2207
2074
|
}
|
|
2208
2075
|
|
|
2209
|
-
|
|
2210
|
-
.bc-campfire {
|
|
2076
|
+
.bc-sherpa-hitbox {
|
|
2211
2077
|
position: absolute;
|
|
2212
|
-
bottom:
|
|
2213
|
-
left:
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
image-rendering: pixelated;
|
|
2219
|
-
box-shadow:
|
|
2220
|
-
-4px 0 0 #8b4513,
|
|
2221
|
-
4px 0 0 #8b4513,
|
|
2222
|
-
0 -4px 0 #ff6600,
|
|
2223
|
-
-4px -4px 0 #ff8800,
|
|
2224
|
-
4px -4px 0 #ff8800,
|
|
2225
|
-
0 -8px 0 #ffaa00,
|
|
2226
|
-
-4px -8px 0 #ff6600,
|
|
2227
|
-
4px -8px 0 #ff6600,
|
|
2228
|
-
0 -12px 0 #ffcc00;
|
|
2229
|
-
animation: bc-fire 0.4s steps(1) infinite;
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
@keyframes bc-fire {
|
|
2233
|
-
0% {
|
|
2234
|
-
box-shadow:
|
|
2235
|
-
-4px 0 0 #8b4513,
|
|
2236
|
-
4px 0 0 #8b4513,
|
|
2237
|
-
0 -4px 0 #ff6600,
|
|
2238
|
-
-4px -4px 0 #ff8800,
|
|
2239
|
-
4px -4px 0 #ff8800,
|
|
2240
|
-
0 -8px 0 #ffaa00,
|
|
2241
|
-
-4px -8px 0 #ff6600,
|
|
2242
|
-
4px -8px 0 #ff6600,
|
|
2243
|
-
0 -12px 0 #ffcc00;
|
|
2244
|
-
}
|
|
2245
|
-
50% {
|
|
2246
|
-
box-shadow:
|
|
2247
|
-
-4px 0 0 #8b4513,
|
|
2248
|
-
4px 0 0 #8b4513,
|
|
2249
|
-
0 -4px 0 #ff8800,
|
|
2250
|
-
-4px -4px 0 #ff6600,
|
|
2251
|
-
4px -4px 0 #ffaa00,
|
|
2252
|
-
0 -8px 0 #ff6600,
|
|
2253
|
-
-4px -8px 0 #ffcc00,
|
|
2254
|
-
4px -8px 0 #ff8800,
|
|
2255
|
-
0 -12px 0 #ff6600;
|
|
2256
|
-
}
|
|
2078
|
+
bottom: -44px;
|
|
2079
|
+
left: -8px;
|
|
2080
|
+
width: 44px;
|
|
2081
|
+
height: 44px;
|
|
2082
|
+
cursor: pointer;
|
|
2083
|
+
z-index: 6;
|
|
2257
2084
|
}
|
|
2258
2085
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
position: absolute;
|
|
2262
|
-
bottom: 16px;
|
|
2263
|
-
left: calc(50% + 24px);
|
|
2264
|
-
width: 4px;
|
|
2265
|
-
height: 4px;
|
|
2266
|
-
background: transparent;
|
|
2267
|
-
image-rendering: pixelated;
|
|
2268
|
-
box-shadow:
|
|
2269
|
-
/* Hat */
|
|
2270
|
-
0 -20px 0 #e74c3c,
|
|
2271
|
-
-4px -20px 0 #e74c3c,
|
|
2272
|
-
4px -20px 0 #e74c3c,
|
|
2273
|
-
-4px -24px 0 #e74c3c,
|
|
2274
|
-
0 -24px 0 #e74c3c,
|
|
2275
|
-
4px -24px 0 #e74c3c,
|
|
2276
|
-
/* Face */
|
|
2277
|
-
-4px -16px 0 #f5c6a0,
|
|
2278
|
-
0 -16px 0 #f5c6a0,
|
|
2279
|
-
4px -16px 0 #f5c6a0,
|
|
2280
|
-
/* Body */
|
|
2281
|
-
-4px -12px 0 #3498db,
|
|
2282
|
-
0 -12px 0 #3498db,
|
|
2283
|
-
4px -12px 0 #3498db,
|
|
2284
|
-
-4px -8px 0 #3498db,
|
|
2285
|
-
0 -8px 0 #3498db,
|
|
2286
|
-
4px -8px 0 #3498db,
|
|
2287
|
-
/* Backpack */
|
|
2288
|
-
8px -12px 0 #8b6914,
|
|
2289
|
-
8px -8px 0 #8b6914,
|
|
2290
|
-
/* Legs */
|
|
2291
|
-
-4px -4px 0 #2c3e50,
|
|
2292
|
-
0 -4px 0 #2c3e50,
|
|
2293
|
-
-4px 0 0 #2c3e50,
|
|
2294
|
-
4px 0 0 #2c3e50;
|
|
2295
|
-
animation: sherpa-idle 1s steps(1) infinite;
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
@keyframes sherpa-idle {
|
|
2299
|
-
0%, 100% {
|
|
2300
|
-
box-shadow:
|
|
2301
|
-
0 -20px 0 #e74c3c,
|
|
2302
|
-
-4px -20px 0 #e74c3c,
|
|
2303
|
-
4px -20px 0 #e74c3c,
|
|
2304
|
-
-4px -24px 0 #e74c3c,
|
|
2305
|
-
0 -24px 0 #e74c3c,
|
|
2306
|
-
4px -24px 0 #e74c3c,
|
|
2307
|
-
-4px -16px 0 #f5c6a0,
|
|
2308
|
-
0 -16px 0 #f5c6a0,
|
|
2309
|
-
4px -16px 0 #f5c6a0,
|
|
2310
|
-
-4px -12px 0 #3498db,
|
|
2311
|
-
0 -12px 0 #3498db,
|
|
2312
|
-
4px -12px 0 #3498db,
|
|
2313
|
-
-4px -8px 0 #3498db,
|
|
2314
|
-
0 -8px 0 #3498db,
|
|
2315
|
-
4px -8px 0 #3498db,
|
|
2316
|
-
8px -12px 0 #8b6914,
|
|
2317
|
-
8px -8px 0 #8b6914,
|
|
2318
|
-
-4px -4px 0 #2c3e50,
|
|
2319
|
-
0 -4px 0 #2c3e50,
|
|
2320
|
-
-4px 0 0 #2c3e50,
|
|
2321
|
-
4px 0 0 #2c3e50;
|
|
2322
|
-
}
|
|
2323
|
-
50% {
|
|
2324
|
-
box-shadow:
|
|
2325
|
-
0 -20px 0 #e74c3c,
|
|
2326
|
-
-4px -20px 0 #e74c3c,
|
|
2327
|
-
4px -20px 0 #e74c3c,
|
|
2328
|
-
-4px -24px 0 #e74c3c,
|
|
2329
|
-
0 -24px 0 #e74c3c,
|
|
2330
|
-
4px -24px 0 #e74c3c,
|
|
2331
|
-
-4px -16px 0 #f5c6a0,
|
|
2332
|
-
0 -16px 0 #f5c6a0,
|
|
2333
|
-
4px -16px 0 #f5c6a0,
|
|
2334
|
-
-4px -12px 0 #3498db,
|
|
2335
|
-
0 -12px 0 #3498db,
|
|
2336
|
-
4px -12px 0 #3498db,
|
|
2337
|
-
-4px -8px 0 #3498db,
|
|
2338
|
-
0 -8px 0 #3498db,
|
|
2339
|
-
4px -8px 0 #3498db,
|
|
2340
|
-
8px -12px 0 #8b6914,
|
|
2341
|
-
8px -8px 0 #8b6914,
|
|
2342
|
-
-4px -4px 0 #2c3e50,
|
|
2343
|
-
4px -4px 0 #2c3e50,
|
|
2344
|
-
-4px 0 0 #2c3e50,
|
|
2345
|
-
0 0 0 #2c3e50;
|
|
2346
|
-
}
|
|
2086
|
+
#basecamp-scene {
|
|
2087
|
+
position: relative;
|
|
2347
2088
|
}
|
|
2348
2089
|
|
|
2349
|
-
/* Speech bubble */
|
|
2350
2090
|
.bc-speech {
|
|
2351
|
-
position:
|
|
2352
|
-
bottom: 52px;
|
|
2353
|
-
left: calc(50% + 8px);
|
|
2091
|
+
position: relative;
|
|
2354
2092
|
background: #1c2030;
|
|
2355
2093
|
border: 1px solid #2a2f42;
|
|
2356
2094
|
border-radius: 8px;
|
|
2357
2095
|
padding: 6px 10px;
|
|
2358
2096
|
font-size: 11px;
|
|
2097
|
+
color: #e4e8f0;
|
|
2359
2098
|
white-space: nowrap;
|
|
2360
|
-
color: var(--text-secondary);
|
|
2361
2099
|
animation: bubble-float 3s ease-in-out infinite;
|
|
2362
2100
|
}
|
|
2363
2101
|
|
|
2364
2102
|
.bc-speech::after {
|
|
2365
2103
|
content: '';
|
|
2366
2104
|
position: absolute;
|
|
2367
|
-
bottom: -
|
|
2105
|
+
bottom: -5px;
|
|
2368
2106
|
left: 20px;
|
|
2369
|
-
width:
|
|
2370
|
-
height:
|
|
2371
|
-
|
|
2372
|
-
border-right:
|
|
2373
|
-
border-
|
|
2107
|
+
width: 8px;
|
|
2108
|
+
height: 8px;
|
|
2109
|
+
background: #1c2030;
|
|
2110
|
+
border-right: 1px solid #2a2f42;
|
|
2111
|
+
border-bottom: 1px solid #2a2f42;
|
|
2112
|
+
transform: rotate(45deg);
|
|
2374
2113
|
}
|
|
2375
2114
|
|
|
2376
2115
|
@keyframes bubble-float {
|
|
@@ -2388,6 +2127,20 @@ header.hidden {
|
|
|
2388
2127
|
transition: opacity 0.5s;
|
|
2389
2128
|
}
|
|
2390
2129
|
|
|
2130
|
+
.bc-speech.guide-mode {
|
|
2131
|
+
border-color: rgba(99, 102, 241, 0.4);
|
|
2132
|
+
background: #1a1d2e;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
.bc-speech.guide-mode::after {
|
|
2136
|
+
background: #1a1d2e;
|
|
2137
|
+
border-color: rgba(99, 102, 241, 0.4);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
#sherpa-quote {
|
|
2141
|
+
transition: opacity 0.5s;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2391
2144
|
/* ============================================================
|
|
2392
2145
|
Activity Trail
|
|
2393
2146
|
============================================================ */
|
package/dist/bin/sanjang.js
CHANGED
|
@@ -132,7 +132,49 @@ else if (command === "help" || command === "--help" || command === "-h") {
|
|
|
132
132
|
`);
|
|
133
133
|
}
|
|
134
134
|
else {
|
|
135
|
-
// Default: start server
|
|
135
|
+
// Default: start server — auto-init if no config exists
|
|
136
|
+
const configPath = resolve(projectRoot, "sanjang.config.js");
|
|
137
|
+
if (!existsSync(configPath)) {
|
|
138
|
+
console.log("⛰ 설정 파일이 없습니다. 프로젝트를 분석합니다...\n");
|
|
139
|
+
const { generateConfig, detectApps } = await import("../lib/config.js");
|
|
140
|
+
const apps = detectApps(projectRoot);
|
|
141
|
+
let appDir;
|
|
142
|
+
if (apps.length >= 2) {
|
|
143
|
+
console.log("⛰ 여러 앱이 감지되었습니다:");
|
|
144
|
+
for (let i = 0; i < apps.length; i++) {
|
|
145
|
+
console.log(` ${i + 1}) ${apps[i].dir}/\t(${apps[i].framework})`);
|
|
146
|
+
}
|
|
147
|
+
console.log("");
|
|
148
|
+
const { createInterface } = await import("node:readline");
|
|
149
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
150
|
+
const answer = await new Promise((r) => { rl.question(" 어떤 앱을 띄울까요? [번호]: ", r); });
|
|
151
|
+
rl.close();
|
|
152
|
+
const idx = parseInt(answer) - 1;
|
|
153
|
+
if (idx < 0 || idx >= apps.length || isNaN(idx)) {
|
|
154
|
+
console.error("⛰ 잘못된 선택입니다.");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
appDir = apps[idx].dir;
|
|
158
|
+
console.log(` → ${appDir}/ (${apps[idx].framework}) 선택됨\n`);
|
|
159
|
+
}
|
|
160
|
+
else if (apps.length === 1) {
|
|
161
|
+
appDir = apps[0].dir;
|
|
162
|
+
}
|
|
163
|
+
const result = generateConfig(projectRoot, { appDir, force });
|
|
164
|
+
if (result.created) {
|
|
165
|
+
console.log(`⛰ ${result.message}`);
|
|
166
|
+
console.log(` 프레임워크: ${result.framework}\n`);
|
|
167
|
+
}
|
|
168
|
+
// Add .sanjang to .gitignore
|
|
169
|
+
const gitignorePath = resolve(projectRoot, ".gitignore");
|
|
170
|
+
if (existsSync(gitignorePath)) {
|
|
171
|
+
const { readFileSync, appendFileSync } = await import("node:fs");
|
|
172
|
+
const content = readFileSync(gitignorePath, "utf8");
|
|
173
|
+
if (!content.includes(".sanjang")) {
|
|
174
|
+
appendFileSync(gitignorePath, "\n# Sanjang local dev camps\n.sanjang/\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
136
178
|
const { startServer } = await import("../lib/server.js");
|
|
137
179
|
await startServer(projectRoot, { port });
|
|
138
180
|
}
|