马一丁

Application day/night mode switch button

@@ -486,7 +486,7 @@ class HTMLRenderer: @@ -486,7 +486,7 @@ class HTMLRenderer:
486 {self._render_tagline()} 486 {self._render_tagline()}
487 </div> 487 </div>
488 <div class="header-actions"> 488 <div class="header-actions">
489 - <button id="theme-toggle" class="action-btn" type="button">🌗 主题切换</button> 489 + <theme-button value="light" id="theme-toggle" size="1.5"></theme-button>
490 <button id="print-btn" class="action-btn" type="button">🖨️ 打印</button> 490 <button id="print-btn" class="action-btn" type="button">🖨️ 打印</button>
491 <button id="export-btn" class="action-btn" type="button" style="display: none;">⬇️ 导出PDF</button> 491 <button id="export-btn" class="action-btn" type="button" style="display: none;">⬇️ 导出PDF</button>
492 </div> 492 </div>
@@ -2907,6 +2907,11 @@ body {{ @@ -2907,6 +2907,11 @@ body {{
2907 display: flex; 2907 display: flex;
2908 gap: 12px; 2908 gap: 12px;
2909 flex-wrap: wrap; 2909 flex-wrap: wrap;
  2910 + align-items: center;
  2911 +}}
  2912 +theme-button {{
  2913 + display: inline-block;
  2914 + vertical-align: middle;
2910 }} 2915 }}
2911 .cover {{ 2916 .cover {{
2912 text-align: center; 2917 text-align: center;
@@ -3797,6 +3802,166 @@ img, canvas, svg {{ @@ -3797,6 +3802,166 @@ img, canvas, svg {{
3797 document.documentElement.classList.remove('no-js'); 3802 document.documentElement.classList.remove('no-js');
3798 document.documentElement.classList.add('js-ready'); 3803 document.documentElement.classList.add('js-ready');
3799 3804
  3805 +/* ========== Theme Button Web Component ========== */
  3806 +(() => {
  3807 + const themeButtonFunc = (root, initTheme, changeTheme) => {
  3808 + const $ = (s) => {
  3809 + let dom = root.querySelectorAll(s);
  3810 + return dom.length == 1 ? dom[0] : dom;
  3811 + };
  3812 + let mainButton = $(".main-button");
  3813 + let daytimeBackground = $(".daytime-background");
  3814 + let cloud = $(".cloud");
  3815 + let cloudList = $(".cloud-son");
  3816 + let cloudLight = $(".cloud-light");
  3817 + let components = $(".components");
  3818 + let moon = $(".moon");
  3819 + let stars = $(".stars");
  3820 + let star = $(".star");
  3821 + let isMoved = false;
  3822 + let isClicked = false;
  3823 +
  3824 + components.onclick = () => {
  3825 + if (isMoved) {
  3826 + mainButton.style.transform = "translateX(0)";
  3827 + mainButton.style.backgroundColor = "rgba(255, 195, 35,1)";
  3828 + mainButton.style.boxShadow = "3em 3em 5em rgba(0, 0, 0, 0.5), inset -3em -5em 3em -3em rgba(0, 0, 0, 0.5), inset 4em 5em 2em -2em rgba(255, 230, 80,1)";
  3829 + daytimeBackground[0].style.transform = "translateX(0)";
  3830 + daytimeBackground[1].style.transform = "translateX(0)";
  3831 + daytimeBackground[2].style.transform = "translateX(0)";
  3832 + cloud.style.transform = "translateY(10em)";
  3833 + cloudLight.style.transform = "translateY(10em)";
  3834 + components.style.backgroundColor = "rgba(70, 133, 192,1)";
  3835 + moon[0].style.opacity = "0";
  3836 + moon[1].style.opacity = "0";
  3837 + moon[2].style.opacity = "0";
  3838 + stars.style.transform = "translateY(-125em)";
  3839 + stars.style.opacity = "0";
  3840 + changeTheme("light");
  3841 + } else {
  3842 + mainButton.style.transform = "translateX(110em)";
  3843 + mainButton.style.backgroundColor = "rgba(195, 200,210,1)";
  3844 + mainButton.style.boxShadow = "3em 3em 5em rgba(0, 0, 0, 0.5), inset -3em -5em 3em -3em rgba(0, 0, 0, 0.5), inset 4em 5em 2em -2em rgba(255, 255, 210,1)";
  3845 + daytimeBackground[0].style.transform = "translateX(110em)";
  3846 + daytimeBackground[1].style.transform = "translateX(80em)";
  3847 + daytimeBackground[2].style.transform = "translateX(50em)";
  3848 + cloud.style.transform = "translateY(80em)";
  3849 + cloudLight.style.transform = "translateY(80em)";
  3850 + components.style.backgroundColor = "rgba(25,30,50,1)";
  3851 + moon[0].style.opacity = "1";
  3852 + moon[1].style.opacity = "1";
  3853 + moon[2].style.opacity = "1";
  3854 + stars.style.transform = "translateY(-62.5em)";
  3855 + stars.style.opacity = "1";
  3856 + changeTheme("dark");
  3857 + }
  3858 + isClicked = true;
  3859 + setTimeout(function () { isClicked = false; }, 500);
  3860 + isMoved = !isMoved;
  3861 + };
  3862 +
  3863 + mainButton.addEventListener("mousemove", function () {
  3864 + if (isClicked) return;
  3865 + if (isMoved) {
  3866 + mainButton.style.transform = "translateX(100em)";
  3867 + daytimeBackground[0].style.transform = "translateX(100em)";
  3868 + daytimeBackground[1].style.transform = "translateX(73em)";
  3869 + daytimeBackground[2].style.transform = "translateX(46em)";
  3870 + star[0].style.top = "10em"; star[0].style.left = "36em";
  3871 + star[1].style.top = "40em"; star[1].style.left = "87em";
  3872 + star[2].style.top = "26em"; star[2].style.left = "16em";
  3873 + star[3].style.top = "38em"; star[3].style.left = "63em";
  3874 + star[4].style.top = "20.5em"; star[4].style.left = "72em";
  3875 + star[5].style.top = "51.5em"; star[5].style.left = "35em";
  3876 + } else {
  3877 + mainButton.style.transform = "translateX(10em)";
  3878 + daytimeBackground[0].style.transform = "translateX(10em)";
  3879 + daytimeBackground[1].style.transform = "translateX(7em)";
  3880 + daytimeBackground[2].style.transform = "translateX(4em)";
  3881 + cloudList[0].style.right = "-24em"; cloudList[0].style.bottom = "10em";
  3882 + cloudList[1].style.right = "-12em"; cloudList[1].style.bottom = "-27em";
  3883 + cloudList[2].style.right = "17em"; cloudList[2].style.bottom = "-43em";
  3884 + cloudList[3].style.right = "46em"; cloudList[3].style.bottom = "-39em";
  3885 + cloudList[4].style.right = "70em"; cloudList[4].style.bottom = "-65em";
  3886 + cloudList[5].style.right = "109em"; cloudList[5].style.bottom = "-54em";
  3887 + cloudList[6].style.right = "-23em"; cloudList[6].style.bottom = "10em";
  3888 + cloudList[7].style.right = "-11em"; cloudList[7].style.bottom = "-26em";
  3889 + cloudList[8].style.right = "18em"; cloudList[8].style.bottom = "-42em";
  3890 + cloudList[9].style.right = "47em"; cloudList[9].style.bottom = "-38em";
  3891 + cloudList[10].style.right = "74em"; cloudList[10].style.bottom = "-64em";
  3892 + cloudList[11].style.right = "110em"; cloudList[11].style.bottom = "-55em";
  3893 + }
  3894 + });
  3895 +
  3896 + mainButton.addEventListener("mouseout", function () {
  3897 + if (isClicked) return;
  3898 + if (isMoved) {
  3899 + mainButton.style.transform = "translateX(110em)";
  3900 + daytimeBackground[0].style.transform = "translateX(110em)";
  3901 + daytimeBackground[1].style.transform = "translateX(80em)";
  3902 + daytimeBackground[2].style.transform = "translateX(50em)";
  3903 + star[0].style.top = "11em"; star[0].style.left = "39em";
  3904 + star[1].style.top = "39em"; star[1].style.left = "91em";
  3905 + star[2].style.top = "26em"; star[2].style.left = "19em";
  3906 + star[3].style.top = "37em"; star[3].style.left = "66em";
  3907 + star[4].style.top = "21em"; star[4].style.left = "75em";
  3908 + star[5].style.top = "51em"; star[5].style.left = "38em";
  3909 + } else {
  3910 + mainButton.style.transform = "translateX(0em)";
  3911 + daytimeBackground[0].style.transform = "translateX(0em)";
  3912 + daytimeBackground[1].style.transform = "translateX(0em)";
  3913 + daytimeBackground[2].style.transform = "translateX(0em)";
  3914 + cloudList[0].style.right = "-20em"; cloudList[0].style.bottom = "10em";
  3915 + cloudList[1].style.right = "-10em"; cloudList[1].style.bottom = "-25em";
  3916 + cloudList[2].style.right = "20em"; cloudList[2].style.bottom = "-40em";
  3917 + cloudList[3].style.right = "50em"; cloudList[3].style.bottom = "-35em";
  3918 + cloudList[4].style.right = "75em"; cloudList[4].style.bottom = "-60em";
  3919 + cloudList[5].style.right = "110em"; cloudList[5].style.bottom = "-50em";
  3920 + cloudList[6].style.right = "-20em"; cloudList[6].style.bottom = "10em";
  3921 + cloudList[7].style.right = "-10em"; cloudList[7].style.bottom = "-25em";
  3922 + cloudList[8].style.right = "20em"; cloudList[8].style.bottom = "-40em";
  3923 + cloudList[9].style.right = "50em"; cloudList[9].style.bottom = "-35em";
  3924 + cloudList[10].style.right = "75em"; cloudList[10].style.bottom = "-60em";
  3925 + cloudList[11].style.right = "110em"; cloudList[11].style.bottom = "-50em";
  3926 + }
  3927 + });
  3928 +
  3929 + const getRandomDirection = () => ["2em", "-2em"][Math.floor(Math.random() * 2)];
  3930 + const moveElementRandomly = (element) => {
  3931 + element.style.transform = `translate(${getRandomDirection()}, ${getRandomDirection()})`;
  3932 + };
  3933 + const cloudSons = root.querySelectorAll(".cloud-son");
  3934 + setInterval(() => { cloudSons.forEach(moveElementRandomly); }, 1000);
  3935 +
  3936 + if (initTheme === "dark") {
  3937 + components.onclick();
  3938 + }
  3939 + };
  3940 +
  3941 + class ThemeButton extends HTMLElement {
  3942 + constructor() { super(); }
  3943 + connectedCallback() {
  3944 + const initTheme = this.getAttribute("value") || "light";
  3945 + const size = +this.getAttribute("size") || 3;
  3946 + const shadow = this.attachShadow({ mode: "closed" });
  3947 + const container = document.createElement("div");
  3948 + container.setAttribute("class", "container");
  3949 + container.setAttribute("style", `font-size: ${(size / 3).toFixed(2)}px`);
  3950 + container.innerHTML = '<div class="components"><div class="main-button"><div class="moon"></div><div class="moon"></div><div class="moon"></div></div><div class="daytime-background"></div><div class="daytime-background"></div><div class="daytime-background"></div><div class="cloud"><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div></div><div class="cloud-light"><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div><div class="cloud-son"></div></div><div class="stars"><div class="star big"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div><div class="star big"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div><div class="star medium"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div><div class="star medium"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div><div class="star small"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div><div class="star small"><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div><div class="star-son"></div></div></div></div>';
  3951 + const style = document.createElement("style");
  3952 + style.textContent = "* { margin: 0; padding: 0; transition: 0.7s; -webkit-tap-highlight-color:rgba(0,0,0,0); } .container { position: relative; width: 180em; height: 70em; display: inline-block; vertical-align: bottom; transform: translate3d(0, 0, 0); } .components{ position: relative; width: 180em; height: 70em; background-color: rgba(70, 133, 192,1); border-radius: 100em; box-shadow: inset 0 0 5em 3em rgba(0, 0, 0, 0.5); overflow: hidden; transition: 0.7s; transition-timing-function: cubic-bezier( 0,0.5, 1,1); cursor: pointer; } .main-button{ margin: 7.5em 0 0 7.5em; width: 55em; height:55em; background-color: rgba(255, 195, 35,1); border-radius: 50%; box-shadow:3em 3em 5em rgba(0, 0, 0, 0.5), inset -3em -5em 3em -3em rgba(0, 0, 0, 0.5), inset 4em 5em 2em -2em rgba(255, 230, 80,1); transition: 1.0s; transition-timing-function: cubic-bezier(0.56, 1.35, 0.52, 1.00); } .moon{ position: absolute; background-color: rgba(150, 160, 180, 1); box-shadow:inset 0em 0em 1em 1em rgba(0, 0, 0, 0.3) ; border-radius: 50%; transition: 0.5s; opacity: 0; } .moon:nth-child(1){ top: 7.5em; left: 25em; width: 12.5em; height: 12.5em; } .moon:nth-child(2){ top: 20em; left: 7.5em; width: 20em; height: 20em; } .moon:nth-child(3){ top: 32.5em; left: 32.5em; width: 12.5em; height: 12.5em; } .daytime-background { position: absolute; border-radius: 50%; transition: 1.0s; transition-timing-function: cubic-bezier(0.56, 1.35, 0.52, 1.00); } .daytime-background:nth-child(2){ top: -20em; left: -20em; width: 110em; height:110em; background-color: rgba(255, 255, 255,0.2); z-index: -2; } .daytime-background:nth-child(3){ top: -32.5em; left: -17.5em; width: 135em; height:135em; background-color: rgba(255, 255, 255,0.1); z-index: -3; } .daytime-background:nth-child(4){ top: -45em; left: -15em; width: 160em; height:160em; background-color: rgba(255, 255, 255,0.05); z-index: -4; } .cloud,.cloud-light{ transform: translateY(10em); transition: 1.0s; transition-timing-function: cubic-bezier(0.56, 1.35, 0.52, 1.00); } .cloud-son{ position: absolute; background-color: #fff; border-radius: 50%; z-index: -1; transition: transform 6s,right 1s,bottom 1s; } .cloud-son:nth-child(6n+1){ right: -20em; bottom: 10em; width: 50em; height: 50em; } .cloud-son:nth-child(6n+2) { right: -10em; bottom: -25em; width: 60em; height: 60em; } .cloud-son:nth-child(6n+3) { right: 20em; bottom: -40em; width: 60em; height: 60em; } .cloud-son:nth-child(6n+4) { right: 50em; bottom: -35em; width: 60em; height: 60em; } .cloud-son:nth-child(6n+5) { right: 75em; bottom: -60em; width: 75em; height: 75em; } .cloud-son:nth-child(6n+6) { right: 110em; bottom: -50em; width: 60em; height: 60em; } .cloud{ z-index: -2; } .cloud-light{ position: absolute; right: 0em; bottom: 25em; opacity: 0.5; z-index: -3; } .stars{ transform: translateY(-125em); z-index: -2; transition: 1.0s; transition-timing-function: cubic-bezier(0.56, 1.35, 0.52, 1.00); } .big { --size: 7.5em; } .medium { --size: 5em; } .small { --size: 3em; } .star { position: absolute; width: calc(2*var(--size)); height: calc(2*var(--size)); } .star:nth-child(1){ top: 11em; left: 39em; animation-name: star; animation-duration: 3.5s; } .star:nth-child(2){ top: 39em; left: 91em; animation-name: star; animation-duration: 4.1s; } .star:nth-child(3){ top: 26em; left: 19em; animation-name: star; animation-duration: 4.9s; } .star:nth-child(4){ top: 37em; left: 66em; animation-name: star; animation-duration: 5.3s; } .star:nth-child(5){ top: 21em; left: 75em; animation-name: star; animation-duration: 3s; } .star:nth-child(6){ top: 51em; left: 38em; animation-name: star; animation-duration: 2.2s; } @keyframes star { 0%,20%{ transform: scale(0); } 20%,100% { transform: scale(1); } } .star-son{ float: left; } .star-son:nth-child(1) { --pos: left 0; } .star-son:nth-child(2) { --pos: right 0; } .star-son:nth-child(3) { --pos: 0 bottom; } .star-son:nth-child(4) { --pos: right bottom; } .star-son { width: var(--size); height: var(--size); background-image: radial-gradient(circle var(--size) at var(--pos), transparent var(--size), #fff); } .star{ transform: scale(1); transition-timing-function: cubic-bezier(0.56, 1.35, 0.52, 1.00); transition: 1s; animation-iteration-count:infinite; animation-direction: alternate; animation-timing-function: linear; } .twinkle { transform: scale(0); }";
  3953 + const changeTheme = (detail) => {
  3954 + this.dispatchEvent(new CustomEvent("change", { detail }));
  3955 + };
  3956 + themeButtonFunc(container, initTheme, changeTheme);
  3957 + shadow.appendChild(style);
  3958 + shadow.appendChild(container);
  3959 + }
  3960 + }
  3961 + customElements.define("theme-button", ThemeButton);
  3962 +})();
  3963 +/* ========== End Theme Button Web Component ========== */
  3964 +
3800 const chartRegistry = []; 3965 const chartRegistry = [];
3801 const wordCloudRegistry = new Map(); 3966 const wordCloudRegistry = new Map();
3802 const STABLE_CHART_TYPES = ['line', 'bar']; 3967 const STABLE_CHART_TYPES = ['line', 'bar'];
@@ -4934,8 +5099,12 @@ document.addEventListener('DOMContentLoaded', () => { @@ -4934,8 +5099,12 @@ document.addEventListener('DOMContentLoaded', () => {
4934 }, 260); 5099 }, 260);
4935 const themeBtn = document.getElementById('theme-toggle'); 5100 const themeBtn = document.getElementById('theme-toggle');
4936 if (themeBtn) { 5101 if (themeBtn) {
4937 - themeBtn.addEventListener('click', () => {  
4938 - document.body.classList.toggle('dark-mode'); 5102 + themeBtn.addEventListener('change', (e) => {
  5103 + if (e.detail === 'dark') {
  5104 + document.body.classList.add('dark-mode');
  5105 + } else {
  5106 + document.body.classList.remove('dark-mode');
  5107 + }
4939 chartRegistry.forEach(applyChartTheme); 5108 chartRegistry.forEach(applyChartTheme);
4940 rerenderWordclouds(); 5109 rerenderWordclouds();
4941 }); 5110 });