298 lines
12 KiB
HTML
298 lines
12 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title><!-- SLOT: TITLE --></title>
|
||
|
|
|
||
|
|
<!-- SLOT: FONTS — Replace with chosen preset's Google Fonts link -->
|
||
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;600;700&display=swap" rel="stylesheet">
|
||
|
|
|
||
|
|
<style>
|
||
|
|
/* ===========================================
|
||
|
|
SAFE BASE — DO NOT DELETE ANY SECTION BELOW
|
||
|
|
This is the defensive foundation layer.
|
||
|
|
All content above this line can be customized.
|
||
|
|
=========================================== */
|
||
|
|
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
|
||
|
|
/* --- VIEWPORT FITTING (MANDATORY) --- */
|
||
|
|
html, body { height: 100%; overflow-x: hidden; }
|
||
|
|
html { scroll-snap-type: y mandatory; scroll-behavior: smooth; }
|
||
|
|
|
||
|
|
.slide {
|
||
|
|
width: 100vw;
|
||
|
|
height: 100vh;
|
||
|
|
height: 100dvh;
|
||
|
|
overflow: hidden;
|
||
|
|
scroll-snap-align: start;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.slide-content {
|
||
|
|
flex: 1;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
justify-content: center;
|
||
|
|
max-height: 100%;
|
||
|
|
overflow: hidden;
|
||
|
|
padding: var(--slide-padding);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- CJK TYPOGRAPHY --- */
|
||
|
|
:lang(zh), :lang(zh-CN), :lang(zh-Hans) {
|
||
|
|
line-height: 1.7;
|
||
|
|
word-break: break-all;
|
||
|
|
overflow-wrap: break-word;
|
||
|
|
}
|
||
|
|
h1:lang(zh-CN), h2:lang(zh-CN), h3:lang(zh-CN) {
|
||
|
|
line-height: 1.3;
|
||
|
|
letter-spacing: 0.02em;
|
||
|
|
}
|
||
|
|
.num, .kpi-value, [data-count], td.num, th.col-num {
|
||
|
|
font-variant-numeric: tabular-nums;
|
||
|
|
font-feature-settings: "tnum" 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- GLOBAL FINANCIAL COLORS (A-SHARE DEFAULT) --- */
|
||
|
|
:root {
|
||
|
|
--color-positive: #dc2626; /* Red = Up (A-share convention) */
|
||
|
|
--color-negative: #16a34a; /* Green = Down */
|
||
|
|
|
||
|
|
/* SLOT: THEME COLORS — Override below with chosen preset */
|
||
|
|
--bg-primary: #ffffff;
|
||
|
|
--text-primary: #1a1a2e;
|
||
|
|
--text-secondary: #5a5a7a;
|
||
|
|
--accent: #2563eb;
|
||
|
|
--accent-secondary: #60a5fa;
|
||
|
|
--border-color: rgba(0, 0, 0, 0.08);
|
||
|
|
|
||
|
|
/* SLOT: FONTS — Override with chosen preset */
|
||
|
|
--font-display: 'Noto Serif SC', serif;
|
||
|
|
--font-body: 'Noto Sans SC', sans-serif;
|
||
|
|
|
||
|
|
/* Responsive typography — ALL sizes use clamp() */
|
||
|
|
--title-size: clamp(1.5rem, 5vw, 4rem);
|
||
|
|
--h2-size: clamp(1.25rem, 3.5vw, 2.5rem);
|
||
|
|
--h3-size: clamp(1rem, 2.5vw, 1.75rem);
|
||
|
|
--body-size: clamp(0.75rem, 1.5vw, 1.125rem);
|
||
|
|
--small-size: clamp(0.65rem, 1vw, 0.875rem);
|
||
|
|
|
||
|
|
/* Responsive spacing — ALL spacing uses clamp() */
|
||
|
|
--slide-padding: clamp(1rem, 4vw, 4rem);
|
||
|
|
--content-gap: clamp(0.5rem, 2vw, 2rem);
|
||
|
|
--element-gap: clamp(0.25rem, 1vw, 1rem);
|
||
|
|
|
||
|
|
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||
|
|
--duration-normal: 0.6s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.positive, .up { color: var(--color-positive); }
|
||
|
|
.negative, .down { color: var(--color-negative); }
|
||
|
|
|
||
|
|
/* Trend arrows — CSS only, never emoji */
|
||
|
|
.trend-up::before {
|
||
|
|
content: '';
|
||
|
|
display: inline-block;
|
||
|
|
width: 0; height: 0;
|
||
|
|
border-left: 4px solid transparent;
|
||
|
|
border-right: 4px solid transparent;
|
||
|
|
border-bottom: 5px solid var(--color-positive);
|
||
|
|
margin-right: 0.25em;
|
||
|
|
vertical-align: middle;
|
||
|
|
}
|
||
|
|
.trend-down::before {
|
||
|
|
content: '';
|
||
|
|
display: inline-block;
|
||
|
|
width: 0; height: 0;
|
||
|
|
border-left: 4px solid transparent;
|
||
|
|
border-right: 4px solid transparent;
|
||
|
|
border-top: 5px solid var(--color-negative);
|
||
|
|
margin-right: 0.25em;
|
||
|
|
vertical-align: middle;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- RESPONSIVE CONSTRAINTS --- */
|
||
|
|
.card, .container, .content-box {
|
||
|
|
max-width: min(90vw, 1000px);
|
||
|
|
max-height: min(80vh, 700px);
|
||
|
|
}
|
||
|
|
img, .image-container {
|
||
|
|
max-width: 100%;
|
||
|
|
max-height: min(50vh, 400px);
|
||
|
|
object-fit: contain;
|
||
|
|
}
|
||
|
|
.chart-container {
|
||
|
|
width: min(90vw, 800px);
|
||
|
|
height: min(50vh, 400px);
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- RESPONSIVE BREAKPOINTS --- */
|
||
|
|
@media (max-height: 700px) {
|
||
|
|
:root {
|
||
|
|
--slide-padding: clamp(0.75rem, 3vw, 2rem);
|
||
|
|
--content-gap: clamp(0.4rem, 1.5vw, 1rem);
|
||
|
|
--title-size: clamp(1.25rem, 4.5vw, 2.5rem);
|
||
|
|
--h2-size: clamp(1rem, 3vw, 1.75rem);
|
||
|
|
}
|
||
|
|
.chart-container { height: min(40vh, 300px); }
|
||
|
|
}
|
||
|
|
@media (max-height: 600px) {
|
||
|
|
:root {
|
||
|
|
--slide-padding: clamp(0.5rem, 2.5vw, 1.5rem);
|
||
|
|
--content-gap: clamp(0.3rem, 1vw, 0.75rem);
|
||
|
|
--title-size: clamp(1.1rem, 4vw, 2rem);
|
||
|
|
--body-size: clamp(0.7rem, 1.2vw, 0.95rem);
|
||
|
|
}
|
||
|
|
.nav-dots, .keyboard-hint, .decorative { display: none; }
|
||
|
|
}
|
||
|
|
@media (max-height: 500px) {
|
||
|
|
:root {
|
||
|
|
--slide-padding: clamp(0.4rem, 2vw, 1rem);
|
||
|
|
--title-size: clamp(1rem, 3.5vw, 1.5rem);
|
||
|
|
--h2-size: clamp(0.9rem, 2.5vw, 1.25rem);
|
||
|
|
--body-size: clamp(0.65rem, 1vw, 0.85rem);
|
||
|
|
}
|
||
|
|
.chart-container { height: min(35vh, 250px); }
|
||
|
|
}
|
||
|
|
@media (max-width: 600px) {
|
||
|
|
:root { --title-size: clamp(1.25rem, 7vw, 2.5rem); }
|
||
|
|
.grid { grid-template-columns: 1fr; }
|
||
|
|
}
|
||
|
|
@media (prefers-reduced-motion: reduce) {
|
||
|
|
*, *::before, *::after {
|
||
|
|
animation-duration: 0.01ms !important;
|
||
|
|
transition-duration: 0.2s !important;
|
||
|
|
}
|
||
|
|
html { scroll-behavior: auto; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/* --- BASE ANIMATIONS --- */
|
||
|
|
.reveal {
|
||
|
|
opacity: 0;
|
||
|
|
transform: translateY(20px);
|
||
|
|
transition: opacity var(--duration-normal) var(--ease-out-expo),
|
||
|
|
transform var(--duration-normal) var(--ease-out-expo);
|
||
|
|
}
|
||
|
|
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
|
||
|
|
.reveal:nth-child(1) { transition-delay: 0.1s; }
|
||
|
|
.reveal:nth-child(2) { transition-delay: 0.2s; }
|
||
|
|
.reveal:nth-child(3) { transition-delay: 0.3s; }
|
||
|
|
.reveal:nth-child(4) { transition-delay: 0.4s; }
|
||
|
|
.reveal:nth-child(5) { transition-delay: 0.5s; }
|
||
|
|
|
||
|
|
/* ===========================================
|
||
|
|
SLOT: THEME STYLES
|
||
|
|
Add preset-specific styles below this line.
|
||
|
|
=========================================== */
|
||
|
|
|
||
|
|
|
||
|
|
/* ===========================================
|
||
|
|
SLOT: COMPONENT STYLES
|
||
|
|
Add KPI cards, tables, charts, etc. below.
|
||
|
|
=========================================== */
|
||
|
|
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<!-- SLOT: PROGRESS BAR (optional) -->
|
||
|
|
<div class="progress-bar" id="progressBar" style="position:fixed;top:0;left:0;height:3px;background:var(--accent);z-index:200;transition:width 0.3s ease;"></div>
|
||
|
|
|
||
|
|
<!-- ===========================================
|
||
|
|
SLOT: SLIDES
|
||
|
|
Add <section class="slide"> blocks here.
|
||
|
|
Each slide MUST have overflow: hidden (inherited from .slide class).
|
||
|
|
=========================================== -->
|
||
|
|
|
||
|
|
|
||
|
|
<!-- SLOT: ECHARTS INLINE (remove if no charts needed)
|
||
|
|
Read references/echarts.min.js and paste full content here.
|
||
|
|
DO NOT use <script src="..."> CDN — file must work offline. -->
|
||
|
|
<!-- <script>/* ECharts v5 - inline */ ...paste echarts.min.js content here... </script> -->
|
||
|
|
|
||
|
|
<script>
|
||
|
|
/* ===========================================
|
||
|
|
SAFE BASE JS — DO NOT DELETE
|
||
|
|
=========================================== */
|
||
|
|
|
||
|
|
/* Chinese number formatting */
|
||
|
|
function formatCN(num, decimals) {
|
||
|
|
if (decimals === undefined) decimals = 1;
|
||
|
|
if (num === null || num === undefined) return '--';
|
||
|
|
var abs = Math.abs(num);
|
||
|
|
var sign = num < 0 ? '-' : '';
|
||
|
|
if (abs >= 1e8) return sign + (abs / 1e8).toFixed(decimals).replace(/\.0$/, '') + '\u4ebf';
|
||
|
|
if (abs >= 1e4) return sign + (abs / 1e4).toFixed(decimals).replace(/\.0$/, '') + '\u4e07';
|
||
|
|
return sign + abs.toLocaleString('zh-CN');
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatPct(num, decimals) {
|
||
|
|
if (decimals === undefined) decimals = 2;
|
||
|
|
if (num === null || num === undefined) return '--';
|
||
|
|
return (num * 100).toFixed(decimals) + '%';
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Slide controller */
|
||
|
|
class SlidePresentation {
|
||
|
|
constructor() {
|
||
|
|
this.slides = document.querySelectorAll('.slide');
|
||
|
|
this.currentSlide = 0;
|
||
|
|
this.progressBar = document.getElementById('progressBar');
|
||
|
|
this.setupObserver();
|
||
|
|
this.setupKeyboard();
|
||
|
|
this.setupTouch();
|
||
|
|
}
|
||
|
|
setupObserver() {
|
||
|
|
var self = this;
|
||
|
|
var obs = new IntersectionObserver(function(entries) {
|
||
|
|
entries.forEach(function(e) {
|
||
|
|
if (e.isIntersecting) {
|
||
|
|
e.target.classList.add('visible');
|
||
|
|
self.currentSlide = Array.from(self.slides).indexOf(e.target);
|
||
|
|
if (self.progressBar) self.progressBar.style.width = ((self.currentSlide + 1) / self.slides.length * 100) + '%';
|
||
|
|
/* Init ECharts if present */
|
||
|
|
e.target.querySelectorAll('.chart-container').forEach(function(el) {
|
||
|
|
if (!el._echartInstance && typeof echarts !== 'undefined' && el.dataset.init) {
|
||
|
|
try { var fn = new Function(el.dataset.init); fn.call(el); } catch(err) {}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}, { threshold: 0.3 });
|
||
|
|
this.slides.forEach(function(s) { obs.observe(s); });
|
||
|
|
}
|
||
|
|
setupKeyboard() {
|
||
|
|
var self = this;
|
||
|
|
document.addEventListener('keydown', function(e) {
|
||
|
|
if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); self.go(self.currentSlide + 1); }
|
||
|
|
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { e.preventDefault(); self.go(self.currentSlide - 1); }
|
||
|
|
});
|
||
|
|
}
|
||
|
|
setupTouch() {
|
||
|
|
var self = this, startY = 0;
|
||
|
|
document.addEventListener('touchstart', function(e) { startY = e.touches[0].clientY; });
|
||
|
|
document.addEventListener('touchend', function(e) {
|
||
|
|
var d = startY - e.changedTouches[0].clientY;
|
||
|
|
if (Math.abs(d) > 50) self.go(self.currentSlide + (d > 0 ? 1 : -1));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
go(i) { if (i >= 0 && i < this.slides.length) this.slides[i].scrollIntoView({ behavior: 'smooth' }); }
|
||
|
|
}
|
||
|
|
|
||
|
|
document.addEventListener('DOMContentLoaded', function() { new SlidePresentation(); });
|
||
|
|
|
||
|
|
/* ===========================================
|
||
|
|
SLOT: CUSTOM JS
|
||
|
|
Add chart init functions, counters, etc.
|
||
|
|
=========================================== */
|
||
|
|
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|