@aidget-community/us-stock-watch 0.0.0-stage → 0.1.0
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/agents.md +34 -0
- package/assets/screenshot.png +0 -0
- package/config.json +11 -0
- package/index.html +282 -0
- package/package.json +23 -4
- package/style.css +517 -0
- package/README.md +0 -3
package/agents.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 股票行情卡片
|
|
2
|
+
|
|
3
|
+
## 定位
|
|
4
|
+
|
|
5
|
+
- 这是一个数据驱动的分时行情卡片,可以展示任意一组东方财富 `trends2` 接口支持的股票;股票数量和代码不是组件能力上限。
|
|
6
|
+
- 当前 `0.1.0` 发布版本只是预置了 AAPL、GOOGL、MSFT、AMZN、TSLA 五只美股科技股,标题和 `US` 标签也按这个场景设置。
|
|
7
|
+
- 更换为非美股或混合市场时,应同步调整标题、`US` 市场标签、对应 `secid` 和股票代码;视觉结构可以继续复用。
|
|
8
|
+
|
|
9
|
+
## 修改股票列表
|
|
10
|
+
|
|
11
|
+
- `index.html` 中的 `STOCKS` 是唯一列表来源。每项必须包含 `symbol`、`company` 和东方财富市场限定代码 `secid`。
|
|
12
|
+
- `secid` 不能固定假设为 `105`;例如纳斯达克使用 `105.AAPL`,其他市场必须以接口返回或官方代码为准。
|
|
13
|
+
- `company` 是本地展示名,不依赖上游名称,便于保持中文标题稳定。
|
|
14
|
+
- 当前样式按五行校准。改成其他数量时,必须同步调整基础样式、宽度断点和低高度断点中的 `grid-template-rows`,重新检查 400×350 及 360×320,并更新 `config.json` 尺寸与真实截图。
|
|
15
|
+
- 修改预置股票后,应同步更新包标题、截图、版本和 registry,不能把旧截图或旧版本号复用于不同内容。
|
|
16
|
+
|
|
17
|
+
## 区域与布局
|
|
18
|
+
|
|
19
|
+
- `stocks`:行情行列表,每行包含展示名称、市场标签与代码、日内走势、最新价和涨跌幅。
|
|
20
|
+
- 左上显示美东时间,右上显示最近一次本地更新时间。窗口位置和尺寸归用户控制;内容更新不得移动窗口。
|
|
21
|
+
- 走势图应占据中间列的主要宽度;红涨、绿跌、灰色平盘。
|
|
22
|
+
|
|
23
|
+
## 数据与刷新
|
|
24
|
+
|
|
25
|
+
- 数据源:`https://push2.eastmoney.com/api/qt/stock/trends2/get`,这是无需密钥的非官方公开接口。
|
|
26
|
+
- 以 `data.preClose` 为昨收,以 `data.trends` 的收盘价字段生成曲线并计算涨跌幅;闭市时展示最近交易日。
|
|
27
|
+
- 无论开闭市均固定每 5 分钟刷新。请求失败时清空行情并显示明确错误,禁止伪造数据、切换备用源或缓存旧行情。
|
|
28
|
+
- 价格使用接口 `decimal` 精度,涨跌幅保留两位小数。
|
|
29
|
+
|
|
30
|
+
## 权限与实现约束
|
|
31
|
+
|
|
32
|
+
- 必须保留 `script` 权限和精确网络白名单 `https://push2.eastmoney.com`,不得增加 `storage` 或 `location`。
|
|
33
|
+
- 只使用本地 HTML、CSS 和原生 SVG;禁止远程脚本、第三方图表库、远程字体和远程图片。
|
|
34
|
+
- 首次渲染或错误状态完成后调用 `window.aidget.ready()`;后续刷新用单个 `setTimeout`,不得产生重叠请求。
|
|
Binary file
|
package/config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "us-stock-watch",
|
|
3
|
+
"title": "美股 · 五大科技股",
|
|
4
|
+
"logo": null,
|
|
5
|
+
"screenshots": ["assets/screenshot.png"],
|
|
6
|
+
"platforms": ["macos", "windows", "linux"],
|
|
7
|
+
"size": { "width": 400, "height": 350 },
|
|
8
|
+
"dragMode": "all",
|
|
9
|
+
"permissions": ["script"],
|
|
10
|
+
"network": ["https://push2.eastmoney.com"]
|
|
11
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="aidget-surface" content="none" />
|
|
7
|
+
<title>美股 · 五大科技股</title>
|
|
8
|
+
<link rel="stylesheet" href="style.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main class="market-card" aria-label="美股五大科技股行情">
|
|
12
|
+
<header class="market-header">
|
|
13
|
+
<div class="market-kicker">
|
|
14
|
+
<span class="market-pulse" aria-hidden="true"></span>
|
|
15
|
+
<span>美东时间</span>
|
|
16
|
+
<time id="market-clock" class="market-clock">--:--</time>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="market-status">
|
|
19
|
+
<span id="status-text">正在获取行情…</span>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<section
|
|
24
|
+
id="stocks"
|
|
25
|
+
class="stock-list"
|
|
26
|
+
data-aidget-region="stocks"
|
|
27
|
+
aria-live="polite"
|
|
28
|
+
aria-busy="true"
|
|
29
|
+
></section>
|
|
30
|
+
</main>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
const STOCKS = [
|
|
34
|
+
{ symbol: 'AAPL', company: '苹果', secid: '105.AAPL' },
|
|
35
|
+
{ symbol: 'GOOGL', company: '谷歌-A', secid: '105.GOOGL' },
|
|
36
|
+
{ symbol: 'MSFT', company: '微软', secid: '105.MSFT' },
|
|
37
|
+
{ symbol: 'AMZN', company: '\u4e9a\u9a6c\u900a', secid: '105.AMZN' },
|
|
38
|
+
{ symbol: 'TSLA', company: '特斯拉', secid: '105.TSLA' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const API_ORIGIN = 'https://push2.eastmoney.com';
|
|
42
|
+
const REFRESH_MS = 5 * 60 * 1000;
|
|
43
|
+
const list = document.querySelector('#stocks');
|
|
44
|
+
const statusText = document.querySelector('#status-text');
|
|
45
|
+
const marketClock = document.querySelector('#market-clock');
|
|
46
|
+
|
|
47
|
+
let refreshTimer = 0;
|
|
48
|
+
let loading = false;
|
|
49
|
+
let initialized = false;
|
|
50
|
+
let hasRendered = false;
|
|
51
|
+
|
|
52
|
+
function updateClock() {
|
|
53
|
+
const time = new Intl.DateTimeFormat('zh-CN', {
|
|
54
|
+
timeZone: 'America/New_York',
|
|
55
|
+
hour: '2-digit',
|
|
56
|
+
minute: '2-digit',
|
|
57
|
+
hourCycle: 'h23',
|
|
58
|
+
}).format(new Date());
|
|
59
|
+
marketClock.textContent = time;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setStatus(state, text) {
|
|
63
|
+
statusText.dataset.state = state;
|
|
64
|
+
statusText.textContent = text;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderSkeleton() {
|
|
68
|
+
if (hasRendered) return;
|
|
69
|
+
list.innerHTML = STOCKS.map(
|
|
70
|
+
() => `
|
|
71
|
+
<div class="stock-row stock-skeleton" aria-hidden="true">
|
|
72
|
+
<div class="identity">
|
|
73
|
+
<span class="skeleton-block skeleton-name"></span>
|
|
74
|
+
<span class="skeleton-block skeleton-ticker"></span>
|
|
75
|
+
</div>
|
|
76
|
+
<span class="skeleton-block skeleton-chart"></span>
|
|
77
|
+
<span class="skeleton-block skeleton-price"></span>
|
|
78
|
+
<span class="skeleton-block skeleton-change"></span>
|
|
79
|
+
</div>
|
|
80
|
+
`,
|
|
81
|
+
).join('');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function fetchStock(stock) {
|
|
85
|
+
const url = new URL('/api/qt/stock/trends2/get', API_ORIGIN);
|
|
86
|
+
url.search = new URLSearchParams({
|
|
87
|
+
secid: stock.secid,
|
|
88
|
+
fields1: 'f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13',
|
|
89
|
+
fields2: 'f51,f52,f53,f54,f55,f56,f57,f58',
|
|
90
|
+
ndays: '1',
|
|
91
|
+
iscr: '0',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const response = await fetch(url, { cache: 'no-store' });
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`${stock.symbol} 请求失败:HTTP ${response.status}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const payload = await response.json();
|
|
100
|
+
if (payload.rc !== 0 || !payload.data) {
|
|
101
|
+
throw new Error(`${stock.symbol} 返回了空行情`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const rows = payload.data.trends;
|
|
105
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
106
|
+
throw new Error(`${stock.symbol} 缺少分时数据`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const closes = rows
|
|
110
|
+
.map((row) => Number(String(row).split(',')[2]))
|
|
111
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
112
|
+
const previousClose = Number(payload.data.preClose);
|
|
113
|
+
const latest = closes[closes.length - 1];
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
closes.length < 2 ||
|
|
117
|
+
!Number.isFinite(previousClose) ||
|
|
118
|
+
previousClose <= 0 ||
|
|
119
|
+
!Number.isFinite(latest)
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(`${stock.symbol} 行情字段不完整`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let change = ((latest - previousClose) / previousClose) * 100;
|
|
125
|
+
if (Math.abs(change) < 0.005) change = 0;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...stock,
|
|
129
|
+
decimal: Number.isInteger(payload.data.decimal) ? payload.data.decimal : 3,
|
|
130
|
+
previousClose,
|
|
131
|
+
latest,
|
|
132
|
+
change,
|
|
133
|
+
closes,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sparkline(stock, index) {
|
|
138
|
+
const width = 74;
|
|
139
|
+
const height = 46;
|
|
140
|
+
const inset = 3;
|
|
141
|
+
const values = [...stock.closes, stock.previousClose];
|
|
142
|
+
const min = Math.min(...values);
|
|
143
|
+
const max = Math.max(...values);
|
|
144
|
+
const range = max - min || 1;
|
|
145
|
+
const x = (position) => (position / (stock.closes.length - 1)) * width;
|
|
146
|
+
const y = (value) => inset + ((max - value) / range) * (height - inset * 2);
|
|
147
|
+
const points = stock.closes.map(
|
|
148
|
+
(value, position) => `${x(position).toFixed(2)},${y(value).toFixed(2)}`,
|
|
149
|
+
);
|
|
150
|
+
const line = `M${points.join(' L')}`;
|
|
151
|
+
const area = `M0,${height} L${points.join(' L')} L${width},${height} Z`;
|
|
152
|
+
const previousY = y(stock.previousClose).toFixed(2);
|
|
153
|
+
const last = points[points.length - 1].split(',');
|
|
154
|
+
const gradient = `spark-fill-${index}`;
|
|
155
|
+
const direction = stock.change > 0 ? '上涨' : stock.change < 0 ? '下跌' : '平盘';
|
|
156
|
+
|
|
157
|
+
return `
|
|
158
|
+
<svg
|
|
159
|
+
class="sparkline"
|
|
160
|
+
viewBox="0 0 ${width} ${height}"
|
|
161
|
+
preserveAspectRatio="none"
|
|
162
|
+
role="img"
|
|
163
|
+
aria-label="${stock.symbol} 日内走势,${direction} ${Math.abs(stock.change).toFixed(2)}%"
|
|
164
|
+
>
|
|
165
|
+
<defs>
|
|
166
|
+
<linearGradient id="${gradient}" x1="0" y1="0" x2="0" y2="1">
|
|
167
|
+
<stop offset="0%" stop-color="currentColor" stop-opacity="0.28"></stop>
|
|
168
|
+
<stop offset="100%" stop-color="currentColor" stop-opacity="0"></stop>
|
|
169
|
+
</linearGradient>
|
|
170
|
+
</defs>
|
|
171
|
+
<line
|
|
172
|
+
class="previous-line"
|
|
173
|
+
x1="0"
|
|
174
|
+
y1="${previousY}"
|
|
175
|
+
x2="${width}"
|
|
176
|
+
y2="${previousY}"
|
|
177
|
+
></line>
|
|
178
|
+
<path class="spark-area" d="${area}" fill="url(#${gradient})"></path>
|
|
179
|
+
<path class="spark-path" d="${line}"></path>
|
|
180
|
+
<circle class="spark-dot" cx="${last[0]}" cy="${last[1]}" r="2.2"></circle>
|
|
181
|
+
</svg>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stockRow(stock, index) {
|
|
186
|
+
const direction = stock.change > 0 ? 'is-up' : stock.change < 0 ? 'is-down' : 'is-flat';
|
|
187
|
+
const price = stock.latest.toFixed(stock.decimal);
|
|
188
|
+
const percentage = `${stock.change > 0 ? '+' : ''}${stock.change.toFixed(2)}%`;
|
|
189
|
+
|
|
190
|
+
return `
|
|
191
|
+
<article class="stock-row ${direction}" data-symbol="${stock.symbol}">
|
|
192
|
+
<div class="identity">
|
|
193
|
+
<h2 class="company-name">${stock.company}</h2>
|
|
194
|
+
<div class="ticker-line">
|
|
195
|
+
<span class="market-chip">US</span>
|
|
196
|
+
<span class="ticker">${stock.symbol}</span>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="sparkline-wrap">${sparkline(stock, index)}</div>
|
|
200
|
+
<span class="price">${price}</span>
|
|
201
|
+
<span class="change-pill">${percentage}</span>
|
|
202
|
+
</article>
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderStocks(stocks) {
|
|
207
|
+
const animate = !hasRendered;
|
|
208
|
+
hasRendered = true;
|
|
209
|
+
list.setAttribute('aria-busy', 'false');
|
|
210
|
+
if (animate) list.classList.add('is-entering');
|
|
211
|
+
list.innerHTML = stocks.map(stockRow).join('');
|
|
212
|
+
if (animate) {
|
|
213
|
+
window.setTimeout(() => list.classList.remove('is-entering'), 900);
|
|
214
|
+
}
|
|
215
|
+
setStatus(
|
|
216
|
+
'ok',
|
|
217
|
+
`更新于 ${new Intl.DateTimeFormat('zh-CN', {
|
|
218
|
+
hour: '2-digit',
|
|
219
|
+
minute: '2-digit',
|
|
220
|
+
hour12: false,
|
|
221
|
+
}).format(new Date())}`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function escapeHtml(value) {
|
|
226
|
+
return String(value).replace(
|
|
227
|
+
/[&<>"']/g,
|
|
228
|
+
(character) =>
|
|
229
|
+
({
|
|
230
|
+
'&': '&',
|
|
231
|
+
'<': '<',
|
|
232
|
+
'>': '>',
|
|
233
|
+
'"': '"',
|
|
234
|
+
"'": ''',
|
|
235
|
+
})[character],
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function renderError(error) {
|
|
240
|
+
hasRendered = true;
|
|
241
|
+
list.setAttribute('aria-busy', 'false');
|
|
242
|
+
list.innerHTML = `
|
|
243
|
+
<div class="error-state" role="alert">
|
|
244
|
+
<span class="error-mark" aria-hidden="true">!</span>
|
|
245
|
+
<h2>行情加载失败</h2>
|
|
246
|
+
<p data-aidget-no-drag>${escapeHtml(error instanceof Error ? error.message : String(error))}</p>
|
|
247
|
+
<button id="retry" class="retry-button" type="button">重新加载</button>
|
|
248
|
+
</div>
|
|
249
|
+
`;
|
|
250
|
+
document.querySelector('#retry').addEventListener('click', refresh);
|
|
251
|
+
setStatus('error', '数据不可用');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function refresh() {
|
|
255
|
+
if (loading) return;
|
|
256
|
+
loading = true;
|
|
257
|
+
window.clearTimeout(refreshTimer);
|
|
258
|
+
list.setAttribute('aria-busy', 'true');
|
|
259
|
+
setStatus('loading', '正在更新…');
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const stocks = await Promise.all(STOCKS.map(fetchStock));
|
|
263
|
+
renderStocks(stocks);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
renderError(error);
|
|
266
|
+
} finally {
|
|
267
|
+
loading = false;
|
|
268
|
+
if (!initialized) {
|
|
269
|
+
initialized = true;
|
|
270
|
+
window.aidget.ready();
|
|
271
|
+
}
|
|
272
|
+
refreshTimer = window.setTimeout(refresh, REFRESH_MS);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
updateClock();
|
|
277
|
+
window.setInterval(updateClock, 30 * 1000);
|
|
278
|
+
renderSkeleton();
|
|
279
|
+
refresh();
|
|
280
|
+
</script>
|
|
281
|
+
</body>
|
|
282
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aidget-community/us-stock-watch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "美股五大科技股日内行情、价格与涨跌幅卡片。",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "micooz",
|
|
8
|
+
"url": "https://github.com/micooz"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/aidget-ai/widgets.git",
|
|
13
|
+
"directory": "packages/us-stock-watch"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"config.json",
|
|
17
|
+
"index.html",
|
|
18
|
+
"style.css",
|
|
19
|
+
"agents.md",
|
|
20
|
+
"assets"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/style.css
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: dark;
|
|
3
|
+
--surface-top: #071a2a;
|
|
4
|
+
--surface-bottom: #04111e;
|
|
5
|
+
--text: #f6fbff;
|
|
6
|
+
--muted: #7794a8;
|
|
7
|
+
--faint: #4d6c81;
|
|
8
|
+
--line: rgba(134, 184, 207, 0.11);
|
|
9
|
+
--up: #ff4f6f;
|
|
10
|
+
--down: #62df8d;
|
|
11
|
+
--flat: #8ea6b5;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html,
|
|
19
|
+
body {
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
margin: 0;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
background: transparent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
container-type: inline-size;
|
|
29
|
+
font-family:
|
|
30
|
+
'Avenir Next', 'SF Pro Display', 'PingFang SC', 'Hiragino Sans GB', 'Segoe UI Variable',
|
|
31
|
+
'Microsoft YaHei UI', 'Noto Sans CJK SC', sans-serif;
|
|
32
|
+
-webkit-font-smoothing: antialiased;
|
|
33
|
+
text-rendering: geometricPrecision;
|
|
34
|
+
user-select: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.market-card {
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
padding: 14px 22px 8px;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
border: 1px solid var(--line);
|
|
46
|
+
border-radius: 18px;
|
|
47
|
+
background:
|
|
48
|
+
radial-gradient(circle at 105% -5%, rgba(25, 81, 112, 0.26), transparent 37%),
|
|
49
|
+
linear-gradient(155deg, var(--surface-top) 0%, #061624 57%, var(--surface-bottom) 100%);
|
|
50
|
+
box-shadow:
|
|
51
|
+
inset 0 1px 0 rgba(207, 239, 251, 0.08),
|
|
52
|
+
inset 0 -18px 52px rgba(2, 9, 16, 0.36),
|
|
53
|
+
0 18px 48px rgba(0, 0, 0, 0.38);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.market-card::after {
|
|
57
|
+
position: absolute;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
content: '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.market-card::after {
|
|
63
|
+
top: 0;
|
|
64
|
+
left: 19%;
|
|
65
|
+
width: 62%;
|
|
66
|
+
height: 1px;
|
|
67
|
+
background: linear-gradient(90deg, transparent, rgba(164, 223, 244, 0.24), transparent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.market-header,
|
|
71
|
+
.stock-list {
|
|
72
|
+
position: relative;
|
|
73
|
+
z-index: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.market-header {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex: 0 0 auto;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: space-between;
|
|
81
|
+
min-height: 16px;
|
|
82
|
+
margin-bottom: 4px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.market-kicker,
|
|
86
|
+
.market-status {
|
|
87
|
+
color: var(--faint);
|
|
88
|
+
font-size: 10px;
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
letter-spacing: 0.15em;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.market-kicker {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 7px;
|
|
97
|
+
letter-spacing: 0.08em;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.market-status {
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
gap: 10px;
|
|
104
|
+
letter-spacing: 0.08em;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.market-pulse {
|
|
108
|
+
width: 6px;
|
|
109
|
+
height: 6px;
|
|
110
|
+
border-radius: 50%;
|
|
111
|
+
background: #7be7ec;
|
|
112
|
+
box-shadow:
|
|
113
|
+
0 0 0 4px rgba(82, 211, 221, 0.08),
|
|
114
|
+
0 0 12px rgba(82, 211, 221, 0.55);
|
|
115
|
+
animation: pulse 2.8s ease-in-out infinite;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.market-clock {
|
|
119
|
+
font-variant-numeric: tabular-nums;
|
|
120
|
+
letter-spacing: 0.05em;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.stock-list {
|
|
124
|
+
display: grid;
|
|
125
|
+
flex: 1 1 auto;
|
|
126
|
+
grid-template-rows: repeat(5, 64px);
|
|
127
|
+
gap: 6px;
|
|
128
|
+
align-content: center;
|
|
129
|
+
min-height: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.stock-row {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: minmax(90px, 1fr) 106px 92px 72px;
|
|
135
|
+
gap: 10px;
|
|
136
|
+
align-items: center;
|
|
137
|
+
min-width: 0;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.stock-row.is-up {
|
|
142
|
+
--accent: var(--up);
|
|
143
|
+
--pill-ink: #fff7f8;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stock-row.is-down {
|
|
147
|
+
--accent: var(--down);
|
|
148
|
+
--pill-ink: #04160c;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.stock-row.is-flat {
|
|
152
|
+
--accent: var(--flat);
|
|
153
|
+
--pill-ink: #07131b;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.identity {
|
|
157
|
+
min-width: 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.company-name {
|
|
161
|
+
margin: 0;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
color: var(--text);
|
|
164
|
+
font-size: 17px;
|
|
165
|
+
font-weight: 720;
|
|
166
|
+
letter-spacing: 0.025em;
|
|
167
|
+
line-height: 1.12;
|
|
168
|
+
text-overflow: ellipsis;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.ticker-line {
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 7px;
|
|
176
|
+
margin-top: 4px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.market-chip {
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
height: 15px;
|
|
184
|
+
padding: 0 4px;
|
|
185
|
+
border: 1px solid rgba(255, 133, 150, 0.15);
|
|
186
|
+
border-radius: 3px;
|
|
187
|
+
background: #5c1f29;
|
|
188
|
+
color: #ffe9ed;
|
|
189
|
+
font-size: 9px;
|
|
190
|
+
font-weight: 800;
|
|
191
|
+
letter-spacing: 0.04em;
|
|
192
|
+
line-height: 1;
|
|
193
|
+
vertical-align: middle;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ticker {
|
|
197
|
+
color: #849daf;
|
|
198
|
+
font-size: 11px;
|
|
199
|
+
font-variant-numeric: tabular-nums;
|
|
200
|
+
font-weight: 700;
|
|
201
|
+
letter-spacing: 0.09em;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.sparkline-wrap {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
width: 106px;
|
|
209
|
+
height: 42px;
|
|
210
|
+
color: var(--accent);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.sparkline {
|
|
214
|
+
display: block;
|
|
215
|
+
width: 106px;
|
|
216
|
+
height: 42px;
|
|
217
|
+
overflow: visible;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.previous-line {
|
|
221
|
+
stroke: rgba(173, 193, 204, 0.38);
|
|
222
|
+
stroke-width: 1;
|
|
223
|
+
stroke-dasharray: 2 2;
|
|
224
|
+
vector-effect: non-scaling-stroke;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.spark-area {
|
|
228
|
+
pointer-events: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.spark-path {
|
|
232
|
+
fill: none;
|
|
233
|
+
stroke: currentColor;
|
|
234
|
+
stroke-linecap: round;
|
|
235
|
+
stroke-linejoin: round;
|
|
236
|
+
stroke-width: 1.45;
|
|
237
|
+
vector-effect: non-scaling-stroke;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.spark-dot {
|
|
241
|
+
fill: currentColor;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.price {
|
|
245
|
+
color: #f7fbff;
|
|
246
|
+
font-family: 'DIN Alternate', 'SF Mono', 'Avenir Next', 'Segoe UI Variable', monospace;
|
|
247
|
+
font-size: 16px;
|
|
248
|
+
font-variant-numeric: tabular-nums;
|
|
249
|
+
font-weight: 650;
|
|
250
|
+
letter-spacing: 0.015em;
|
|
251
|
+
line-height: 1;
|
|
252
|
+
text-align: right;
|
|
253
|
+
white-space: nowrap;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.change-pill {
|
|
257
|
+
display: inline-flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
justify-content: center;
|
|
260
|
+
min-width: 74px;
|
|
261
|
+
height: 28px;
|
|
262
|
+
padding: 0 8px;
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
background: var(--accent);
|
|
265
|
+
color: var(--pill-ink);
|
|
266
|
+
font-family: 'DIN Alternate', 'Avenir Next', 'Segoe UI Variable', sans-serif;
|
|
267
|
+
font-size: 15px;
|
|
268
|
+
font-variant-numeric: tabular-nums;
|
|
269
|
+
font-weight: 760;
|
|
270
|
+
letter-spacing: 0.01em;
|
|
271
|
+
line-height: 1;
|
|
272
|
+
white-space: nowrap;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#status-text {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 6px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#status-text::before {
|
|
282
|
+
width: 4px;
|
|
283
|
+
height: 4px;
|
|
284
|
+
border-radius: 50%;
|
|
285
|
+
background: #557487;
|
|
286
|
+
content: '';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#status-text[data-state='ok']::before {
|
|
290
|
+
background: #6ed9a0;
|
|
291
|
+
box-shadow: 0 0 8px rgba(110, 217, 160, 0.45);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#status-text[data-state='error']::before {
|
|
295
|
+
background: var(--up);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.is-entering .stock-row {
|
|
299
|
+
opacity: 0;
|
|
300
|
+
transform: translateY(8px);
|
|
301
|
+
animation: row-in 420ms cubic-bezier(0.2, 0.75, 0.2, 1) forwards;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.is-entering .stock-row:nth-child(2) {
|
|
305
|
+
animation-delay: 45ms;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.is-entering .stock-row:nth-child(3) {
|
|
309
|
+
animation-delay: 90ms;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.is-entering .stock-row:nth-child(4) {
|
|
313
|
+
animation-delay: 135ms;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.is-entering .stock-row:nth-child(5) {
|
|
317
|
+
animation-delay: 180ms;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.stock-skeleton {
|
|
321
|
+
grid-template-columns: minmax(90px, 1fr) 106px 92px 72px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.skeleton-block {
|
|
325
|
+
display: block;
|
|
326
|
+
border-radius: 6px;
|
|
327
|
+
background: linear-gradient(
|
|
328
|
+
100deg,
|
|
329
|
+
rgba(104, 142, 164, 0.08) 20%,
|
|
330
|
+
rgba(125, 177, 199, 0.16) 42%,
|
|
331
|
+
rgba(104, 142, 164, 0.08) 65%
|
|
332
|
+
);
|
|
333
|
+
background-size: 220% 100%;
|
|
334
|
+
animation: shimmer 1.5s linear infinite;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.skeleton-name {
|
|
338
|
+
width: 62px;
|
|
339
|
+
height: 18px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.skeleton-ticker {
|
|
343
|
+
width: 74px;
|
|
344
|
+
height: 11px;
|
|
345
|
+
margin-top: 7px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.skeleton-chart {
|
|
349
|
+
width: 92px;
|
|
350
|
+
height: 32px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.skeleton-price {
|
|
354
|
+
justify-self: end;
|
|
355
|
+
width: 76px;
|
|
356
|
+
height: 17px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.skeleton-change {
|
|
360
|
+
justify-self: end;
|
|
361
|
+
width: 72px;
|
|
362
|
+
height: 28px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.error-state {
|
|
366
|
+
display: flex;
|
|
367
|
+
grid-row: 1 / -1;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
align-items: center;
|
|
370
|
+
justify-content: center;
|
|
371
|
+
padding: 24px;
|
|
372
|
+
text-align: center;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.error-mark {
|
|
376
|
+
display: grid;
|
|
377
|
+
width: 34px;
|
|
378
|
+
height: 34px;
|
|
379
|
+
place-items: center;
|
|
380
|
+
margin-bottom: 13px;
|
|
381
|
+
border: 1px solid rgba(255, 79, 111, 0.38);
|
|
382
|
+
border-radius: 50%;
|
|
383
|
+
background: rgba(255, 79, 111, 0.1);
|
|
384
|
+
color: var(--up);
|
|
385
|
+
font-family: 'DIN Alternate', sans-serif;
|
|
386
|
+
font-size: 20px;
|
|
387
|
+
font-weight: 800;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.error-state h2 {
|
|
391
|
+
margin: 0;
|
|
392
|
+
color: var(--text);
|
|
393
|
+
font-size: 18px;
|
|
394
|
+
line-height: 1.2;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.error-state p {
|
|
398
|
+
max-width: 320px;
|
|
399
|
+
margin: 8px 0 16px;
|
|
400
|
+
color: var(--muted);
|
|
401
|
+
font-size: 12px;
|
|
402
|
+
line-height: 1.55;
|
|
403
|
+
user-select: text;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.retry-button {
|
|
407
|
+
padding: 7px 14px;
|
|
408
|
+
border: 1px solid rgba(122, 190, 215, 0.25);
|
|
409
|
+
border-radius: 8px;
|
|
410
|
+
background: rgba(87, 151, 176, 0.12);
|
|
411
|
+
color: #d8edf5;
|
|
412
|
+
font:
|
|
413
|
+
700 12px/1 'Avenir Next',
|
|
414
|
+
'PingFang SC',
|
|
415
|
+
sans-serif;
|
|
416
|
+
cursor: pointer;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.retry-button:hover {
|
|
420
|
+
background: rgba(87, 151, 176, 0.2);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.retry-button:focus-visible {
|
|
424
|
+
outline: 2px solid #7be7ec;
|
|
425
|
+
outline-offset: 2px;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
@container (max-width: 420px) {
|
|
429
|
+
.market-card {
|
|
430
|
+
padding: 12px 17px 7px;
|
|
431
|
+
border-radius: 15px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.company-name {
|
|
435
|
+
font-size: 14px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.stock-list {
|
|
439
|
+
grid-template-rows: repeat(5, 58px);
|
|
440
|
+
gap: 5px;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.stock-row,
|
|
444
|
+
.stock-skeleton {
|
|
445
|
+
grid-template-columns: minmax(86px, 1fr) 120px 74px 64px;
|
|
446
|
+
gap: 5px;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.sparkline-wrap,
|
|
450
|
+
.sparkline {
|
|
451
|
+
width: 120px;
|
|
452
|
+
height: 36px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.price {
|
|
456
|
+
font-size: 13px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.change-pill {
|
|
460
|
+
min-width: 64px;
|
|
461
|
+
height: 24px;
|
|
462
|
+
padding: 0 5px;
|
|
463
|
+
border-radius: 7px;
|
|
464
|
+
font-size: 12px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.ticker {
|
|
468
|
+
font-size: 10px;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
@media (max-height: 330px) {
|
|
473
|
+
.stock-list {
|
|
474
|
+
grid-template-rows: repeat(5, 44px);
|
|
475
|
+
gap: 3px;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.sparkline-wrap,
|
|
479
|
+
.sparkline {
|
|
480
|
+
height: 30px;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@keyframes row-in {
|
|
485
|
+
to {
|
|
486
|
+
opacity: 1;
|
|
487
|
+
transform: translateY(0);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
@keyframes shimmer {
|
|
492
|
+
to {
|
|
493
|
+
background-position: -220% 0;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@keyframes pulse {
|
|
498
|
+
0%,
|
|
499
|
+
100% {
|
|
500
|
+
opacity: 0.62;
|
|
501
|
+
transform: scale(0.9);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
50% {
|
|
505
|
+
opacity: 1;
|
|
506
|
+
transform: scale(1.08);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
@media (prefers-reduced-motion: reduce) {
|
|
511
|
+
*,
|
|
512
|
+
*::before,
|
|
513
|
+
*::after {
|
|
514
|
+
animation-duration: 0.01ms !important;
|
|
515
|
+
animation-iteration-count: 1 !important;
|
|
516
|
+
}
|
|
517
|
+
}
|
package/README.md
DELETED