Spaces:
Sleeping
Sleeping
File size: 141,967 Bytes
0a5d32e 3dc69f8 0a5d32e b1b0036 0a5d32e 3dc69f8 0a5d32e f5990cb 059e85b f5990cb 059e85b b1b0036 3dc69f8 f5990cb b1b0036 3dc69f8 f5990cb 3dc69f8 b1b0036 0a5d32e f5990cb 3dc69f8 0a5d32e 3dc69f8 0a5d32e 3dc69f8 0a5d32e f5990cb 3dc69f8 0a5d32e 3dc69f8 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e e02ce54 0a5d32e f5990cb 059e85b f5990cb 3dc69f8 0a5d32e f1121d3 f5990cb f1121d3 f5990cb 1b38599 f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb f1c47a3 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f1c47a3 0a5d32e f5990cb 0a5d32e f5990cb f1c47a3 0a5d32e f5990cb f1c47a3 0a5d32e f5990cb f1c47a3 0a5d32e f5990cb f1c47a3 0a5d32e f5990cb 0a5d32e 9615d53 f5990cb 9615d53 f5990cb f1c47a3 0a5d32e b1b0036 502ffd8 b1b0036 0a5d32e f5990cb 0a5d32e 686c7cd 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e f5990cb 0a5d32e 6366111 e02ce54 0a5d32e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 | /* ═══════════════════════════════════════════════════════════════
重譯文本分析系統 Agent — Refined Dark Academic Dashboard
Palette: Sky-500 (#0ea5e9) + Indigo-400 (#818cf8) on Slate-950
Typography: Inter (UI) + JetBrains Mono (data) + Crimson Pro (headings)
WCAG AA compliant (4.5:1+)
═══════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600;700&display=swap');
/* ── 1. TOKENS & RESET ─────────────────────────────────────────── */
/* 預設淺色;系統設定為深色模式時(或明確帶 .dark class)自動切換深色 */
/* 底色體系(2026-08-12 三修):千卉回報「純黑或純白背景面積很大,視覺壓力
也變大」。量過三層才確定抱怨的來源不只對話欄——淺色的頁面底 #f8fafc 幾乎
等於白,而真正的大面積純白是 --c-surface(表格資料列+所有 Gradio 區塊);
深色則是 #020617 近乎純黑。改成三層結構,淺深共用同一套邏輯:
底(--c-bg) 淡藍/帶藍深色,鋪滿頁面、canvas、導覽、對話欄
浮起物(--c-bubble-bg)比底亮一階,對話氣泡
資料紙張(--c-surface)淺色維持純白當表格底(資料密集區可讀性優先),
深色跟著抬亮
淺色氣泡「比欄底亮一階、浮在上面」是千卉選的方案 2,剛好與深色本來就有的
關係(氣泡比欄底亮)一致,兩個主題不再各講各的。 */
:root {
--c-bg: #f3f8fd;
--c-surface: #ffffff;
--c-surface2: #eef5fc;
--c-surface3: #ffffff;
--c-surface3-border: #e2e8f0;
--c-border: rgba(2,132,199,.16);
--c-border-hover: rgba(2,132,199,.35);
/* 2026-08-12(業主回報淺色對比不足):淺色的 --c-border 疊在白/近白底上
只有約 1.2:1,遠低於 WCAG 1.4.11 對「UI 元件邊界」要求的 3:1,區塊外框
跟水平分界線在淺色模式下等於看不見(深色當初已針對 .app-header/欄邊框/
標題底線個別補過 rgba(148,163,184,.28),淺色從沒補)。這裡改成兩級線條,
之後新增元件挑其中一級,不要再直接用 --c-border 畫可見邊界:
--c-border-strong 結構線(區塊外框、欄位分隔、水平分界)
--c-divider 細線(表格資料列線、對話氣泡邊框)
深色值直接沿用原本散在各選擇器的硬寫值,深色外觀零改變。
2026-08-12 二修(千卉看實機截圖後回饋「線條對比好高、蠻醜」):
結構線從 #7c8798(對白 3.64:1)柔化到 #a3aebd(2.25:1)。取捨要
講明白——2.25:1 沒有達到 WCAG 1.4.11 對「UI 元件邊界」的 3:1 門檻,
是刻意的視覺決定;但比改版前的 1.2:1 高近一倍,業主回報的「看不清楚
區塊」在這個亮度差已經解決。文字對比不受這條影響,仍全數通過 AA。
細線 --c-divider 淺色 #b6c0cd 對氣泡底 1.64:1、對欄底 1.76:1,
跟深色 rgba(148,163,184,.28) 疊 #0d1526 算出來的 1.67:1 幾乎相同,
這就是「深淺兩個模式的氣泡邊框看起來一樣淡」的依據(用量測對齊,
不是各自挑一個看起來順眼的值)。
2026-08-12 四修(千卉指定:邊線與水平線改成跟「重新選擇研究情境」
按鈕的邊框同一個色):結構線淺色改成 rgba(2,132,199,.30),也就是
button.secondary 用的那個值,全站線條與次要按鈕從此同一個來源。
疊在新的淺底 #f3f8fd 上約 1.46:1,比三修的 2.3:1 再淡一階——這是
第三次往柔和方向調,已經明確離 WCAG 1.4.11 的 3:1 很遠,屬業主拍板
的視覺取捨(文字對比不受影響,仍全數通過 AA)。
2026-08-12 三修(千卉:淺色系的邊框不要太深,要維持亮麗的淺藍):
#a3aebd/#b6c0cd 是中性灰藍,配上這輪的淡藍底會顯灰、顯髒。改走
sky 相:結構線 #6ba3d6、細線 #a9cbe8、輸入框 #7fb0d9。**亮度級距
不動**(結構線仍約 2.3:1、氣泡邊框仍約 1.6:1,跟前一輪柔化後的手感
相同),換的是色相不是輕重——這樣既維持千卉要的柔和度,也讓線條跟
底色同屬一個藍色家族。
--c-border 本身維持原值不動:它還餵給 --block-border-color/
--border-color-primary 這兩個 Gradio 原生元件用的變數,動了會牽連
一整批沒盤點過的元件。 */
--c-border-strong: rgba(2,132,199,.30);
--c-divider: #a9cbe8;
/* 對話氣泡邊框獨立一個 token:前一版讓它共用 --c-divider,結果深淺兩邊
對不齊——淺色的 #a9cbe8 是對著「結構線那一級的深色值(alpha .28)」
挑的,但深色的 --c-divider 其實是更淡的 .18,實測深色氣泡邊框只有
1.37:1、淺色 1.61:1,兩個模式看起來不一樣。分成獨立 token 之後兩邊
各自對齊到 1.61:1,--c-divider 也回歸單一用途(表格資料列細線)。 */
--c-bubble-border: #a9cbe8;
/* 表格表頭底色與對話氣泡底色改成獨立 token(原本借用 --c-surface2/
--c-surface):淺色時兩者疊在自己的鄰接底色上都只有 1.03–1.07:1,
業主回報「表頭跟文字黏在一起」「看不清楚區塊的顏色」就是這個。 */
--c-thead-bg: #d8e8f6;
--c-bubble-bg: #ffffff;
--c-accent: #0284c7;
--c-accent-strong: #0369a1;
--c-accent2: #4f46e5;
--c-accent2-strong: #4338ca;
--c-accent3: #9333ea;
--c-accent-rgb: 2,132,199;
--c-accent2-rgb: 79,70,229;
--c-accent3-rgb: 147,51,234;
--c-bg-rgb: 255,255,255;
--c-text: #1e293b;
--c-text-soft: #334155;
--c-text-muted: #475569;
--c-text-subtle: #64748b;
--c-success: #047857;
--c-success-rgb: 4,120,87;
--c-warn: #b45309;
--c-user-bg: #dcfce7;
--c-user-border: #86efac;
--c-user-text: #166534;
--c-code-bg: #e0f2fe;
--c-code-text: #b45309;
--c-input-bg: #ffffff;
/* 2026-08-12:原本 rgba(2,132,199,.22) 疊白只有約 1.3:1,輸入框邊界同樣
受 1.4.11 的 3:1 規範;語料 metadata 表單「欄位看不清楚」有一半是這條。 */
--c-input-border: #7fb0d9;
--c-link-hover: #075985;
--c-scrollbar: #cbd5e1;
--c-scrollbar-hover: #94a3b8;
--font-size-base: 0.875rem;
--body-background-fill: var(--c-bg) !important;
--background-fill-primary: var(--c-surface) !important;
--background-fill-secondary: #dfeaf6 !important;
--block-background-fill: var(--c-surface) !important;
--block-border-color: var(--c-border) !important;
--border-color-primary: var(--c-border) !important;
--color-accent: var(--c-accent) !important;
--color-accent-soft: rgba(2,132,199,.08) !important;
--body-text-color: var(--c-text) !important;
--block-label-text-color: var(--c-text-muted) !important;
--block-title-text-color: var(--c-text-soft) !important;
--input-background-fill: var(--c-input-bg) !important;
--input-border-color: var(--c-input-border) !important;
--button-primary-background-fill: var(--c-accent) !important;
--button-primary-text-color: #fff !important;
--shadow-drop: 0 1px 3px rgba(15,23,42,.10) !important;
}
/* 注意:系統深色模式的 @media 覆寫改放在 launch(head=...) 注入的獨立 <style>(見 _DARK_MODE_HEAD_CSS),
因為 Gradio 會把 css= 內的選擇器自動加上 .gradio-container .contain 前綴,
:root 一旦被包進 @media 只會留下加了前綴的版本,而 ":root" 不可能是任何元素的子孫,等於永遠不會命中。 */
/* 明確以 ?__theme=dark 網址參數強制深色時 */
.dark {
--c-bg: #0b1424;
--c-surface: #172136;
--c-surface2: #1b2740;
--c-surface3: #1e293b;
--c-surface3-border: #334155;
--c-border: rgba(14,165,233,.11);
--c-border-hover: rgba(14,165,233,.30);
/* 深色的結構線沿用原本散在 .app-header/.nav-col/.chat-col/標題底線
各處的硬寫值 rgba(148,163,184,.28)(已通過實機檢視),token 化之後
那些個別覆寫規則就整組刪掉了,避免同一個色值散落六處。 */
--c-border-strong: rgba(148,163,184,.28);
--c-divider: rgba(148,163,184,.18);
/* .26 是實測對齊值:疊在深色氣泡底 #172136 上等於 1.61:1,跟淺色
#a9cbe8 疊 #f5faff 的 1.61:1 相同(見 :root 的同名 token 註解)。 */
--c-bubble-border: rgba(148,163,184,.26);
--c-thead-bg: #1e2a42;
--c-bubble-bg: #172136;
--c-accent: #0ea5e9;
--c-accent-strong: #38bdf8;
--c-accent2: #818cf8;
--c-accent2-strong: #a5b4fc;
--c-accent3: #c084fc;
--c-accent-rgb: 14,165,233;
--c-accent2-rgb: 129,140,248;
--c-accent3-rgb: 192,132,252;
--c-bg-rgb: 9,14,26;
--c-text: #e2e8f0;
--c-text-soft: #cbd5e1;
--c-text-muted: #94a3b8;
--c-text-subtle: #475569;
--c-success: #34d399;
--c-success-rgb: 52,211,153;
--c-warn: #fbbf24;
--c-user-bg: rgba(34,197,94,.18);
--c-user-border: rgba(34,197,94,.42);
--c-user-text: #bbf7d0;
--c-code-bg: rgba(14,165,233,.12);
--c-code-text: #fbbf24;
--c-input-bg: #1a2540;
--c-input-border: rgba(14,165,233,.15);
--c-link-hover: #7dd3fc;
--c-scrollbar: #1e3a5f;
--c-scrollbar-hover: #334d6e;
--body-background-fill: var(--c-bg) !important;
--background-fill-primary: var(--c-surface) !important;
--background-fill-secondary: #080f1c !important;
--block-background-fill: var(--c-surface) !important;
--block-border-color: var(--c-border) !important;
--border-color-primary: var(--c-border) !important;
--color-accent: var(--c-accent) !important;
--color-accent-soft: rgba(14,165,233,.08) !important;
--body-text-color: var(--c-text) !important;
--block-label-text-color: var(--c-text-muted) !important;
--block-title-text-color: var(--c-text-soft) !important;
--input-background-fill: var(--c-input-bg) !important;
--input-border-color: var(--c-input-border) !important;
--button-primary-background-fill: var(--c-accent) !important;
--button-primary-text-color: #fff !important;
--shadow-drop: 0 1px 3px rgba(0,0,0,.5) !important;
}
/* width 一定要明確指定,否則容器會依內容伸縮(本來就沒設定過,這是造成
對話視窗/按鈕/上傳卡片寬度在不同狀態間跳動不一致的根本原因)。
原本 max-width:1680px + margin:0 auto 是舊版單欄/二欄置中版面留下的
設定;Phase 8 三欄式工作區(nav-col/chat-col/canvas-col)改成滿版
貼齊左右邊界(千卉要求 2026-07-22),改成 width:100%、拿掉
max-width 上限與左右 padding。首頁 .homepage-col 自己有獨立的
max-width:50rem + margin auto 置中,不受這裡影響,寬螢幕上還是會維持
舒適閱讀寬度、不會被拉滿版。 */
.gradio-container {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
/* .gradio-container 內部還包了一層 Gradio 自己的 .app(class="main fillable app")
wrapper。原本以為是它的左右 padding 造成貼不到邊界,歸零後留白仍在;
千卉 2026-07-23 截圖回報四邊仍有留白,重新用 devtools 沿著
.main-layout-row 往上逐層量 computed style 才找到真因:.app 被 Gradio
設了 `max-width:1280px` 搭配 auto 置中的 margin-left(量到 77.5px =
(1435-1280)/2,抓包抓得很準),是 margin 不是 padding。真正修法是拿掉
max-width 限制、margin 歸零,padding 那條規則留著無害但其實沒解決過
問題。
後續(同日):千卉截圖標出頂部/底部還有大片空白,實測 .app 本身還有
`padding:16px 0px`(上下各16px,之前只清過左右)、.gradio-container
也還有 `padding:0 0 24px`(底部24px)——這兩個之前為了「左右貼齊」
只處理過左右方向,上下方向從沒動過,這次一併歸零。 */
.app {
padding: 0 !important;
max-width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
/* 2026-07-23:千卉回報頂部列(D)沒有真正固定置頂、底部「透過API使用/
設定」(G)也沒有固定置底。實測用 devtools 沿著 <footer> 往上量整條
祖先鏈才發現:Gradio 原生把 gradio-app/.gradio-container/.app/
.wrap 這四層全部設成 display:flex; flex-direction:column,.wrap 底下
剛好就是「主內容(.contain)+footer」兩個 flex 子項——結構本來就是
對的,問題是這條鏈的高度全部用 auto(依內容撐高),不是真正鎖定
100vh。內容只要比視窗高一點點(哪怕只多15px),整個外層文件就會
跟著捲動,把「理論上該固定」的頂部列/底部連結一起拖走——這才是
「沒有真正置頂/置底」的根本原因,不是 sticky/fixed 沒設對。
修法:把這條 Gradio 原生就是 flex column 的鏈路鎖死在 100vh,讓
.contain(真正裝我們 gr.Blocks 內容的那層)變成唯一會捲動的區域,
footer 用 flex-shrink:0 釘在最底。這樣完全不用碰 position:sticky/
fixed(8-B 已經踩過 sticky 在 Gradio wrapper 裡貼不住的坑),延續
這個檔案「結構性分離,不依賴 sticky」的既有原則,只是這次分離的
層級是整個頁面殼,不只是單一元件。 */
html, body {
height: 100% !important;
margin: 0 !important;
}
gradio-app {
display: block !important;
height: 100vh !important;
overflow: hidden !important;
}
.gradio-container {
height: 100vh !important;
overflow: hidden !important;
}
.app {
height: 100% !important;
overflow: hidden !important;
}
.wrap {
height: 100% !important;
overflow: hidden !important;
}
.contain {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow-y: auto !important;
}
/* 2026-07-23:千卉確認頂部/底部貼齊後,決定整個 footer(透過API使用/
設定連結列)不要了,直接拿掉,不只是隱藏內容——footer_links 參數也
一併清空,避免 Gradio 還是生出一個空的 <footer> 佔用 flex 高度。 */
footer {
display: none !important;
}
/* 千卉截圖回報頂部列還是沒有真正貼齊頂端,量到 main-layout-row 距離
.contain 頂端還有 72px 空隙——追出來是 .nav-col/.chat-col/.canvas-col
原本寫死的 `calc(100vh - 150px)` 這個 magic number,是照舊版「頁面
周圍還有一堆 padding/margin」的年代校準出來的高度預算。這次把
.app/.gradio-container 的上下 padding 都清空、footer 也鎖定
flex-shrink:0 之後,實際可用空間比 150px 這個預留值還多,多出來的
空間沒地方去,就變成三欄跟外層容器之間看得到的空隙。
修法:不要再猜一個固定 px 預算,改成整條鏈都用 flex 100% 往下傳,
有多少空間就填多少,不會再跟實際版面脫節。.contain 底下只有一個
直屬子元素(Gradio Blocks 的根 Column),這裡讓它 flex 撐滿
.contain,裡面的 main-layout-row 再 flex 撐滿它,nav-col/chat-col/
canvas-col 三欄改成 height:100%(相對 main-layout-row 的高度),
不再各自寫死 calc(100vh - 150px)。 */
.contain > .column {
display: flex !important;
flex-direction: column !important;
height: 100% !important;
min-height: 0 !important;
}
.main-layout-row {
flex: 1 1 auto !important;
min-height: 0 !important;
}
*, *::before, *::after { box-sizing: border-box; }
/* 全域安全網(陷阱 13 第 3 點):Gradio 的 markdown 渲染統一包在
<span class="md ...">,預設是 inline 卻常常裝 block 層級的 <p>/<ul> 等,
會在內容最前面產生依 span 自己字級算出的隱形空隙。這裡統一改成 block,
避免以後在還沒踩過的地方(如新的 gr.Markdown 元件)又重複這個問題;
字級仍留給各自的選擇器決定,這裡不覆寫 font-size。 */
.md { display: block !important; }
/* ── 2. HEADER(卡片化,2026-07-23 精簡成單行)───────────────────── */
/* 2026-07-23 千卉實機回饋:原本整塊是「有外框+漸層底色+圓角」的卡片,
看起來像一個獨立區塊漂在畫面上方。改成完全扁平:拿掉外框、底色、圓角,
只留一條下邊框當工作區的水平分界;產品名與步驟列改上下兩行。 */
.app-header {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-wrap: nowrap;
gap: .9rem;
text-align: left;
padding: 1.5rem 1.25rem 1.1rem;
position: relative;
margin: 0 0 1.25rem;
border: none;
/* 頂部標題與內容的分界線,用結構線 token(--c-border-strong)。
2026-08-12 之前這裡是 --c-border 加上「.dark .app-header 個別覆寫成
rgba(148,163,184,.28)」的組合(深色太淡的 #17),淺色端則一直沒補、
業主回報看不清楚。兩邊的值現在都收進 --c-border-strong,這裡不再需要
任何深色個別覆寫。
── 保留的踩坑記錄(雖然這批 .dark 規則已刪,通則仍成立)──
Gradio 會自動幫 CSS 選擇器加容器前綴,這個機制處理「.dark A, .dark B」
這種逗號並列、且每個分支都以 .dark 開頭的選擇器時有 bug:只有第一個
分支前綴正確,後面每個分支都被錯誤地多插一次 .dark(變成需要兩層
.dark 祖先才能匹配,永遠選不到東西,等於整條規則除了第一分支都是死的)。
已在 Gradio 6.20.0 實測。之後任何 .dark 開頭的規則要並列多個選擇器,
都要拆成一條一個選擇器,不要用逗號合併。 */
border-bottom: 1px solid var(--c-border-strong);
background: none;
}
/* 注意:這三個選擇器的漸層文字底色(background-image: linear-gradient(...var()...))
改在 launch(head=...) 注入的 <style> 裡設定(見 _DARK_MODE_HEAD_CSS)——
這裡只保留其餘不受影響的樣式(字型、間距等)。
踩坑(2026-07-22 千卉實機回報,首頁與 Canvas 標題皆會發生,須反白選取
才看得到):真正的根因不是重繪/合成層問題(曾誤判為 Chromium 動態內容
repaint bug,試過 transform:translateZ(0) 無效,已移除),而是瀏覽器
對 `background` 這個簡寫屬性(shorthand)解析失敗——當值是
`linear-gradient(...多個裸 var() 當色標...)` 且整條宣告掛
`!important` 時,瀏覽器會把簡寫展開成多個子屬性(background-image/
background-color/...),但 background-image 這個子屬性最終解析成空值
(用 devtools 讀 CSSStyleDeclaration 已直接證實:getPropertyValue
('background-image') 回傳空字串,即使 getPropertyPriority 顯示
'important'),導致完全沒有漸層可畫、文字又是 transparent,看起來就是
整個標題消失;反白選取會強制瀏覽器重繪,這次改用其他路徑重新算出
實際漸層才「顯現」。修法:不要用 `background` 簡寫,直接寫
`background-image: linear-gradient(...) !important;` 這個子屬性本身,
跳過簡寫展開的解析路徑——已用瀏覽器 devtools 即時修改驗證過,改長寫法
後漸層立即正常顯示。*/
.app-header .title-line {
font-family: 'Crimson Pro', 'Noto Serif TC', Georgia, serif !important;
font-size: 1.3rem !important;
font-weight: 700 !important;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 !important;
letter-spacing: -.01em;
line-height: 1.2 !important;
white-space: nowrap;
flex: none;
}
.app-header .title-line .en {
font-size: 1.05rem !important;
font-weight: 600 !important;
}
.badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 9px;
border-radius: 20px;
font-size: 0.70rem;
font-weight: 700;
letter-spacing: 0.05em;
margin: 0 3px;
vertical-align: middle;
}
.badge-llm1 {
background: rgba(var(--c-accent-rgb),.10);
color: var(--c-accent-strong);
border: 1px solid rgba(var(--c-accent-rgb),.30);
}
.badge-llm2 {
background: rgba(var(--c-accent2-rgb),.10);
color: var(--c-accent2-strong);
border: 1px solid rgba(var(--c-accent2-rgb),.30);
}
/* step indicators:2026-07-23 二度調整——header 改成上下兩行(產品名一行、
步驟列一行),所以這裡不再需要 flex:1 去跟標題搶同一行的水平空間,改成
自己佔滿整行寬度。兩行之間的間距一樣交給 .app-header 的 gap,不另設
margin-top,避免兩邊疊加。 */
.app-header .steps {
display: flex; justify-content: flex-start; flex-wrap: wrap;
gap: 4px; margin: 0; width: 100%; min-width: 0;
}
.step-pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 13px;
border-radius: 20px;
font-size: 0.72rem;
font-weight: 600;
background: rgba(var(--c-accent-rgb),.06);
border: 1px solid rgba(var(--c-accent-rgb),.30);
color: var(--c-text-soft);
letter-spacing: .03em;
transition: border-color .2s, background .2s;
}
.step-pill:hover {
background: rgba(var(--c-accent-rgb),.12);
border-color: rgba(var(--c-accent-rgb),.55);
}
.step-pill .step-num {
width: 17px; height: 17px; border-radius: 50%;
background: rgba(var(--c-accent-rgb),.22);
color: var(--c-accent-strong);
display: inline-flex; align-items: center; justify-content: center;
font-size: 0.62rem; font-weight: 800;
}
/* 已完成步驟:綠色實心,取代原本永遠同一個樣子的裝飾用藥丸 */
.step-pill--done {
background: rgba(var(--c-success-rgb),.14);
border-color: rgba(var(--c-success-rgb),.4);
color: var(--c-success);
}
.step-pill--done .step-num {
background: var(--c-success);
color: #fff;
font-size: 0.68rem;
}
/* 目前所在步驟:強調外框 + 持續的呼吸動畫。單一步驟內部可能安靜運算好幾
分鐘(如指標計算),沒有新的 log 出現時畫面會像凍住,加一個不間斷的
輕微動畫給使用者「系統還在動」的持續訊號;respects prefers-reduced-motion
(見全域 REDUCED MOTION 規則,會自動把動畫時長歸零) */
.step-pill--current {
background: rgba(var(--c-accent-rgb),.16);
border: 1px solid var(--c-accent);
color: var(--c-accent-strong);
font-weight: 700;
animation: stepPulse 1.6s ease-in-out infinite;
}
.step-pill--current .step-num {
background: var(--c-accent);
color: #fff;
}
@keyframes stepPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--c-accent-rgb),.35); }
50% { box-shadow: 0 0 0 5px rgba(var(--c-accent-rgb),0); }
}
/* 執行失敗卡住的步驟:紅色警示樣式,且不套用 stepPulse 呼吸動畫——原本
失敗時圓點會卡在「進行中」動畫永遠閃爍,讓使用者誤以為系統還在運算 */
.step-pill--failed {
background: rgba(220,38,38,.14);
border-color: rgba(220,38,38,.4);
color: #dc2626;
font-weight: 700;
}
.step-pill--failed .step-num {
background: #dc2626;
color: #fff;
}
/* ── 3. TABS ───────────────────────────────────────────────────── */
/* 2026-07-24(E1):這裡原本有一整組 `.tabs > .tab-nav` sticky/背景/hover/
selected 樣式,但這版 Gradio 的實際 DOM 是 `.tabs > .tab-wrapper >
.tab-container`、沒有任何 `.tab-nav` 節點,那組規則從寫下來就是死規則,
已整組移除。 */
/* 2026-07-24(item 1):統計四子頁包在 .tabitem 裡,其 1.5rem 上 padding
讓標題比「語言指標分析」頁(無 tabitem,走 .canvas-result margin)多縮
24px,三頁標題上方留白不一致(實測 idx 42px vs stat 66px)。上 padding
歸零讓統計頁標題對齊 idx;水平與下方 padding 保留。 */
/* 水平 padding 移到 .canvas-result 統一處理(見該處註解),這裡只留下方
間距;上方 0 是為了跟「語言指標分析」頁標題頂端對齊(item 1)。 */
.tabitem { padding: 0 0 1.5rem !important; }
/* 2026-07-24(C1):統計比較分析的四個子頁改由左側導覽列驅動後,Canvas 內
這排分頁按鈕列(敘述統計|差異性分析|…)跟左欄四個項目是同一組選項的兩套
UI,重複。整排在視覺上隱藏,但 gr.Tabs 結構保留——巢狀分頁的切換仍靠 JS 去
點這排按鈕(見 bindResultNav 的 tabButtons.click)。
踩坑(連踩兩種隱藏法):
① display:none —— .click() 完全不觸發切換,這版 Gradio 的 Svelte on:click
對移出渲染樹的元素不反應。
② 標準 visually-hidden(position:absolute + width:1px + clip)—— 也失敗,
但原因不同:把 wrapper 縮成 1px 寬會觸發 Gradio 自己的分頁溢出邏輯,把
四個分頁全塞進 overflow dropdown,可見容器只剩一顆溢出選單鈕,JS 選擇器
(抓可見容器的 > button)就抓不到分頁按鈕了。
正解:必須保留 width:100%,Gradio 才會把四個分頁留在可見容器、不觸發溢出;
只用 height:0 + overflow:hidden + opacity:0 做視覺隱藏(不動寬度、不改
position)。按鈕高度歸零但仍在渲染樹、寬度正常,.click() 照常切換。
(實測:四顆按鈕在位、點第 4 顆→集群分析、點第 1 顆→敘述統計,正確。) */
#canvas_stat .tabs > .tab-wrapper {
height: 0 !important;
min-height: 0 !important;
overflow: hidden !important;
opacity: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
/* ── 4. CHATBOT ────────────────────────────────────────────────── */
.chatbot {
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
}
/* 主對話視窗:下方接技術執行紀錄 Accordion,兩者視覺上合併成一張卡片
(只留上方圓角,底部邊框拿掉,改由 Accordion 頂端的邊框當作分界線) */
/* 2026-07-24(仿 Claude):對話視窗貼齊欄邊——拿掉外框、圓角、白色底,
背景透明吃 chat-col 的底色,只有氣泡本身有底色。 */
.main-chat {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
}
.main-chat .bubble-wrap,
.main-chat div[role="log"] {
background: transparent !important;
}
/* #2 消除重複滾軸:只讓 bubble-wrap 捲動,外層不捲 */
.main-chat [class*="wrapper"],
.main-chat > div > div:not([role="log"]) {
overflow: hidden !important;
}
.main-chat div[role="log"].bubble-wrap {
overflow-y: auto !important;
overflow-x: hidden !important;
}
/* 2026-07-23:.main-chat 外層改用 flex-grow 決定高度(見「5b. COLUMN
LAYOUT」區塊的 5:1 比例設定),中間這層 wrapper 也要跟著撐滿,
高度才會一路傳到 bubble-wrap,捲動區域才會是「剩下的全部空間」
而不是「內容多高就多高」。 */
.main-chat > div,
.main-chat [class*="wrapper"] {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
/* 訊息從頂部開始排列,消除空白 */
div[role="log"].bubble-wrap,
div[role="log"][aria-label="chatbot conversation"],
.main-chat .bubble-wrap,
.bubble-wrap {
flex-direction: column !important;
justify-content: flex-start !important;
align-content: flex-start !important;
align-items: flex-start !important;
min-height: 0 !important;
/* 2026-07-24 千卉:對話列的左右留白對齊導覽列(16px)。原本 .5rem(8px)
讓氣泡比導覽列項目更貼邊,三欄留白不一致。 */
padding: 16px !important;
}
/* 清除 Gradio 在 bubble-wrap 內加的空白 spacer div */
.bubble-wrap > div:empty { display: none !important; }
.bubble-wrap > :first-child { margin-top: 0 !important; padding-top: 0 !important; }
/* ── 隱藏訊息計數氣泡、頁碼、複製按鈕浮層 ── */
.main-chat .count,
.main-chat .message-count,
.main-chat [class*="count"],
.main-chat .page,
.main-chat .page-count,
.main-chat .scroll-hide,
.main-chat .copy-text-button,
.main-chat .copy-btn,
.main-chat button[title*="copy" i],
.main-chat button[aria-label*="copy" i],
.main-chat [data-testid*="copy"],
.main-chat .bubble-wrap > div:last-child > span:only-child {
display: none !important;
}
/* 徹底隱藏對話視窗內所有複製/工具浮層圖示 */
.main-chat .icon-button-wrapper,
.main-chat [class*="icon-button"],
.main-chat .message-buttons,
.main-chat .message-row button,
.main-chat button[title*="複製"],
.main-chat button[aria-label*="複製"] {
display: none !important;
}
/* ── 通用訊息氣泡 ── */
/* 不同時間產生的訊息之間如果間距太小,會分不出是好幾則獨立訊息還是同一則,
拉大訊息列間距讓每則訊息之間有清楚的留白斷開 */
/* Gradio 預設給每個 message-row 左右各留 20px margin(原本應是保留給頭像用的
空間,本專案沒有用頭像),導致 AI 訊息離左邊、使用者訊息離右邊都比預期遠,
這裡歸零讓氣泡貼近對話視窗邊緣。另外 Gradio 還設了 max-width: calc(100% - 60px),
AI 訊息貼左邊不受影響,但使用者訊息靠 margin-left:auto 貼右邊,這 60px
缺口會整個落在最右側,一併覆寫掉 */
.main-chat .message-row {
margin-bottom: 1.1rem !important;
margin-left: 0 !important;
margin-right: 0 !important;
width: 100% !important;
max-width: 100% !important;
align-self: stretch !important;
}
/* 2026-07-24(千卉回報氣泡留白遠超過 16px):這一層是「看不見」的外框,
真正畫出氣泡底色與圓角的是內層 .md(見下方註解與本專案筆記)。它原本的
水平 padding 1rem 會把可見氣泡再往內推 16px——bubble-wrap 已給 16px,
疊起來可見氣泡實際落在 32px。水平內距歸零,可見氣泡才真的貼在 16px;
氣泡內的文字留白由 .md 自己的 padding 提供。 */
/* 2026-07-30(2-C 第 6 項):對話氣泡文字改 12px(千卉指定)。字級固定寫在
這裡跟下面 .message-row.bot .message/.message p/.message .md 四個地方
——不是重複設定,是 Gradio 這個氣泡結構本來就有巢狀 <p>/<span class="md">
各自帶自己的字級(見下面兩則舊註解,實測不會沿用外層容器字級),四層
都要一起改成 12px,畫面上才會真的變小;只改最外層這條,實際顯示的文字
仍會被內層 .p/.md 的 var(--font-size-base) 蓋掉,完全看不出效果。 */
/* 2026-07-31(Batch G 第 3 項):業主回報 12px 太小,改 14px。四處連動改,
理由同上——只改一處字級不一致,這裡跟下面 .message-row.bot .message/
.message p/.message .md 四處都要同步改成 14px。 */
.main-chat .message-row .message {
border-radius: 10px !important;
font-size: 14px !important;
line-height: 1.7 !important;
padding: 0 !important;
width: fit-content !important;
max-width: 90% !important;
transition: box-shadow .2s !important;
}
/* Gradio 在氣泡外層又包了一層同樣帶 .message class 的巢狀 div(非語意重複,
是它自己的 panel-full-width 包裝),上面這條規則會連內層也命中,
造成 padding 上下疊加兩次,視覺上像是文字最上面跟最下面多了一整行空白。
這裡把內層那份歸零,只留外層真正的框線與留白 */
.main-chat .message-row .message .message {
padding: 0 !important;
border-radius: 0 !important;
max-width: 100% !important;
}
/* 使用者訊息:淺灰底色跟白色頁面背景錯開,不用外框線,靠右對齊與置左的
AI 訊息做區分 */
/* Gradio 實際輸出的內層元素同時帶 user/bot 與 message 兩個 class(非巢狀父子),
用複合選擇器只命中該元素本身,避免命中內層 panel-full-width 造成雙重框線 */
.main-chat .message-row.user .message,
.main-chat .message-row.user-row .user.message {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
color: var(--c-text) !important;
margin-left: auto !important;
font-weight: 500 !important;
box-shadow: none !important;
max-width: 80% !important;
text-align: right !important;
}
.main-chat .message-row.user,
.main-chat .message-row.user-row { margin-bottom: 1.1rem !important; }
/* 2026-08-22(業主:使用者泡泡外框/底色希望跟 AI 不同):真正畫出氣泡底色與
邊框的是內層 .md(見上面「AI 訊息」區塊那條共用選擇器 .message-row .message
.md),使用者與 AI 訊息目前吃到同一份 --c-bubble-bg/--c-bubble-border,
看起來一樣。這裡只針對使用者泡泡的 .md 覆寫,改用專屬的 --c-user-bg/
--c-user-border/--c-user-text(原本就有定義、只是從沒接到任何選擇器上)
——淺色是淡綠、深色是半透明綠,跟 AI 泡泡的中性藍灰做出區隔。 */
.main-chat .message-row.user .message .md,
.main-chat .message-row.user-row .user.message .md {
background: var(--c-user-bg) !important;
border: 1px solid var(--c-user-border) !important;
color: var(--c-user-text) !important;
}
/* AI 訊息(主對話視窗):淺灰底色跟白色頁面背景錯開,不用外框線 */
.main-chat .message-row.bot .message,
.main-chat .message-row.bot-row .bot.message {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
color: var(--c-text) !important;
font-size: 14px !important;
line-height: 1.7 !important;
box-shadow: none !important;
text-align: left !important;
}
/* Gradio 產生的 <p> 段落本身有自己的 font-size(實測為固定 16px),
不會沿用上面設在外層容器的字級,且段落間距預設高達 16px,
視覺上偏大又有明顯空行,這裡直接命中 <p> 覆寫 */
.main-chat .message-row .message p {
font-size: 14px !important;
line-height: 1.6 !important;
margin: 0 0 .5rem !important;
}
.main-chat .message-row .message p:last-child { margin-bottom: 0 !important; }
/* 包住段落的 <span class="md prose"> 預設是 inline,卻裡面塞了 block 層級的
<p>(不合法巢狀),瀏覽器會讓這個 span 依自己的字級(16px)先產生一個看
不見的行高空隙,出現在第一段之前——這也是最上面「多一行空白」的真正來源。
改成 block 並統一字級後,這段隱形空隙就會消失 */
.main-chat .message-row .message .md {
display: block !important;
font-size: 14px !important;
line-height: 1.6 !important;
padding: 0.9rem 1.1rem !important;
/* 2026-07-24:氣泡底色改白(淺色)/較淺的 surface(深色)。原本是
--c-surface2(淺灰),改成 --c-surface:淺色=白、深色=#0d1526(比
chat-col 的 --c-bg #020617 淺一階),氣泡浮在欄底上。 */
/* 2026-08-12(業主回報淺色看不清楚區塊):白氣泡疊在 chat-col 的
--c-bg #f8fafc 上只有 1.07:1,等於沒有邊界;深色那一階(#0d1526 疊
#020617)本來就夠,所以只動淺色值——改用 --c-bubble-bg(淺色 #eef2f7、
深色維持 #0d1526)。填色差在近白區間拉不到 3:1(那要暗到不像對話框),
填色之外還要一條邊框才看得出範圍。
2026-08-12 二修(千卉:淺色氣泡也要淡邊框、深淺一致):邊框從結構線
降到細線 --c-divider。淺色 #b6c0cd 對氣泡底 1.64:1、對欄底 1.76:1,
深色 rgba(148,163,184,.28) 疊 #0d1526 是 1.67:1,兩個模式的邊框
亮度差幾乎相同,看起來才是同一種「淡邊框」。 */
background: var(--c-bubble-bg) !important;
border: 1px solid var(--c-bubble-border) !important;
border-radius: 10px !important;
}
/* ── AI 頭像(2026-08-13 四版,業主要求改成「彩虹弧線」圖示)───────
gr.Chatbot(avatar_images=(None, ai-avatar.svg)) 的前端結構是
.message-row > .avatar-container > img。Gradio 原生給 35px 圓、
1px 邊框、margin-right: var(--spacing-xxl),這裡收成 28px 並改成
紫→藍漸層圓底(跟標題字共用同一條 heading-gradient)。
(中間版本一度試過純色底+固定色碼色譜光線,業主看實機覺得不好看,
已改回這版:漸層畫在 CSS 不畫在 SVG 裡,理由同最初設計——avatar_images
是 Python 端參數,同一個檔案沒辦法隨明暗主題換色;交給 CSS 就直接吃
--c-accent-strong/--c-accent2/--c-accent3 這組已經會跟著主題切換的
token,不用再另外補 .dark/@media 深色分支,SVG 檔本身只留白色三道
同心弧(白色描邊疊在任一主題的漸層上都夠亮)。
踩坑提醒:漸層一律寫 background-image 子屬性,不要用 background
簡寫(本專案已踩過兩次,簡寫在這條 pipeline 下會解析成空值)。
flex:none 是防「圖示被 flex 壓縮」那個舊坑(收合 icon bar 曾被壓成 8px)。 */
.main-chat .avatar-container {
width: 28px !important;
height: 28px !important;
flex: none !important;
align-self: flex-start !important;
margin-right: 10px !important;
margin-left: 0 !important;
border: none !important;
border-radius: 50% !important;
background-image: linear-gradient(135deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%) !important;
}
.main-chat .avatar-container img {
width: 100% !important;
height: 100% !important;
/* 2026-08-12 二版(業主:頭像圖案有點小):內距 5px→3px,圖案在 28px
圓內的可用範圍從 18px 放大到 22px;SVG 本身的 path 也一併撐大並改成
實心(見 assets/icons/ai-avatar.svg)。兩件事一起做才有感——單改內距
只是把細線稿放大,單改 path 又會被內距框住。 */
padding: 3px !important;
object-fit: contain !important;
opacity: 1 !important;
}
/* 頭像佔掉 38px(28+10)之後,氣泡的 90% 上限要從同一條 flex line 扣掉,
否則長訊息會把對話欄撐出捲軸。 */
.main-chat .message-row.bot .message,
.main-chat .message-row.bot-row .bot.message {
max-width: calc(90% - 38px) !important;
}
.main-chat .message-row.bot .message strong,
.main-chat .message-row.bot .message b,
.main-chat .message-row.bot-row .bot.message strong,
.main-chat .message-row.bot-row .bot.message b { color: var(--c-accent-strong) !important; font-weight: 700 !important; }
.main-chat .message-row.bot .message em,
.main-chat .message-row.bot .message i,
.main-chat .message-row.bot-row .bot.message em,
.main-chat .message-row.bot-row .bot.message i { color: var(--c-accent2-strong) !important; }
.main-chat .message-row.bot .message code,
.main-chat .message-row.bot-row .bot.message code {
background: var(--c-code-bg) !important;
color: var(--c-code-text) !important;
padding: 1px 6px !important;
border-radius: 4px !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.82em !important;
}
.main-chat .message-row.bot .message a,
.main-chat .message-row.bot-row .bot.message a {
color: var(--c-accent-strong) !important;
text-decoration: underline !important;
text-underline-offset: 2px !important;
}
/* 2026-07-23:技術執行紀錄從 Accordion 轉正成獨立頁面(canvas_exec_col),
不再是接在對話視窗卡片下方的「抽屜」,改用跟 canvas_idx_col 等其他三個
結果頁共用的 .canvas-result class(已在 gr.Column 宣告時掛上),這裡不用
再另外寫專屬的卡片外框樣式,避免跟共用樣式重複定義出兩套外觀。 */
/* 執行視窗(簡易訊息顯示欄,非對話氣泡) */
/* 原本這裡還設了 max-height + overflow-y:auto 讓內部自己捲動,但整個頁面
本身也會捲動,兩個捲動容器疊在一起、捲軸位置又離得很近,看起來像「兩條
捲軸」造成混亂——拿掉內部捲動限制,讓內容自然撐開高度,只用頁面本身的
捲軸,維持單一捲動體驗 */
/* 2026-07-24(#16):水平 padding 歸零——canvas-result 已提供 1.25rem 左內距
讓內容跟標題(都在 96px)齊平,這裡原本再加 .75rem(12px) 會把 log 文字推到
108px、比標題與其他結果頁多縮 12px。只留上下間距。 */
.exec-log {
background: transparent !important;
border: none !important;
padding: .55rem 0 !important;
margin-top: 0 !important;
}
.exec-log p {
font-size: var(--font-size-base) !important;
line-height: 1.5 !important;
margin: 0 0 .3rem !important;
padding: 0 !important;
color: var(--c-text-muted) !important;
font-family: 'JetBrains Mono', 'Noto Sans TC', monospace !important;
white-space: pre-wrap !important;
word-break: break-word !important;
}
.exec-log p:last-child { color: var(--c-text-soft) !important; }
.exec-log code {
background: none !important;
color: var(--c-accent-strong) !important;
padding: 0 .15rem 0 0 !important;
font-size: var(--font-size-base) !important;
}
/* 跟對話視窗一樣:包住段落的 <span class="md"> 預設 inline 卻裝 block 的
<p>,會在最前面產生依自己字級算出的隱形空隙,這裡一併改成 block 並統一字級 */
.exec-log .md {
display: block !important;
font-size: var(--font-size-base) !important;
}
/* ── 5b. COLUMN LAYOUT ─────────────────────────────────────────── */
/* gap: 0 讓對話視窗跟緊接著的技術執行紀錄 Accordion 完全貼合,
視覺上合併成一張卡片(見「融合」設計討論) */
/* Phase 8-B:三欄式工作區。nav-col 窄、固定寬度不隨視窗縮放;chat-col/
canvas-col 各自獨立捲動(各自的內容不會因為對方變長而互相影響),高度
統一扣掉頁面上方 Tab 列與外距抓一個概略值,之後看實際渲染再微調 */
/* 2026-07-23:跟 chat-col/canvas-col 一樣鎖定高度+自己捲動,理由是
頁殼改成真 100vh 鎖死之後(見上方 .contain 那組規則),nav-col 如果
內容比這個高度多(手風琴展開+8個導覽項目在矮視窗上有可能發生),
沒有自己的 overflow 就會撐開 .main-layout-row,逼 .contain 整層捲動
——雖然不會再拖走頂部列/footer了(那兩個已經在 .contain 外面),但
chat-col/canvas-col 會跟著被迫一起捲動,體驗還是不對。讓三欄各自
獨立捲動,互不影響。 */
/* border-right:導覽欄與對話欄各在自己的右緣掛一條 1px 垂直分隔線
(千卉 2026-07-23 指定),三欄之間才有明確的邊界。畫在欄位自己的
邊框上、不是插入額外的分隔元件,這樣它會自動跟著欄高走完整條。
padding 左右 8px(千卉 2026-07-23):欄裡的東西都不要貼著兩側邊緣。
留白設在欄位本身而不是逐項設定,「開始新分析」按鈕、收合鈕、結果導覽
項目才會全部吃到同一份,不會有人漏掉——先前只設在 .result-nav 上時,
「開始新分析」因為是 .nav-col 的直接子項,就沒被涵蓋到。 */
.nav-col {
flex: 0 0 220px !important;
max-width: 220px !important;
display: flex !important;
flex-direction: column !important;
/* Bug 4-B 修復(2026-07-30):跟 .chat-col/.canvas-col/.main-layout-row
一樣補上 flex-wrap:nowrap。這裡沿用 Gradio .column 預設的 wrap 值,
_populate_tabs 把下載捷徑那組(nav_dl_group)掛進來後,導覽列內容
一旦超過這個固定 height,就會被摺到第二條 flex line(往右排到可視
寬度外),再被下面的 overflow-x:hidden 裁掉——節點還在 DOM,只是渲染
到看不見的地方,這正是「報告完成後導覽列項目整組消失」的根因。
超高內容改走既有的 overflow-y:auto 垂直捲動,不要再被摺行。
這條 flex-wrap:nowrap 是本檔的地雷區,2-B 這輪改版千萬不要移除
——見下一則註解。 */
flex-wrap: nowrap !important;
gap: .3rem !important;
height: 100% !important;
/* 2026-07-30(2-B 第 2 項)三段式改版:原本 overflow-y:auto 直接設在
.nav-col 本身,讓「品牌字標+操作項目+(曾經在頂端的)明暗/收合鈕」
整欄一起捲動。這輪把明暗切換/收合鈕搬到欄底常駐(置底+置右),
如果還是讓 .nav-col 整欄捲動,內容一多,底部工具列會被捲動推出可視
範圍——等於復發 Bug 4-B(「內容一多,重要項目跟著捲不見」)的同一種
症狀,只是這次主角換成底部工具列而不是下載捷徑。修法:改成「上方
品牌列(固定)+中間 .nav-items-scroll(flex:1,自己 overflow-y:auto)
+底部 .nav-bottom-toolbar(固定)」三段式結構,捲動只發生在中間段,
.nav-col 自己的 overflow 改成 hidden(三個直屬子項各自負責自己要不要
溢出,nav-col 本身不再需要當捲動容器)。flex-wrap:nowrap 依然保留在
這裡:三個直屬子項總高度若超過 100%,沒有這條會被摺到第二條 flex
line,跟 Bug 4-B 是同一種機制,不能拿掉。 */
overflow: hidden !important;
border-right: 1px solid var(--c-border-strong) !important;
/* 5-A:導覽列上下左右統一 16px 留白。所有按鈕/項目寬度撐滿這個內距框,
右上收合鈕的右緣也就落在 16px 處,不再多出 ~20px 空隙。 */
padding: 16px !important;
}
/* 中間可捲動項目區:研究情境(已隱藏的空殼)、回首頁連結、結果導覽都在
這裡面。flex:1 讓它吃掉扣除頂端品牌列與底部工具列之後的剩餘高度;
min-height:0 是關鍵——flex 子項預設 min-height:auto 會用內容高度當下限,
讓這個子項無法縮小到比內容矮,overflow-y:auto 就永遠不會真的觸發捲動,
而是把 .nav-col 撐爆(跟 Bug 4-B 的成因是同一條 flexbox 規範,只是換了
一層)。flex-wrap:nowrap 同理沿用 .nav-col 的防禦寫法,避免這裡也被
Gradio 預設的 .column flex-wrap:wrap 摺行。 */
.nav-items-scroll {
display: flex !important;
flex: 1 1 auto !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
min-height: 0 !important;
gap: .3rem !important;
overflow-y: auto !important;
overflow-x: hidden !important;
}
/* 底部工具列(明暗切換+收合):flex:none 固定高度,不參與中間段的縮放,
永遠貼在 .nav-col 底部;border-top 當作千卉指定的「上方水平分隔線」。
justify-content:flex-end 靠右對齊(千卉指定「置底+置右」)。 */
#nav_bottom_toolbar, #nav_bottom_toolbar > .html-container { padding: 0 !important; margin: 0 !important; }
.nav-bottom-toolbar {
display: flex !important;
flex: none !important;
align-items: center !important;
justify-content: flex-end !important;
gap: 2px !important;
padding-top: .6rem !important;
border-top: 1px solid var(--c-border-strong) !important;
}
/* 真因排查記錄:Gradio 的 .column 預設會帶 flex-wrap:wrap。平常沒設高度時
不會有事,但這裡額外設了固定 height 讓內容用捲動取代撐高,一旦內容總高
度超過這個 height,flex-wrap:wrap 會把「排不下」的子元件換到*右邊新開一
欄*(水平並排),而不是照預期垂直捲動——這就是 canvas-col 一度出現內容
跑到欄外、往右位移一整欄寬度的真正原因(不是 sticky、也不是 overflow-x
單獨造成的,那兩個只是讓症狀被看見的地方)。修法:flex-wrap 明確關掉。 */
.chat-col {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
/* 2026-07-24(仿 Claude 對話欄):對話視窗與輸入框直接貼齊欄邊、彼此
之間也無間隙——gap 從 1rem 改 0。欄本身給一個底色(--c-bg),讓上面
的白色氣泡與輸入框浮出來;右側分隔線保留。 */
gap: 0 !important;
height: 100% !important;
overflow: hidden !important;
min-width: 0 !important;
background: var(--c-bg) !important;
border-right: 1px solid var(--c-border-strong) !important;
}
/* 對話視窗:輸入框高度比例 5:1(千卉要求,2026-07-23)。用 flex-grow
依 chat-col 當下的可用高度分配,不寫死 px,縮放視窗時比例仍會維持;
chatbot 元件不再用 Gradio 原生 height= 參數(那是寫死 px),改讓外層
wrapper 高度完全交給 flex 決定。訊息列表本身的捲動仍是 Gradio 原生
行為(.bubble-wrap 內部自己有捲軸),外層 wrapper 用 overflow:hidden
避免出現雙捲軸。 */
.main-chat {
flex: 1 1 auto !important; /* 2026-07-24:吃掉輸入框以外的全部高度(見 #chat_upload_zone) */
min-height: 0 !important;
height: auto !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
}
/* canvas-col 本身「不」捲動——它只負責把步驟列(固定)跟 canvas-scroll
(捲動)疊在一起、限制在同一個總高度內。踩坑記錄:原本想讓步驟列用
position:sticky 貼在 canvas-col 頂端,但 sticky 元素只能在「自己最近的
容器」範圍內移動,而 Gradio 自動包在 gr.HTML() 外面的 wrapper 高度只比
步驟列本身高一點點,捲動超過那一點高度後 sticky 就跟著捲走、貼不住。
改成結構性分離:步驟列跟 canvas-scroll 是兩個獨立區塊,前者天生不參與
捲動,後者自己 overflow-y:auto,兩者高度互不相干,不用依賴 sticky。 */
.canvas-col {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
gap: 0 !important;
height: 100% !important;
overflow: hidden !important;
min-width: 0 !important;
}
.canvas-col .app-header {
flex-shrink: 0 !important;
}
/* 2-A(2026-07-30):頂部產品名+步驟列只在「AI 對話」頁顯示,其他結果頁
(語言指標分析/敘述統計/差異性分析/監督式學習/非監督式學習/
LLM輔助分析/技術執行紀錄)都不顯示——那幾頁已經佔滿 Canvas 寬度,
步驟列這時只是重複資訊。data-page 屬性是 _APP_HEAD_SCRIPT 的
applyActivePage() 寫在 .main-layout-row 上的既有機制,這裡純加一條
CSS,不需要新增任何 Python wiring。用 :not([data-page="chat"]) 反選,
不用把 5 個結果頁 key 都列一次。elem_id 鎖 app_header_html 的外層
wrapper——elem_classes 會同時蓋在外層 .block 與內層 .prose 兩層 div 上
(本檔多處踩過這坑,見 .upload-hint 那段完整記錄),id 選擇器沒有這個
問題。 */
.main-layout-row:not([data-page="chat"]) #app_header_wrap {
display: none !important;
}
/* 最後一段殘餘留白(10px):Gradio 幫所有 gr.HTML() 元件的內容包了一層
.html-container,本身有 padding:10px 12px,這是全站每個 gr.HTML()
共用的預設樣式(首頁卡片、運算中提示等都靠它),不能整個歸零,只精準
鎖定「包住 .app-header 的那一個」html-container,不影響其他 gr.HTML()
元件(例如同樣在 canvas-col 裡的 computing_html)。 */
.html-container:has(.app-header) {
padding: 0 !important;
}
.canvas-scroll {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
flex: 1 1 auto !important;
min-height: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
/* 2026-07-24 千卉:Canvas 內容左右等寬。水平內距統一收在這一層(20px,
跟上方 .app-header 的左右 20px 一致),底下所有內容區塊都吃同一份——
原本只有 .canvas-result 自己加 20px,而 upload_zone/metadata_zone/
分析面板是 canvas-scroll 的直屬子項、左右各 0,同一欄裡兩種留白。
.canvas-result 的水平 padding 因此歸零,避免疊成 40px。 */
padding-left: 20px !important;
padding-right: 20px !important;
}
/* 2026-07-23:頁面切換模型。點結果類 nav 項目時 JS 會在 .main-layout-row
上加這個 class(見 _APP_HEAD_SCRIPT 的 applyActivePage()),對話欄收起、
Canvas 佔滿剩下的寬度。canvas-col 的 flex 要蓋過 Gradio 自己依 scale=7
算出的 inline flex-grow,才會真的補滿收起對話欄後空出來的寬度(同一類
問題 8-E 的 min-width 已經踩過一次,這裡沿用同一個「stylesheet
!important 蓋得過沒加 !important 的 inline style」原則)。
踩坑(新案例,跟 8-B 的 .column flex-wrap 是同一大類但這次是 .row 本身
從沒被設過):.main-layout-row(gr.Row)本身從來沒設過 flex-wrap:nowrap
——chat-col/canvas-col 各自「內部」的 flex-wrap:nowrap 只管它們自己的
子元件怎麼排,不影響 main-layout-row 怎麼排這三欄本身。(訂正 2026-07-30:
這裡原本誤寫成 nav-col 也在這份清單裡,實際上當時 nav-col 從未設過
flex-wrap:nowrap——這正是 Bug 4-B「報告完成後導覽列整組消失」被忽略的
直接原因,已於上方 .nav-col 規則補上,見該處 2026-07-30 註解。)平常
三欄用 flex-grow 精準分配寬度、剛好不超版所以wrap從沒被觸發過;這次
canvas-col 拿掉 max-width 上限、又改用 flex-basis:auto 之後,內容較寬
的表格會把 canvas-col 的 intrinsic 寬度撐到跟 nav-col 放不進同一行,
Gradio 預設的 flex-wrap:wrap 就把 canvas-col 換到 nav-col 下面另起
一行——整個版面變成上下堆疊、頁面被撐到 1200px+ 高,捲動 canvas 內容
時連 nav-col 都跟著被推出視窗外。修法:main-layout-row 本身也要明確
nowrap,不能只顧內部三欄各自的 nowrap。 */
.main-layout-row {
flex-wrap: nowrap !important;
}
.main-layout-row.chat-collapsed .chat-col {
display: none !important;
}
.main-layout-row.chat-collapsed .canvas-col {
flex: 1 1 auto !important;
max-width: none !important;
min-width: 0 !important;
}
/* Phase 8-C 踩坑:結果內容搬進 canvas-scroll 後,內容總高度遠超過
canvas-col 給的固定 height,理論上該讓 canvas-scroll 自己捲動就好;但
exec_log_accordion(技術執行紀錄)被壓縮成只剩標題列高度(22px),內容
反而擠進一個內部小捲動框。原因:CSS flexbox 規範規定「flex 子項的
automatic minimum size」只有在該子項 overflow:visible 時才會採用內容
高度當下限,一旦子項 overflow 不是 visible(Gradio 會自動幫 Accordion
這類元件的外層 wrapper 加上 inline overflow:auto,方便使用者拖曳調整
大小),下限就變成 0,於是在總內容超出容器高度、瀏覽器要收縮子項時,
這個子項就被壓到接近 0——同排的 upload_row/metadata_row/canvas_idx_col
等純 gr.Column 因為維持預設 overflow:visible 沒有這個下限問題,才沒被
壓縮。修法不是去動 Accordion 的 overflow(那是 Gradio 拖曳調整大小的
功能,不想關掉),而是直接讓這個捲動容器裡的所有子項都不參與收縮 */
.canvas-scroll > * {
flex-shrink: 0 !important;
}
/* ── 5d. HOMEPAGE(Phase 8:首頁 4 張情境卡片)──────────────────── */
/* 隱藏版情境按鈕:純作 JS 橋接目標(見 _APP_HEAD_SCRIPT),
點卡片=模擬點這裡的按鈕,Python 端 .click() 綁定完全不動。
gr.Button 的 elem_classes 直接掛在 <button> 標籤本身(不像
gr.HTML() 會多一層 wrapper),所以這裡單純 display:none 就完全
不佔版面,沒有下面 .nav-html-bridge 那個問題。 */
.sc-bridge-btn { display: none !important; }
/* 2026-07-23 踩坑:gr.HTML() 元件的實際內容跟 Gradio 自動產生的外層
wrapper(.block)是兩個不同的 DOM 節點——只把內層內容(例如
#mobile_nav_toggle/#nav_backdrop)設 display:none,wrapper 本身
還是會依自己的預設 min-height/padding 佔用版面高度,疊加上跟相鄰
元件的 flex gap,變成看得到但摸不著原因的空白區塊(千卉截圖回報
頂部/底部大片空白時發現)。第一輪試過 display:contents 沒有真的
清零(懷疑跟 elem_classes 同時疊在 .block 與 .prose 兩層有關,
detail 見 create_app() 裡兩個 gr.HTML() 呼叫旁的完整記錄),改用
最直接可靠的做法:wrapper 直接用 elem_id 鎖定、預設寫死
display:none——一般 block 元素包一個 display:none 的子元素天生
零高度,不用依賴 display:contents 這種較冷門的屬性。JS 對應改成
切換這兩個 wrapper 本身的 display(見 _APP_HEAD_SCRIPT 的
_syncMobileHamburger()),內層 #mobile_nav_toggle/#nav_backdrop
自己原本的桌面/手機斷點 CSS 完全不用動。 */
/* 兩層踩坑疊在一起:①這個 wrapper 帶 Gradio 自己的 "padded" class,
就算內層內容 display:none,wrapper 本身還是會因為 line-height留下
約20px空氣行高②就算加 height:0 讓 wrapper 自己不佔高度,JS 為了
手機斷點把它切成 display:block 時,它仍然是「有在排版流程裡」的
flex 子項,外層 column 的 gap:16px 還是會在它前後插入間距——CSS
flexbox 的 gap 只認「這個子項是不是 display:none」,不管它自己的
box 高度是不是 0。用 devtools 逐一 display:none 排查才抓到這一層,
單靠 height:0 治標不治本。
真正修法:桌面寬度(>900px)這兩個 wrapper 必須是貨真價實的
display:none(徹底退出 flex 排版,不只是零高度),用 !important
蓋掉 JS 可能設的 inline display:block;手機寬度(<=900px)才放行讓
JS 的 block/none 判斷生效。這樣桌面寬度不管 nav-col 存不存在,這兩個
wrapper 永遠不佔用任何 flex 空間。 */
#mobile_nav_toggle_wrap, #nav_backdrop_wrap {
display: none;
}
@media (min-width: 901px) {
#mobile_nav_toggle_wrap, #nav_backdrop_wrap {
display: none !important;
}
}
.homepage-col { max-width: 50rem; margin: 2.5rem auto 3rem; padding: 0 1rem; }
.homepage-hero { text-align: center; margin-bottom: 2.4rem; }
/* 2026-07-30(1-C):業主指定移除首頁第一行「重譯文本量化分析平台」
(原本的 .kicker),HTML 端已拿掉該 <span>,.kicker 本身連同兩側裝飾線
的 CSS 規則一併移除(沒有任何元素會再套用它)。 */
/* 2026-07-27:老師確認「譯彩紛呈:重譯文本分析系統」是大系列品牌名稱,
這一款子產品其實叫「II 分析引擎 SpectraEngine」——原本大系列名稱佔用
最大最搶眼的 h1+漸層,子產品名反而縮在最小的一行,跟實際定位相反。
重新分配視覺權重:大系列名稱降級成小字(series-name),子產品名稱升級
成 h1 大標題+漸層。 */
/* 2026-07-30(1-D/1-E):業主指定放大到 18px/14px(原本 .95rem/.85rem,
換算 16px 根基約 15.2px/13.6px)。直接寫死 px,不用 rem,避免驗收量測
時因根字級假設不同而跟業主指定的精確數字對不上。 */
.homepage-hero .series-name {
margin: .9rem 0 .1rem; font-size: 18px; font-weight: 600;
color: var(--c-text-soft);
}
.homepage-hero .subtitle { margin: 0 0 1.1rem; font-size: 14px; color: var(--c-text-muted); }
.homepage-hero h1 {
margin: 0 0 .6rem; font-size: 2.1rem; line-height: 1.3; font-weight: 700;
/* 同 .app-header .title-line 的漸層文字踩坑(見 _DARK_MODE_HEAD_CSS 上方註解):
background 簡寫在瀏覽器展開成子屬性時 background-image 會解析成
空值,改直接寫 background-image 這個子屬性本身即可。這裡進一步採用
.app-header .title-line 已驗證過、支援巢狀 <span class="en"> 的寫法
(-webkit-text-fill-color 會被子元素繼承,color:transparent 單獨用
在有巢狀 span 時未實測過是否可靠,兩個都寫上比較保險)。 */
background-image: linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%);
-webkit-background-clip: text; background-clip: text;
color: transparent; -webkit-text-fill-color: transparent;
}
/* 2026-07-31(Batch E 第 1 項):業主回報這行沒有跟其他行對齊置中。
量測證實:.lede 是唯一被覆寫成 text-align:left 的行,其餘行
(series-name/subtitle/h1/footnote)都吃 .homepage-hero 本身的
text-align:center。文字方塊(<p> 的 border box)四行寬度其實一致,
問題出在「視覺文字置中」——.lede 文字換行後每行長度不同,
text-align:left 讓每行從方塊左緣起排,量出來的文字視覺中心會比
方塊中心明顯偏左(1280px 寬實測:文字視覺中心 x≈602,其他行
x≈640,偏差近 40px,方向是「偏左」不是業主主觀感受的「偏右」,
可能是她螢幕寬度或縮放比例不同所致)。拿掉這條 text-align:left
覆寫,改回跟其他行一致的置中,四行的文字視覺中心才會真正對齊。 */
.homepage-hero .lede {
margin: 1.8rem 0 0; text-align: center; font-size: var(--font-size-base);
line-height: 1.8; color: var(--c-text-muted);
}
/* 1-B:首頁「開始研究分析」按鈕,進入工作區的唯一入口。這是真正的
gr.Button(不是首頁卡片那種 HTML+隱藏按鈕橋接手法——單一按鈕不需要,
見 create_app() 裡 start_analysis_btn 旁的說明),elem_classes 直接掛在
<button> 標籤本身(同 .sc-bridge-btn 那則註解),用 elem_id 選擇器蓋過
Gradio 預設樣式。藍紫色漸層沿用 h1 已在用的同一組色票(--c-accent/
--c-accent2),不是另外發明新配色。width 用 fit-content+margin auto
置中,不管外層 Gradio 自動包的 wrapper 是否撐滿欄寬都能正確置中。 */
#start_analysis_btn {
display: flex !important;
width: fit-content !important;
margin: 2.2rem auto 0 !important;
padding: .95rem 2.8rem !important;
font-size: 1.05rem !important;
font-weight: 700 !important;
border: none !important;
border-radius: 14px !important;
color: #fff !important;
background-image: linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%) !important;
box-shadow: 0 16px 40px rgba(var(--c-accent2-rgb),.35) !important;
transition: filter 150ms ease, box-shadow 150ms ease !important;
}
#start_analysis_btn:hover {
filter: brightness(1.06);
box-shadow: 0 20px 46px rgba(var(--c-accent2-rgb),.45) !important;
}
/* 3-A 追加:情境卡片搬進工作區後改垂直排列(業主指定「不要 2×2 矩陣」)。
這個 class 現在只有工作區的研究選擇區塊在用(首頁卡片已移除,見 1-A),
直接把兩欄矩陣改成單欄即可,不用另外開一個 class。 */
.scenario-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-top: 1.2rem; }
.scenario-card {
all: unset; box-sizing: border-box; cursor: pointer;
display: flex; gap: 1rem; align-items: flex-start; text-align: left;
padding: 1.3rem 1.4rem;
background: var(--c-surface);
border: 1px solid rgba(var(--c-accent-rgb),.40) !important;
border-radius: 16px;
box-shadow: 0 16px 50px rgba(15,23,42,.20);
transition: border-color 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
}
.scenario-card:hover, .scenario-card:focus-visible {
border-color: rgba(var(--c-accent-rgb),.7) !important;
background: linear-gradient(180deg, rgba(var(--c-accent-rgb),.06) 0%, var(--c-surface) 100%);
}
.scenario-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.scenario-card .sc-index {
flex: none; font-family: 'Crimson Pro', serif; font-weight: 600;
font-size: 1.8rem; line-height: 1; padding-top: .1rem;
color: rgba(var(--c-accent-rgb),.4); transition: color 180ms ease;
}
.scenario-card:hover .sc-index, .scenario-card:focus-visible .sc-index { color: var(--c-accent); }
.scenario-card .sc-body { min-width: 0; display: flex; flex-direction: column; }
.scenario-card .sc-title { margin: 0 0 .35rem; font-size: 1.02rem; font-weight: 700; color: var(--c-text); line-height: 1.4; }
.scenario-card .sc-desc { margin: 0; font-size: var(--font-size-base); color: var(--c-text-muted); line-height: 1.65; }
.homepage-secondary { display: flex; justify-content: center; margin-top: 2rem; }
.homepage-secondary .placeholder-link {
display: inline-flex; align-items: center; gap: .4rem;
font-size: var(--font-size-base); color: var(--c-accent); cursor: pointer;
}
.homepage-secondary .placeholder-link:hover { text-decoration: underline; }
.homepage-secondary .placeholder-link svg { width: 1rem; height: 1rem; opacity: 1; }
.homepage-footnote { margin-top: 2rem; text-align: center; font-size: .82rem; color: var(--c-text-subtle); }
@media (prefers-reduced-motion: reduce) { .scenario-card { transition: none; } }
/* ── 5e. NAV-COL 收合/手機漢堡抽屜(Phase 8-E)────────────────── */
/* gr.Button(icon=...) 這版 Gradio 實測是渲染成 <img src="...">,不是行內
<svg>,CSS currentColor 對它沒有作用(img 不會繼承文字顏色)。因此
assets/icons/*.svg 檔案裡直接把 stroke 寫死成淺色模式的 --c-text-muted
(#475569),深色模式改用 filter 把整張圖反相+去彩,逼近深色模式的
--c-text-muted (#94a3b8) 色調,這是唯一能讓「檔案路徑圖示」跟著主題
變色的辦法(不用另外準備一份深色版 svg 檔案)。 */
img.button-icon { opacity: .82; }
.dark img.button-icon {
filter: grayscale(1) invert(1) brightness(1.55) contrast(.85);
}
/* nav-col 收合/手機抽屜控制鈕:桌面收合鈕與手機關閉鈕互斥顯示,
預設(>900px)只顯示收合鈕;@media (max-width:900px) 那組規則會反過來
(見下方)。這兩顆是純 gr.HTML() 刻的按鈕,行內 SVG 用 _icon(),
currentColor 正常運作,不受上面 img.button-icon 的限制。 */
/* 2026-07-24:頂端一列=品牌字標(左)+ 明暗/收合鈕(右)。原本只有一個
靠右的收合鈕(.nav-collapse-row),改成 space-between 兩端對齊;字標與
下方「開始新分析」的間距(5-D)由這裡的 margin-bottom 控制,拉近一點。 */
/* Gradio 幫 gr.HTML 外層 .block/.html-container 掛了 ~12px 預設 padding,
會讓頂端列(品牌+收合鈕)比下方按鈕多縮 12px、右緣多 12px 空隙(就是
千卉量到的那 20+px)。把這個 wrapper 的 padding 歸零,頂端列才跟按鈕
一樣貼齊 nav-col 的 16px 內距框。 */
#nav_top_html, #nav_top_html > .html-container { padding: 0 !important; margin: 0 !important; }
.nav-top-row {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: .35rem;
}
/* 2026-07-30(2-B 第 1/2 項):頂端列現在只剩品牌字標——明暗切換/收合鈕
搬到欄底新的 .nav-bottom-toolbar(見下方),.nav-top-actions 這個右側
容器已無內容可放,但選擇器留著不刪:nav-top-row 的 justify-content:
space-between 沒有第二個 flex item 時退化成單純靠左,不影響外觀,刪掉
反而要動兩處收合樣式的選擇器,徒增風險。 */
.nav-top-actions { display: flex; align-items: center; gap: 2px; }
/* 2026-07-31(Batch E 第 2 項):業主改主意,品牌字標從 3 行改 2 行
(「譯彩紛呈 II 分析引擎」/「SpectraEngine」),字級統一 1.1rem
(原本 3 行是 .92rem/.78rem/.66rem 遞減,2 行不再需要遞減層次)。
原本靠 flex column 讓三行各自不同字級仍能整齊靠左對齊
(align-items:flex-start)的理由,2 行同字級一樣適用,結構不用改。
漸層維持套在同一個外層元素上(不是各行各自 background-clip):
background-image 畫在 .nav-brand 整個 box 上,兩行文字一起被裁切顯示,
漸層才會像流過兩行的單一色帶,而不是每行各自重新起始一次相同的漸層
(後者會讓每行左端都是同一個顏色,視覺上像兩條各自獨立的色塊)。 */
.nav-brand {
display: flex; flex-direction: column; align-items: flex-start;
font-weight: 700; letter-spacing: .2px; line-height: 1.2;
background-image: linear-gradient(120deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%);
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; color: transparent;
}
.nav-brand .nav-brand-line1, .nav-brand .nav-brand-line2 { font-size: 1.1rem; }
.nav-drawer-close-row { display: none; justify-content: flex-end; margin-bottom: .5rem; }
.nav-collapse-toggle, .nav-drawer-close, .mobile-nav-toggle, .nav-icon-btn {
all: unset; box-sizing: border-box; cursor: pointer;
display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 32px; border-radius: 8px;
color: var(--c-text-muted);
transition: background-color 150ms ease-out, color 150ms ease-out;
}
.nav-collapse-toggle:hover, .nav-drawer-close:hover, .mobile-nav-toggle:hover, .nav-icon-btn:hover {
background: rgba(var(--c-accent-rgb),.08); color: var(--c-text);
}
.nav-collapse-toggle:active, .nav-icon-btn:active {
background: rgba(var(--c-accent-rgb),.16);
}
/* 明暗切換鈕:兩個圖示常駐 DOM,只用 CSS 切換顯示(預設淺色→顯示月亮,
代表「點了會變深色」)。JS 只改 data-theme-icon,不動 DOM 結構。 */
#theme_toggle .ico-moon { display: inline-flex; }
#theme_toggle .ico-sun { display: none; }
#theme_toggle[data-theme-icon="dark"] .ico-moon { display: none; }
#theme_toggle[data-theme-icon="dark"] .ico-sun { display: inline-flex; }
.nav-collapse-toggle:focus-visible, .nav-drawer-close:focus-visible,
.mobile-nav-toggle:focus-visible, .nav-icon-btn:focus-visible {
outline: 2px solid var(--c-accent); outline-offset: 2px;
}
.nav-collapse-toggle svg { transition: transform 200ms ease-out; }
.nav-col.collapsed .nav-collapse-toggle svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
.nav-collapse-toggle svg { transition: none; }
.nav-col { transition: none !important; }
}
/* 桌面收合成窄 icon bar:文字用 font-size:0 隱藏(不是 display:none,
避免影響按鈕的 accessible name/可點擊區域),圖示尺寸另外鎖死不受
font-size 影響。統計比較分析的 4 個子按鈕是純文字、沒有各自圖示,
收合時整組 Accordion 直接隱藏(窄版 icon bar 塞不下二層選單);重置
按鈕若在收合狀態被點擊,JS 會先自動展開 nav-col 再讓確認彈窗顯示
(見 _APP_HEAD_SCRIPT setNavCollapsed 呼叫處),避免確認彈窗擠在
60px 窄欄裡。這整組「收合外觀」規則只在桌面寬度生效(min-width:901px)
——手機沒有「窄 icon bar」這個中間態,nav-col 在手機是 off-canvas
抽屜,要嘛整個滑出來、要嘛整個收在畫面外,即使 localStorage 殘留
collapsed=1 也不該套用這組窄化樣式(見下方 900px 媒體查詢改用
position:fixed 抽屜,兩者規則互斥,不需要互相 revert)。 */
.nav-col { transition: flex-basis 200ms ease-out, max-width 200ms ease-out; }
@media (min-width: 901px) {
/* gr.Column(min_width=200) 會在元件上寫死 inline style="min-width:200px",
這個 inline min-width 會蓋掉外部 stylesheet 的 max-width(flexbox
規範:min-width 與 max-width 衝突時 min-width 優先),光設
max-width:60px 收合欄寬不會有效果,撐在 200px 不動——必須額外用
!important 覆寫 min-width 本身(stylesheet 的 !important 蓋得過
沒加 !important 的 inline style,這點跟蓋不過 inline !important
不一樣)。 */
.nav-col.collapsed { flex: 0 0 60px !important; max-width: 60px !important; min-width: 60px !important; }
/* 收合成 60px icon bar:頂端列只剩品牌字標(已無其他項目可置中,
justify-content:center 留著也不影響),字標本身在 60px 裡塞不下、
隱藏起來,展開後才出現。 */
.nav-col.collapsed .nav-top-row { justify-content: center; }
.nav-col.collapsed .nav-brand { display: none !important; }
/* 2026-07-30(2-B 第 2 項):明暗切換/收合鈕搬到欄底 .nav-bottom-toolbar
後,收合時的置中邏輯也跟著搬過來——明暗切換鈕在 60px 裡放不下(跟收合
鈕擠在一起會超寬),一起隱藏,只留收合鈕自己置中,展開後才恢復兩顆
並排靠右(見 .nav-bottom-toolbar 規則本身)。
這裡一定要加 !important:.nav-bottom-toolbar 本身的 justify-content:
flex-end 也掛了 !important(本檔到處都是這個坑,CSS cascade 裡
!important 不管 specificity 高低都贏過沒加的規則),沒加會被蓋掉、
收合時仍然靠右對齊,量出來就會跟預期的「置中」對不上。 */
.nav-col.collapsed .nav-bottom-toolbar { justify-content: center !important; }
.nav-col.collapsed #theme_toggle { display: none !important; }
/* .reset-confirm 已於 2026-07-24 搬出 nav-col 改成全螢幕 modal,
不再需要在收合狀態下隱藏;.result-nav-accordion 同日隨「統計比較
分析」手風琴一起移除。 */
.nav-col.collapsed .result-nav-divider { display: none !important; }
/* 選擇器裡的 #result_nav_col 是為了贏過下方 nav-underline 樣式的
`#result_nav_col .result-nav-btn`(1 個 id + 1 個 class = 1,1,0)。
兩邊都掛 !important 時由 specificity 決勝,純 class 的
`.nav-col.collapsed .result-nav-btn`(0,3,0)會輸,收合後的置中
與 padding 就不會生效。加上 id 祖先後是 1,3,0,穩定勝出。 */
/* 2026-07-24 回歸修正:為了統一字級加了 `#reset_btn`(1,0,0)之後,純 class
的 `.nav-col.collapsed .reset-btn`(0,3,0)反而輸掉,收合時「開始新分析」
的文字不再被 font-size:0 隱藏、把 60px 窄欄撐破。這裡改用 `#reset_btn`
當選擇器(1,2,0)贏回來。呼應本檔既有註記:任何為了蓋過 Gradio 而加 id
的改動,都要回頭盤同名 class 的其他規則有沒有被連帶壓掉。 */
.nav-col.collapsed #reset_btn,
.nav-col.collapsed #nav_guide_btn,
.nav-col.collapsed #nav_about_btn,
.nav-col.collapsed #result_nav_col .result-nav-btn {
justify-content: center !important;
padding: .45rem !important; /* 2026-07-24 收窄:原本 .6rem 把圖示擠到 8px(見下方註解) */
font-size: 0 !important;
}
/* 千卉回報「收合後看不見 icon」的真因(2026-07-24 量出來才發現):不是顏色
太淡,是圖示被擠成 8px。60px 欄寬扣掉 nav 的 16px 左右內距只剩 28px,再扣
按鈕自己的 .6rem 左右內距(19.2px),留給圖示的空間只剩 ~9px,而 img 是
flex 子項、預設可被壓縮,就一路縮到 8px。
修法三件:①收合時欄內距縮到 8px(展開仍維持 16px)②按鈕內距縮到 .45rem
③圖示 flex:none 明確不可壓縮。可用寬 = 60-16-14.4 ≈ 29.6px > 22px。
另外 opacity 拉到 1(預設 .82 沒有文字陪襯時偏淡);disabled 的淡化來自
按鈕本身 opacity:.38(父層),不受影響。 */
.nav-col.collapsed { padding-left: 8px !important; padding-right: 8px !important; }
.nav-col.collapsed .button-icon {
width: 22px !important; height: 22px !important;
flex: none !important; opacity: 1 !important;
}
.nav-col.collapsed .result-nav { padding-top: .8rem !important; }
}
/* 2026-07-24:這裡原本有一整組 #nav_stat_accordion 樣式(CSS ::before 掛
chart-bar 圖示、label-wrap 的 justify-content 修正、深色模式 filter),
隨「統計比較分析」Accordion 一起移除。四個子頁現在是平列的一般導覽項目,
圖示走 gr.Button(icon=) 那條路,不需要這組特例。 */
/* 手機漢堡開關(mobile_nav_toggle)與抽屜遮罩(nav_backdrop):兩者都是
放在 workspace_row 外層、一開始就 visible=True 的獨立元件(理由見
create_app() 該處註解),CSS 預設整個不顯示,只在手機寬度且 nav-col
已掛載(情境已選定)時,由 _APP_HEAD_SCRIPT 的 _syncMobileHamburger()
切換 display,避免首頁或桌面寬度下平白多一顆浮動按鈕。 */
.mobile-nav-toggle {
display: none;
position: fixed; top: 14px; left: 14px; z-index: 45;
background: var(--c-surface) !important;
border: 1px solid var(--c-border-strong) !important;
box-shadow: 0 4px 16px rgba(15,23,42,.18);
}
/* 2026-07-30:抽屜展開時藏起浮動漢堡鈕,見 setMobileDrawer() 註解——它跟
nav-col 疊在同一個左上角,蓋住品牌字標。抽屜本身已有 nav_drawer_close
可以關閉,這顆鈕開著時沒有作用。 */
.mobile-nav-toggle--hidden { display: none !important; }
.nav-backdrop {
display: none;
position: fixed; inset: 0; z-index: 30;
background: rgba(2,6,23,.5);
opacity: 0; pointer-events: none;
transition: opacity 200ms ease-out;
}
.nav-backdrop.visible { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .nav-backdrop { transition: none; } }
@media (max-width: 900px) {
/* 手機寬度:nav-col 不再跟著 chat-col/canvas-col 上下堆疊(那條規則
見「17. RESPONSIVE」,已把 nav-col 排除),改成 off-canvas 抽屜,
預設收在畫面左側外面,漢堡鈕/背景遮罩點擊才滑入。 */
.nav-col {
position: fixed !important;
top: 0; left: 0; bottom: 0;
width: 78vw !important; max-width: 300px !important;
flex: none !important; height: 100vh !important;
overflow-y: auto !important;
background: var(--c-surface) !important;
box-shadow: 4px 0 24px rgba(15,23,42,.28) !important;
z-index: 40;
transform: translateX(-100%);
transition: transform 220ms ease-out;
padding: 1.1rem 1rem !important;
}
.nav-col.mobile-open { transform: translateX(0) !important; }
/* 桌面收合是縮寬窄 icon bar 那組規則整段包在 @media(min-width:901px)
裡(見「5e」上方),手機寬度完全不會命中,這裡不需要另外
revert——即使 localStorage 殘留 collapsed=1,.nav-col.collapsed
在手機寬度就是普通展開的抽屜外觀,不會被窄化。 */
/* 手機抽屜沒有「窄 icon bar」中間態,收合鈕無意義 → 只隱藏收合鈕本身,
品牌字標與明暗切換鈕仍留在抽屜頂端;關閉抽屜改用下方的 x(drawer-close)。 */
#nav_collapse_toggle { display: none !important; }
.nav-drawer-close-row { display: flex !important; }
.mobile-nav-toggle { display: inline-flex; }
.nav-backdrop { display: block; }
}
/* ── 6. UPLOAD AREA(醒目卡片)─────────────────────────────────── */
/* 注意:max-width + margin auto 是舊版單欄置中版面留下的樣式,現在
upload-zone 是右側 action-col 裡的元件,要跟同一欄的 scenario-buttons
同寬(撐滿欄寬),不能再置中收窄 */
/* 2026-07-23 千卉實機回饋:原本的強調色外框+大範圍陰影+漸層底色讓這張
卡片像一個浮起來的彈窗。改成扁平——外框、陰影、底色全部拿掉,只保留
圓角與內邊距維持內容的呼吸空間。彈出動畫保留,它負責的是「這塊剛剛才
出現」的狀態提示,跟視覺重量無關。 */
/* 2026-07-31(Batch E 第 4-c 項):業主附圖標註「文本上傳」標題(這裡是
::before 產生的「📂 上傳已斷詞文本」偽標題)左緣比步驟列第一顆藥丸左緣
往右縮排。實測量出來:canvas-scroll 本身已經統一給所有直屬子項 20px
左內距(跟 .app-header 的 20px 左內距是同一個基準線,兩者天生就對齊),
問題是這裡自己又疊了一層 1.5rem(24px)左右內距,把內容多推了 24px。
拿掉左右內距,改吃 canvas-scroll 那層統一的 20px,跟步驟列基準線對齊
(同時也順手把上方留白從「zone 自己的 1.4rem 頂部內距」壓掉,見下方
padding 從 "1.4rem 1.5rem" 改成 "0 0 1.4rem" 的異動——只留底部內距
維持内容與下一個區塊的呼吸空間,頂部改靠 margin-top 的 .6rem 加上標題
本身既有的 margin 提供間距即可,不用內外雙重留白)。 */
.upload-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem !important;
margin: .6rem 0 0 !important;
width: 100% !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
@keyframes popupIn {
from { opacity: 0; transform: translateY(-8px) scale(.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.upload-zone::before {
content: "📂 上傳已斷詞文本";
display: block;
font-size: 1.05rem;
font-weight: 700;
color: var(--c-accent-strong);
/* 2026-07-31(Batch G 第 2 項):業主回報「上傳已斷詞文本」標題上方
留白比其他階段標題少。實測(header 底緣到標題文字頂緣的距離):
研究選擇 h3 ≈ 32px、語料 Metadata 說明 h3 ≈ 68px、這裡(::before
偽元素)只有 ≈ 9.6px(等於 .upload-zone 自己的 margin-top .6rem,
偽元素本身預設沒有 margin-top,不像另外兩個標題是真的 <h3>、天生
帶 Gradio markdown prose 的標題頂部留白 ≈22.4px)。收斂目標訂在
32px(貼近「研究選擇」現有數值,改動最小),這裡補上 1.4rem
(22.4px)頂部留白,湊出跟其他標題一致的視覺留白。 */
margin-top: 1.4rem;
margin-bottom: .9rem;
letter-spacing: .02em;
}
/* 原本這裡有 .upload-zone:hover/:focus-within 加深 border-color 的規則,
2026-07-23 卡片扁平化拿掉外框後就沒有東西可加深了,一併移除。 */
/* 對話輸入框(Phase 8:不再負責上傳,改回純展示用的常駐輸入框):
刻意跟上面「醒目卡片」風格的 .upload-zone 拉開視覺重量差異,
不用強調色框線/陰影/彈出動畫,也不掛 ::before 的標題文字,
避免使用者誤以為這裡也是一個可操作的上傳入口 */
/* 2026-07-24(仿 Claude):輸入框貼齊欄邊——拿掉外框、圓角、內距、上方
margin(原本 .6rem 是它跟對話視窗的間隙,也一起拿掉)。 */
.upload-zone-plain {
border: none !important;
border-radius: 0 !important;
padding: 0 !important;
margin: 0 !important;
/* width 用 auto 不用 100%:下方 #chat_upload_zone 會給左右 margin 讓面板
內縮,若寫死 100% 會連同 margin 一起超出欄寬(實測右側溢出 15px)。 */
width: auto !important;
}
/* 輸入框本體:底色改成跟氣泡同一階(白/深色淺一階),去掉 Gradio 預設的
外框與圓角,四邊貼齊 chat-col。頂端用 1px 細線跟對話視窗分界(取代原本
靠間隙分隔)。 */
#chat_upload_zone .multimodal-textbox,
#chat_upload_zone .input-container,
#chat_upload_zone textarea {
background: var(--c-surface) !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* 2026-07-24 千卉:輸入框右邊與下面要有留白。先前的 16px 只是「面板內部的
內距」,白色面板本身仍貼齊欄邊(實測面板距欄 左1 右2 下1)。改成面板整個
往內縮 16px,白底與欄邊之間真的空出來;不再需要頂端分隔線(面板浮在欄底
色上,本身就是分界)。Gradio 的 .form 外層帶淡藍底+框線也一併中性化,
否則會在面板外圍多一圈色帶。 */
#chat_upload_zone { margin: 0 16px 16px !important; border-top: none !important; }
#chat_upload_zone .form {
background: transparent !important;
border: none !important;
}
/* 2026-07-23:chat-col 改成 gap:1rem 的 flex 欄,跟 chatbot 之間的留白
已經由 gap 統一處理,這裡不用再疊加 margin-top(舊註解提到的「技術
執行紀錄」分組已在 Phase 8-B 搬去 canvas-col,跟這裡無關,是過期
說明)。改成跟 .main-chat 同一套 5:1 flex-grow 比例分配高度。 */
/* 2026-07-24(#15):輸入框改「自然高度、貼齊底部」,不再用 flex:1 佔 1/7
欄高——原本被撐成 153px 但內容只佔一小塊,底下留 61px 空縫(千卉標的
下方縫隙)。對話視窗改 flex:1 1 auto 吃掉剩餘全部高度,輸入框 flex:0 0 auto
縮到內容高度。 */
#chat_upload_zone {
flex: 0 0 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
/* Gradio MultimodalTextbox 的內層 padding 左右不對稱(full-container 12px、
input-wrapper 右 4px、textarea 0)造成左 14/右 18 不一致(#15)。統一:
full-container 對稱 12px、input-wrapper 歸零,textarea 左右緣一致。 */
/* 2026-07-24 千卉:左/下要跟右邊一樣的留白。原本 full-container 上下 10px、
左右 12px,加上內層殘留造成 textarea 距欄 左14/右15/下17 不一致。改成
四邊統一 14px,內層一律歸零,四邊留白就一致。 */
#chat_upload_zone .full-container { padding: 14px !important; }
#chat_upload_zone .input-wrapper,
#chat_upload_zone .input-row { padding: 0 !important; margin: 0 !important; }
#chat_upload_zone textarea { padding: 0 !important; }
/* textarea 只有 67px 但 input-row 是 72px(拿掉送出鈕後留下的槽位高度),
下緣多 5px 空隙、跟左右留白對不齊。讓 textarea 撐滿整列高度即可。 */
#chat_upload_zone .input-row { align-items: stretch !important; }
#chat_upload_zone textarea { height: 100% !important; }
/* 格式要求直接寫在上傳卡片裡,跟卡片本身有明顯區隔但仍在同一張卡片內,
使用者不用往上捲回對話視窗找 AI 說過的規則 */
/* 已經在 upload-zone 卡片裡了,卡片本身就有外框,這裡不再加第二層框線/底色。
注意:Gradio 把 elem_classes 同時蓋在外層 wrapper(.block.upload-hint)
跟內層 markdown 內容(.prose.upload-hint)兩個 div 上,凡是用裸的
.upload-hint 當 selector,padding/margin/border 都會在兩層各套一次、
疊加成兩倍間距或雙重邊框(已踩坑兩次:先是雙重分隔線,後是跟下面拖放框
距離過遠)。統一寫法:只在 .block.upload-hint(外層)設定間距,
.prose.upload-hint(內層)明確歸零,字級/顏色類不影響版面的規則才維持
用裸 .upload-hint(重複套用無副作用) */
.upload-hint.block {
padding: 0 0 .9rem !important;
margin-bottom: .9rem !important;
}
.upload-hint.prose {
padding: 0 !important;
margin: 0 !important;
}
.upload-hint {
font-size: var(--font-size-base) !important;
}
.upload-hint p, .upload-hint li { font-size: var(--font-size-base) !important; color: var(--c-text-soft) !important; }
.upload-hint strong { color: var(--c-accent-strong) !important; }
/* 同陷阱 13 第 3 點:<span class="md"> 預設 inline 卻裝 block 的 <p>,
會在最前面產生依自己字級算出的隱形空隙,這裡一併修正 */
.upload-hint .md {
display: block !important;
font-size: var(--font-size-base) !important;
}
.upload-actions {
margin-top: 1rem !important;
gap: .6rem !important;
}
.upload-actions button { border-radius: 10px !important; font-weight: 700 !important; }
/* 「回首頁」連結(原「開始新分析」按鈕,2026-07-30 改名+改視覺語彙)與其
確認對話框:換情境/回首頁共用同一套機制,elem_id="reset_btn" 與後端
wiring 完全不變,只是文案/圖示/樣式改了。
千卉這輪指定:樣式要跟 #result_nav_col .result-nav-btn 那組「文字連結」
一致(無邊框、無底色、置左),不要另創一套——做法不是新增一個 class,
是把 #reset_btn 併進下方 #result_nav_col .result-nav-btn 那幾條共用規則
的選擇器清單(見該處),這裡只留 margin-top(它在 nav-items-scroll 裡
排在已隱藏的 scenario-buttons 之後,用來跟上面留出一點呼吸空間,
result-nav-btn 本身沒有這個需求,所以留在這裡單獨設,不搬過去)。 */
.reset-btn { margin-top: .8rem !important; }
/* 2026-07-24:確認框從 nav-col 內嵌卡片改成置中 modal。
刻意不對 overlay 設 display——Gradio 的 visible=False 就是靠 display:none
生效,這裡若寫 display:flex !important 會把「隱藏」本身蓋掉,對話框變成
永遠開著。改用「維持 Gradio 給的 flex 排版 + 面板自己 margin:auto」達成
水平垂直置中,完全不碰 display 這個屬性。 */
.reset-confirm-overlay {
position: fixed !important;
inset: 0 !important;
z-index: 120 !important;
padding: 1.5rem !important;
background: rgba(15, 23, 42, .55) !important;
backdrop-filter: blur(2px);
}
.reset-confirm-panel {
margin: auto !important; /* flex 容器內上下左右皆置中 */
width: 100% !important;
max-width: 400px !important;
flex: none !important; /* 不被 overlay 的 flex 拉伸成滿版高 */
gap: 1.2rem !important;
padding: 1.5rem !important;
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
box-shadow: 0 20px 50px rgba(15, 23, 42, .3) !important;
}
/* 深色模式:頁面本身已經是深色(--c-surface #0d1526),同一階深灰疊上去
幾乎看不出後面被壓暗,遮罩形同不存在。改用更黑的值才有「後面那層被關掉」
的分離感。兩種寫法都給:.dark 是 Gradio 切主題時掛的 class,媒體查詢則
對應本檔既有的 prefers-color-scheme token 機制(見 _DARK_MODE_HEAD_CSS)。 */
.dark .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
@media (prefers-color-scheme: dark) {
:root:not(.light) .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
}
.reset-confirm-panel .reset-confirm-text p { margin: 0 !important; }
/* 橫式:兩顆按鈕同寬並排,窄視窗也不換行 */
.reset-confirm-actions {
flex-wrap: nowrap !important;
gap: .7rem !important;
}
.reset-confirm-actions button {
border-radius: 10px !important;
font-weight: 700 !important;
}
/* 2026-08-26:「關於」面板沿用 .reset-confirm-overlay 同一套置中 modal
機制(同一個 elem_classes 掛在 overlay 上),但內容是說明文字不是
一兩句確認詞,.reset-confirm-panel 的 400px 版寬會把整段文字擠成
窄豆腐塊,所以另開 .about-panel 加寬;內容也比確認框長,視窗矮的時候
(筆電、手機橫向)要能捲動,不能被 overlay 裁掉看不到下半段。文字
對齊也從確認框的置中改左靠——長段落置中會很難讀。 */
.about-panel {
margin: auto !important;
width: 100% !important;
max-width: 560px !important;
max-height: min(80vh, 640px) !important;
overflow-y: auto !important;
flex: none !important;
gap: 1rem !important;
padding: 1.6rem 1.8rem !important;
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
box-shadow: 0 20px 50px rgba(15, 23, 42, .3) !important;
text-align: left !important;
}
.about-panel h2, .about-panel h3 {
text-align: left !important;
}
.about-panel hr {
border: none !important;
border-top: 1px solid var(--c-border-strong) !important;
margin: .6rem 0 !important;
}
.about-panel .about-close-row {
display: flex !important;
justify-content: flex-end !important;
}
.about-panel .about-close-row button {
border-radius: 10px !important;
font-weight: 700 !important;
}
/* Phase 8-D:結果導覽(情境選定後顯示在開始新分析按鈕下方),純文字
標籤(暫不用圖示,等下一輪視覺打磨補 Heroicons SVG),維持跟
scenario-buttons 一致的「貼齊左邊、無強調色底」低視覺重量風格,
避免搶過主要操作按鈕的注意力 */
/* 左右留白已上移到 .nav-col(見該處註解),這裡不再重複設定,避免疊加成
雙倍縮排。上緣的 border-top 因此會跟著欄位的 padding 內縮 8px,跟下方
.result-nav-divider 本來就有的內縮一致。 */
.result-nav {
margin-top: 1rem !important;
padding-top: 1rem !important;
border-top: 1px solid var(--c-border-strong) !important;
gap: .3rem !important;
}
/* 2026-07-23:導覽項目改用 Bootstrap 5.3 `.nav-underline` 的視覺模型
(getbootstrap.com/docs/5.3/components/navs-tabs/)——未選取的項目是
純文字,沒有邊框也沒有底色;選取的項目只換文字顏色再加一條粗線。官方
的線畫在下緣(橫向分頁用),這裡是垂直導覽,依慣例改畫在左緣。
千卉回饋原本「看起來像很多按鈕」的真正原因不是這裡的樣式寫錯,而是
下面這個選擇器少了 id 祖先:Gradio 自己的 secondary 按鈕樣式(Svelte
編譯出 button.svelte-xxx.secondary 這種 type+class 組合)specificity
比純 class 高,即使掛 !important 也贏不了,所以 border:none 一直沒生效、
Gradio 預設的框線與底色整個透出來。子項目當初已經用 #result_nav_col
修過(見下方註解),主項目漏掉了,這次補上。
2026-07-30:「回首頁」(#reset_btn)併入這組選擇器清單,跟 nav_top_btn
(AI對話)等結果導覽項目共用一模一樣的視覺語彙。#reset_btn 不在
#result_nav_col 底下,直接用 id 自己的 specificity(1,0,0)就夠贏過
Gradio 的 button.svelte-xxx.secondary,不需要額外的祖先。 */
#result_nav_col .result-nav-btn,
#reset_btn,
#nav_guide_btn,
#nav_about_btn {
width: 100% !important;
justify-content: flex-start !important;
text-align: left !important;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
/* 2026-07-24(5-B):改成 ChatGPT 側欄那種「填色」互動模型——未選純文字、
hover 淡背景、按下更深、選取淡主色背景+主色文字。圓角讓填色區塊內縮、
不貼齊欄邊。拿掉原本 Bootstrap nav-underline 的左緣線(千卉指定改填色)。 */
#result_nav_col .result-nav-btn,
#reset_btn,
#nav_guide_btn,
#nav_about_btn {
padding: .5rem .7rem !important;
font-size: var(--font-size-base) !important;
font-weight: 500 !important;
border-radius: 8px !important;
color: var(--c-text-soft) !important;
transition: background-color .15s ease-out, color .15s ease-out !important;
}
#result_nav_col .result-nav-btn:hover:not(:disabled),
#reset_btn:hover:not(:disabled),
#nav_guide_btn:hover:not(:disabled),
#nav_about_btn:hover:not(:disabled) {
background: rgba(var(--c-accent-rgb),.08) !important;
color: var(--c-text) !important;
}
#result_nav_col .result-nav-btn:active:not(:disabled),
#reset_btn:active:not(:disabled),
#nav_guide_btn:active:not(:disabled),
#nav_about_btn:active:not(:disabled) {
background: rgba(var(--c-accent-rgb),.16) !important;
}
/* 歷史紀錄:這裡原本有一整組 .result-nav-subbtn 樣式,把統計比較分析底下
的四頁做成「小一號字+灰一階+真縮排」的子選單(縮排寫法是 2026-07-23
從 Bootstrap 官方 Sidebars 範例的 .btn-toggle-nav a 移植過來的,用
margin-left 真的把元素往右推、寬度跟著縮,而不是只用 padding-left 讓
文字位移)。同日千卉決定四頁改用跟其他導覽項目一模一樣的樣式並各自配
圖示、左緣齊平,這組樣式整個不再需要,連同 class 一起移除。
Gradio secondary 按鈕邊框那個 specificity 坑不會因此消失——它現在由
上方 `#result_nav_col .result-nav-btn` 那組共用規則一併處理。 */
/* 2026-07-24:這裡原本有 .result-nav-accordion 與其 .label-wrap 的樣式
(拿掉 Gradio .padded 的水平 12px、標題列對齊其他導覽項目),隨
「統計比較分析」Accordion 一起移除。 */
/* 2026-07-23:頁面切換模型——目前使用者所在的那一頁在導覽列高亮,
applyActivePage() JS 幫對應按鈕加/移除這個 class。視覺先求「看得出
選中誰」的基本可用,細部樣式(跟 Bootstrap 借來的分組/層級規範)留給
下一輪 nav-col 視覺重做一起處理,這裡不重複做兩次。 */
#result_nav_col .result-nav-btn.active {
background: rgba(var(--c-accent-rgb),.14) !important;
color: var(--c-accent-strong) !important;
font-weight: 700 !important;
}
/* 技術執行紀錄:低優先度診斷工具,跟上面的分析結果用分隔線+降低視覺
權重(字級與顏色都比其他導覽項目再低一階)區隔開來 */
.result-nav-divider {
height: 1px;
background: var(--c-border-strong);
margin: .5rem .3rem;
}
/* 同樣要帶 #result_nav_col,否則純 class(0,1,0)會被上方
`#result_nav_col .result-nav-btn`(1,1,0)的字級/顏色蓋掉,
技術執行紀錄就不再是降權的樣子 */
/* 2026-07-24 千卉:導覽列所有項目字級一致(以「AI對話」的 .88rem 為基準)。
技術執行紀錄原本 .82rem 比其他項目小一階,改回 .88rem;它的「低優先度」
仍靠顏色(--c-text-muted)與上方分隔線表達,不再靠縮小字級。 */
/* 2026-08-12:「使用說明」加在 nav-items-scroll 最後一項之後,發現它被推到
整個捲動區的最底部——Gradio 給 gr.Column 的預設是 flex:1 1 auto,
result_nav_col 因此把剩餘高度全部吃掉(實測長到 729px,內容其實只有
400 出頭)。業主要的是「最後一個項目下方」,不是「釘在欄位底部」,
所以讓它只佔內容高度,使用說明就會緊跟在技術執行紀錄下面。 */
#result_nav_col {
flex: 0 0 auto !important;
}
#result_nav_col .result-nav-btn--muted,
#nav_guide_btn.result-nav-btn--muted,
#nav_about_btn.result-nav-btn--muted {
font-size: var(--font-size-base) !important;
font-weight: 500 !important;
color: var(--c-text-muted) !important;
}
/* 語料 Metadata 說明表單卡片:跟 .upload-zone 同批扁平化(2026-07-23),
兩者是同一種「Canvas 裡的操作區塊」,樣式要保持一致 */
/* 2026-07-31(Batch E 第 4-c 項):同 .upload-zone 那則註解,左右內距
歸零改吃 canvas-scroll 統一的 20px(原本 1.6rem 額外內距造成標題左緣
跟步驟列對不齊),頂部內距也歸零壓掉多餘留白,只留底部內距。 */
/* 2026-07-31(Batch F):Batch E 把左內距整個歸零,副作用是標題文字直接
貼死在 canvas-scroll 的 20px 基準線上(業主實測回報「標題字貼到左邊
邊框了」)。這裡補回左內距 1.5rem(沿用 .analysis-panel 內距同一套
數值,不另創),只補左邊、不動右邊/頂部/底部,維持 Batch E 顧到的
「zone 本身的框(雖然這塊沒有可視邊框,但量測用的 bounding box)左緣
仍落在 canvas-scroll 20px 基準線上」,只是內容(標題與底下的表單/
卡片)整體再往內縮 1.5rem,跟步驟列基準線的關係從「內容緊貼」變回
「內容有呼吸空間」。 */
/* 2026-07-31(Batch G 第 2 項):業主回報「語料 Metadata 說明」標題上方
留白比其他階段標題多。實測發現根因不在 .metadata-zone 自己的
margin-top(跟 .upload-zone/.scenario-select-zone 用同一份 .6rem),
而是它上面那個兄弟節點 upload_progress_zone(3-C 加的上傳進度卡片,
elem_id="upload_progress_zone"):格式驗證通過、換頁到語料說明時,
它的內容被清空成空字串(見 on_upload_confirm 最後一個 yield),但
Gradio 幫 gr.HTML() 包的 .html-container 就算內容是空字串,仍保留
預設的 10px 12px 內距,等於平白多墊了 20px 高度的空白卡片在語料
說明標題正上方,這是量出來的留白比研究選擇/文本上傳多出一大截
的真正原因(不是 .metadata-zone 自己的留白設定有問題)。
修法:不改動 upload_progress_zone 那個共用元件的空狀態渲染(那是
跟「分析運算」進度卡共用的機制,改了會牽動另一個地方,超出這批
範圍),改用負 margin-top 直接讓 .metadata-zone 往上收,抵銷掉這段
多出來的空白卡片高度,讓最終畫面上的留白收斂到跟其他三個標題一致
的 32px(研究選擇的現有數值,改動最小)。這個負值只在「upload_
progress_zone 已清空、metadata-zone 剛顯示」這個時間點生效,兩者
在畫面上都沒有可見邊框/背景,往上收不會露出接縫或裁切。 */
.metadata-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem 1.5rem !important;
margin: -1.65rem 0 1rem !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
.metadata-zone .gr-box, .metadata-zone .form { background: transparent !important; }
/* 3-A:研究選擇區塊(工作區第 1 步),跟 .upload-zone/.metadata-zone
同一種「Canvas 裡的操作區塊」,樣式沿用同一批扁平化卡片語彙,不另創
一套視覺風格。 */
/* 2026-07-31(Batch E 第 4-c 項):同 .upload-zone,左右內距歸零、頂部
內距歸零,讓「研究選擇」標題左緣跟步驟列第一顆藥丸左緣對齊、上方
留白縮小。 */
/* 2026-07-31(Batch F):同 .metadata-zone 那則 Batch F 註解,左內距
歸零的副作用是標題貼死在步驟列基準線上,補回 1.5rem 左內距(跟
.analysis-panel 同一套數值)。zone 本身的 bounding box 左緣不受影響
(padding 加在框內,不是 margin),仍對齊 canvas-scroll 20px 基準線,
只有內容(標題與底下的情境卡片)整體往內縮。 */
.scenario-select-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem 1.5rem !important;
margin: .6rem 0 0 !important;
width: 100% !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
/* 2026-07-24 千卉:①「原文標題/作者/語言…」這些欄位標籤在深色模式吃到
Gradio 自己的樣式(實測 #475569,對比只有 2.40:1,遠低於 AA 4.5),跟旁邊
表格表頭「群組標籤」(--c-text-muted #94a3b8、6.92:1)落差很大。統一成
--c-text-muted。②兩張表格沒撐滿容器(容器內寬 1069、表格只有 1051,右側
空 18px),Gradio dataframe 的 table 寬度是依內容算的,補 width:100%。 */
#metadata_zone .container > span,
#metadata_zone label > span {
color: var(--c-text-muted) !important;
}
#metadata_zone table { width: 100% !important; }
/* 2026-08-12(業主截圖標了五個 ✗):語料說明表單的欄位被 Gradio 自動包成
白色卡片,跟下面「群組層資訊」那種「小標題+內容直接攤開」的樣子不一致。
實測拆出來是兩層各做一半:
.form(svelte-d5xbca) 底透明但有 1px 外框 → 卡片的框
.block.padded(含 fieldset)白底 + 10px 12px 內距 → 卡片的白色填充
兩層都要處理才會真的消失,只清其中一層會留下框或留下白塊。用 `>` 只打
「.form 直屬的那層」,避免波及群組層/個體層那兩個 Dataframe(它們是
metadata_zone 的直屬子項,不在 .form 裡面,外框要留著)。
內距歸零之後,欄位左緣自然跟「原文資訊」小標題與下方表格對齊,這就是
業主要的「靠左對齊」;欄位本身的邊框(--c-input-border)保留,截圖上
打叉的是外面那層白卡片,不是輸入框自己。 */
#metadata_zone .form {
border: none !important;
background: transparent !important;
box-shadow: none !important;
overflow: visible !important;
gap: 1rem !important;
/* align-items 從預設的 stretch 改 flex-start,是「群組數量/比較方式」
兩組 radio 被裁掉一半的真正解法(那是既有 bug,業主截圖裡就已經被裁,
不是這批改動造成的)。原因:.form 是 flex 列,stretch 會把每個欄位的
高度統一拉成整列的高度,而那個列高只算到 radio 的選項區(61px),
沒有把上面的欄位標籤(20px+8px 下距)算進去,多出來的 28px 就被
fieldset 自帶的 overflow:auto 裁掉。改成 flex-start 之後每個欄位各自
用自己的內容高度,裁切消失。
排除過但無效的方向(留記錄免得下次重試):改 overflow、加
flex-shrink:0、min-height:fit-content/max-content、把 fieldset 改成
flex column——前三個不影響那個被算錯的列高,最後一個會讓標籤跟選項
變成左右並排。 */
align-items: flex-start !important;
}
#metadata_zone .form > .block,
#metadata_zone .form > fieldset.block {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
/* Gradio 給 fieldset 的 overflow:auto 是裁掉標籤的那把刀(真因見上面
.form 的 align-items 註解),改 visible 當第二道保險。 */
overflow: visible !important;
}
/* 2026-08-12(業主):「群組數量/比較方式」這兩個欄位標籤要跟「原文標題/
作者」一樣是小灰字粗體。Gradio 給文字欄位標籤的是 .7rem/700/灰,但
radio 的 fieldset 標籤走另一條規則,渲染成一般大小的深色字,兩種欄位
放在同一張表單裡就不同調。這裡的 .7rem 不是新發明的字級,是對齊 Gradio
既有的欄位標籤值(實測文字欄位標籤就是 11.2px=.7rem/700/
--c-text-muted),所以不套 --font-size-base(那是內文用的,標籤比內文小
一階是這個表單本來的層級設計)。 */
#metadata_zone fieldset.block > span {
font-size: .7rem !important;
font-weight: 700 !important;
color: var(--c-text-muted) !important;
margin-bottom: 8px !important;
}
/* 「比較結構」從 gr.Group 改成 gr.Column 之後(見 app_agent.py 同批註解),
Gradio 那條藍色標題橫幅與外框就不存在了,這裡只補回跟其他小標題一致的
上下節奏——它的 Markdown 用的是同一個 `**粗體**` 寫法,顏色/字級自動與
「原文資訊」「群組層資訊」相同,不用另外設。 */
#meta_compare_group {
background: transparent !important;
border: none !important;
padding: 0 !important;
gap: 0 !important;
}
/* Phase 8-C:結果內容區塊(語言指標分析/統計比較分析/LLM輔助分析),
從原本的 gr.Tab()(自帶 .tabitem padding)改成 gr.Column,這裡補上
跟上面 upload-zone/metadata-zone 一致的垂直間距,避免緊貼在一起 */
/* 2026-07-24(左留白統一):水平內距集中在這裡(1.25rem),所有結果頁
(語言指標分析/統計四子頁/LLM輔助分析/技術執行紀錄)左右緣一致。
統計頁的 .tabitem 水平 padding 因此歸零,避免 canvas-result + tabitem
疊加成雙重縮排(先前「語言指標分析」頁沒有 tabitem、內容貼左緣,跟其他頁
對不齊,就是這個差異)。 */
/* 水平內距已上移到 .canvas-scroll 統一處理(見該處註解),這裡只留上方間距,
否則會跟 canvas-scroll 疊成兩倍縮排。 */
.canvas-result { margin-top: 1.2rem !important; padding-left: 0 !important; padding-right: 0 !important; }
/* 2026-07-24:「分析運算」內容區塊——運算中顯示三個子階段進度,運算後變成
分段摘要(標題+狀態+一句結論+查看入口)。取代舊的單行 .computing-zone
「運算中…(步驟 N/3)」提示(元件與 elem_id 沿用 computing_zone)。
這塊卡片同時也是 _build_upload_html「文本上傳」進度卡片共用的同一組
CSS 語彙(見 .analysis-panel-title 下方 2026-07-31 註解),不是各自
重複刻一份。 */
/* 2026-07-31(Batch E 第 4-c 項):這是唯一一個真正有外框/底色的「卡片」
(其他內容區塊如 upload-zone/metadata-zone 都已扁平化、沒有可視邊框),
computing_html/upload_progress_html 這兩個 gr.HTML() 元件預設會被
Gradio 包一層 .html-container(10px 12px 內距,見 .html-container:
has(.app-header) 那則註解),這裡比照同一招數精準鎖定「包住
.analysis-panel 的那一個」歸零,讓卡片本身的左緣落在 canvas-scroll
統一的 20px 基準線上,卡片左緣才能跟其他內容區塊(研究選擇/文本上傳/
語料說明/結果頁)的左緣落在同一條線上。 */
.html-container:has(.analysis-panel) {
padding: 0 !important;
}
/* 2026-07-31(Batch G 第 2 項):四個階段標題的上方留白統一收斂到 32px
(量測基準:header 步驟列底緣到標題文字頂緣的距離,貼近「研究選擇」
現有數值,改動最小)。這張卡片同時是「文本上傳」進度卡與「分析運算」
內容區塊共用的樣式,原本 margin-top 1.2rem + padding-top 1.2rem
(卡片外緣到標題文字共 ≈39px)比目標多,改窄 margin-top 到 .75rem,
卡片外緣往上收,標題(.analysis-panel-title 是 margin:0 的 h2,
不吃額外標題留白)跟著一起收斂到 32px 左右,不動 padding(左右/底部
內距與卡片內 analysis-row 的呼吸空間維持不變,只調外距)。 */
.analysis-panel {
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface2) !important;
padding: 1.2rem 1.5rem 1.4rem !important;
margin-top: .75rem !important;
}
/* 2026-07-31(Batch E 第 4-c 項,已被下方 Batch F 註解取代):卡片本身
保留左右內距(1.5rem)維持內容呼吸空間,裡面的狀態列(analysis-row)
都還需要跟卡片邊框有安全距離,不能整塊歸零(那樣會變成上面
.upload-zone 那種完全扁平卡片,跟這裡刻意保留的「有外框的卡片」設計
衝突)。當時改成只把標題本身用負 margin-left(等於卡片左內距的
1.5rem)拉出去,讓標題文字獨自貼齊卡片左側邊框,卡片邊框本身又落在
canvas-scroll 的 20px 基準線上(見上方 .html-container:has
(.analysis-panel) 那則),兩者相加後標題文字左緣正好等於步驟列第一顆
藥丸的左緣。 */
/* 2026-07-31(Batch F):業主實測回報「文本上傳」進度卡片(用的正是這個
.analysis-panel-title)標題貼死在卡片邊框上,就是上面這個負
margin-left 造成的,它把標題硬拉到跟卡片邊框零距離,等於故意抵銷掉
.analysis-panel 自己的 1.5rem 左內距。業主裁示的修法是「卡片外框對齊
步驟列、標題往內縮」,不是「標題貼齊步驟列、卡片跟著位移」,所以拿掉
這個負 margin,讓標題乖乖吃卡片本身既有的 1.5rem 左內距即可,跟卡片
內的 analysis-row 狀態列用的是同一份內距,兩者左緣自然對齊、不再各吃
各的規則。 */
.analysis-panel-title {
margin: 0 0 1rem 0 !important;
font-size: 1.05rem !important;
font-weight: 700 !important;
color: var(--c-text) !important;
}
/* 2026-08-05:分析運算進行中的連線提醒,低視覺重量(跟標題/狀態列比起來
是次要資訊),用 warn 色系底+左邊框做出「提示」語彙,不搶過主要內容。 */
.analysis-panel-hint {
margin: 0 0 1rem 0 !important;
padding: .6rem .8rem !important;
border-left: 3px solid var(--c-warn) !important;
border-radius: 4px !important;
background: rgba(180,83,9,.08) !important;
font-size: .82rem !important;
line-height: 1.5 !important;
color: var(--c-text-muted) !important;
}
.dark .analysis-panel-hint { background: rgba(251,191,36,.10) !important; }
.analysis-row {
display: flex;
flex-direction: column;
gap: .25rem;
padding: .7rem 0;
border-top: 1px solid var(--c-border-strong);
}
.analysis-row:first-of-type { border-top: none; padding-top: 0; }
.analysis-row-head { display: flex; align-items: center; gap: .55rem; }
.analysis-row-title {
font-size: var(--font-size-base) !important;
font-weight: 600 !important;
color: var(--c-text) !important;
}
.analysis-row-body {
padding-left: 1.65rem; /* 對齊圖示右側,讓結論文字跟標題文字左緣一致 */
font-size: var(--font-size-base);
color: var(--c-text-soft);
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .3rem .9rem;
}
.analysis-row--pending .analysis-row-title,
.analysis-row--pending .analysis-row-body { color: var(--c-text-muted) !important; }
.analysis-entry {
color: var(--c-accent-strong) !important;
font-weight: 600;
cursor: pointer;
text-decoration: none;
}
.analysis-entry:hover { text-decoration: underline; }
/* 狀態記號:完成 ✓、失敗 !、等待中 •,尺寸統一,跟旋轉動畫的 spinner 佔位一致 */
.analysis-mark {
flex: none;
width: 20px; height: 20px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 50%;
font-size: .72rem; font-weight: 700; line-height: 1;
}
.analysis-mark--done { background: rgba(34,197,94,.15); color: #16a34a; }
.analysis-mark--failed { background: rgba(220,38,38,.15); color: #dc2626; }
.analysis-mark--pending{ background: var(--c-surface); color: var(--c-text-muted);
border: 1px solid var(--c-border); }
/* 轉圈動畫:踩坑(2026-07-24)——border 這個「簡寫」在這版 Gradio 的
pipeline 下整條不會生效(跟本檔 background 簡寫的踩坑同一類),實測
border-width 算出來是 0px、圓圈整個沒被畫出來,只有同規則裡的
border-top-color 長寫法有作用。改成全部拆成長寫法子屬性就正常。 */
.analysis-spinner {
flex: none;
width: 20px; height: 20px;
border-radius: 50%;
border-width: 2.5px !important;
border-style: solid !important;
border-color: rgba(var(--c-accent-rgb),.25) !important;
border-top-color: var(--c-accent) !important;
animation: computingSpin .8s linear infinite;
}
@keyframes computingSpin { to { transform: rotate(360deg); } }
/* 2026-07-24(B2):Gradio 對「正在串流輸出」的元件(block wrapper)自動加
.generating class,帶一圈會呼吸的藍色邊框動畫。頂部步驟列與分析運算內容
區塊都是同一個 generator 的輸出,各被套一圈,畫面上兩個藍框一起閃、很礙眼。
這個 app 不需要這個效果,整站關掉。 */
.generating {
border-color: transparent !important;
animation: none !important;
box-shadow: none !important;
}
/* 2026-07-24(A2):結果導覽項目在對應結果產出前是 disabled(見 create_app
的 interactive=False 與 _populate_tabs 的閘門)。給明確的灰階+禁用游標,
讓「還不能點」一眼看得出來,不是壞掉。 */
#result_nav_col .result-nav-btn:disabled,
#result_nav_col .result-nav-btn[disabled] {
opacity: .38 !important;
cursor: not-allowed !important;
}
.upload-area, [data-testid="droparea"] {
border: 2px dashed rgba(var(--c-accent-rgb),.28) !important;
border-radius: 12px !important;
background: rgba(var(--c-accent-rgb),.03) !important;
transition: border-color .2s ease, background .2s ease !important;
}
.upload-area:hover, [data-testid="droparea"]:hover {
border-color: rgba(var(--c-accent-rgb),.5) !important;
background: rgba(var(--c-accent-rgb),.08) !important;
}
.file-preview {
border: 1px solid var(--c-border-strong) !important;
border-radius: 10px !important;
background: var(--c-surface) !important;
}
/* 修正:此版 Gradio 的 File(list) 元件在 flex 版面下,外層 holder 會因
min-width:0 而整個縮成 0~4px 寬,導致內容(含下載連結)視覺上溢出到
容器外,要橫向捲動才找得到下載按鈕。強制給滿版寬度,並固定「下載」欄
在最前面、檔名欄自動換行,避免長檔名把下載連結擠出視野。 */
.file-preview-holder {
width: 100% !important;
flex: 1 1 auto !important;
min-width: 0 !important;
}
.file-preview {
table-layout: fixed !important;
width: 100% !important;
}
.file-preview tr > :first-child {
width: auto !important;
white-space: normal !important;
word-break: break-all !important;
}
.file-preview tr > :nth-child(2) {
width: 92px !important;
white-space: nowrap !important;
}
.file-preview tr > :last-child {
width: 34px !important;
}
/* ── 6. BUTTONS ────────────────────────────────────────────────── */
button.primary, button[data-testid="submit"] {
background: linear-gradient(135deg, #0284c7 0%, #4f46e5 100%) !important;
border: none !important;
border-radius: 9px !important;
font-weight: 700 !important;
font-size: var(--font-size-base) !important;
color: #fff !important;
box-shadow: 0 2px 12px rgba(2,132,199,.28) !important;
transition: transform .18s ease, box-shadow .18s ease, opacity .18s !important;
cursor: pointer !important;
letter-spacing: .02em;
}
button.primary:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 22px rgba(2,132,199,.40) !important; }
button.primary:active { transform: translateY(0) !important; opacity: .92 !important; }
button.primary:disabled { opacity: .5 !important; cursor: not-allowed !important; transform: none !important; }
button.secondary {
background: rgba(var(--c-accent-rgb),.08) !important;
border: 1px solid rgba(var(--c-accent-rgb),.30) !important;
color: var(--c-accent-strong) !important;
border-radius: 9px !important;
font-weight: 600 !important;
font-size: var(--font-size-base) !important;
cursor: pointer !important;
transition: background .18s ease, border-color .18s ease !important;
}
button.secondary:hover {
background: rgba(var(--c-accent-rgb),.16) !important;
border-color: rgba(var(--c-accent-rgb),.45) !important;
}
/* ── 7. INPUTS ─────────────────────────────────────────────────── */
textarea, input[type="text"] {
background: var(--c-input-bg) !important;
border: 1px solid var(--c-input-border) !important;
border-radius: 9px !important;
color: var(--c-text) !important;
font-size: var(--font-size-base) !important;
line-height: 1.6 !important;
transition: border-color .18s ease, box-shadow .18s ease !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--c-accent) !important;
box-shadow: 0 0 0 3px rgba(var(--c-accent-rgb),.16) !important;
outline: none !important;
}
/* ── 8. DATAFRAME ──────────────────────────────────────────────── */
.table-wrap {
border: 1px solid var(--c-border-strong) !important;
border-radius: 12px !important;
overflow: hidden !important;
}
/* 2026-08-12(業主回報語料 metadata 表單「文字跟表頭背景黏在一起」):
表頭底色原本借用 --c-surface2,淺色是 #f1f5f9 疊在白色資料列上只有
1.04:1,表頭等於沒有存在感;改用獨立 token --c-thead-bg(淺色 #e2e8f0,
對白 1.19:1,表頭文字 --c-text-muted 在其上仍有 6.15:1 過 AA),深色沿用
原本的 #111827 不變。表頭與資料列真正的分界靠下面那條 border-bottom,
從 rgba(accent,.16)(約 1.2:1)換成結構線 token。 */
table thead tr { background: var(--c-thead-bg) !important; }
table thead th {
background: transparent !important;
color: var(--c-text-muted) !important;
font-weight: 700 !important;
font-size: 0.75rem !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
border-bottom: 1px solid var(--c-border-strong) !important;
padding: 10px 12px !important;
white-space: nowrap;
}
table tbody td {
background: var(--c-surface) !important;
color: var(--c-text-soft) !important;
font-size: var(--font-size-base) !important;
/* 資料列之間的細線用 --c-divider(Tier 2):這種密集重複的線如果也拉到
3:1,整張表會變成網格紙,反而更難讀。結構性的外框與表頭分界才走 Tier 1。 */
border-color: var(--c-divider) !important;
font-family: 'JetBrains Mono', monospace !important;
padding: 7px 12px !important;
}
table tbody tr:nth-child(even) td { background: rgba(var(--c-accent-rgb),.025) !important; }
table tbody tr:hover td { background: rgba(var(--c-accent-rgb),.08) !important; }
/* ── 9. IMAGE FRAMES ───────────────────────────────────────────── */
.image-container, .image-frame {
border: 1px solid var(--c-border-strong) !important;
border-radius: 12px !important;
overflow: hidden !important;
background: var(--c-surface) !important;
}
.image-container img { display: block; width: 100%; height: auto; }
/* ── 10. RESULT SECTION CARDS ──────────────────────────────────── */
.result-card {
background: var(--c-surface);
border: 1px solid var(--c-border-strong);
border-radius: 14px;
padding: 1.25rem 1.5rem;
margin-bottom: 1.25rem;
}
.result-card-title {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--c-text-subtle);
margin: 0 0 .75rem;
padding-bottom: .5rem;
border-bottom: 1px solid var(--c-border-strong);
display: flex; align-items: center; gap: 6px;
}
.result-card-title .dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--c-accent);
display: inline-block;
}
.stat-pill {
display: inline-flex; align-items: center; gap: 5px;
background: rgba(var(--c-accent-rgb),.08);
border: 1px solid rgba(var(--c-accent-rgb),.20);
border-radius: 6px;
padding: 3px 10px;
font-size: 0.78rem;
color: var(--c-accent-strong);
font-weight: 600;
margin: 2px;
}
/* ── 11. MARKDOWN (result tabs) ────────────────────────────────── */
.prose h3, .md h3, .svelte-1ipelgc h3 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.25rem !important;
font-weight: 600 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.45rem;
margin: 1.4rem 0 .8rem !important;
}
.prose h4, .md h4 {
font-size: 0.85rem !important;
font-weight: 700 !important;
color: var(--c-text-muted) !important;
text-transform: uppercase;
letter-spacing: .06em;
margin: 1rem 0 .4rem !important;
}
.prose strong, .md strong { color: var(--c-accent-strong) !important; }
.prose em, .md em { color: var(--c-accent2-strong) !important; }
.prose code, .md code {
background: var(--c-code-bg) !important;
color: var(--c-code-text) !important;
padding: 2px 6px;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.82em;
}
.prose ul li, .md ul li { color: var(--c-text-soft); line-height: 1.75; }
.prose ol li, .md ol li { color: var(--c-text-soft); line-height: 1.75; }
.prose p { line-height: 1.75; color: var(--c-text-soft); }
/* h1/h2/h5/h6/表格/hr/blockquote 補齊——原本只有 h3/h4 有樣式,LLM 報告等
自由格式 Markdown 若真的輸出到這幾個標題等級會吃到瀏覽器預設樣式。
刻意只 scope 在 #canvas_report(LLM 輔助分析頁),不像 h3/h4 沿用全站
.prose/.md 泛用寫法:踩過一次坑才發現 gr.HTML() 元件外層也會被 Gradio
包一層 .prose class(不是只有 gr.Markdown() 才有),首頁標題就是用
gr.HTML() 寫死的 <h1>,全站套用 h1 樣式會蓋掉首頁標題自己的漸層字設計
(color: transparent 被這裡的 color:...!important 蓋掉)。 */
#canvas_report .prose h1, #canvas_report .md h1 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.6rem !important;
font-weight: 700 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.5rem;
margin: 1.6rem 0 .9rem !important;
}
#canvas_report .prose h2, #canvas_report .md h2 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.4rem !important;
font-weight: 600 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.45rem;
margin: 1.5rem 0 .8rem !important;
}
#canvas_report .prose h5, #canvas_report .md h5 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 0.95rem !important;
font-weight: 600 !important;
color: var(--c-text-muted) !important;
margin: 1rem 0 .4rem !important;
}
#canvas_report .prose h6, #canvas_report .md h6 {
font-size: 0.78rem !important;
font-weight: 700 !important;
color: var(--c-text-subtle) !important;
text-transform: uppercase;
letter-spacing: .06em;
margin: .8rem 0 .3rem !important;
}
/* 表格——原本完全沒樣式,LLM 報告的表格吃瀏覽器預設白色框線,深色主題下很刺眼 */
#canvas_report .prose table, #canvas_report .md table {
width: 100%; border-collapse: collapse; margin: 1rem 0;
font-size: 0.82rem;
}
#canvas_report .prose th, #canvas_report .md th,
#canvas_report .prose td, #canvas_report .md td {
border: 1px solid var(--c-divider);
padding: .5rem .75rem; text-align: left;
color: var(--c-text-soft);
}
#canvas_report .prose th, #canvas_report .md th {
background: rgba(var(--c-accent-rgb),.08);
color: var(--c-text); font-weight: 700;
}
#canvas_report .prose tr:nth-child(even) td, #canvas_report .md tr:nth-child(even) td { background: var(--c-surface2); }
/* 水平線——原本完全沒樣式,吃瀏覽器預設 */
#canvas_report .prose hr, #canvas_report .md hr { border: none; border-top: 1px solid var(--c-border-strong); margin: 1.5rem 0; }
/* 引言區塊(LLM 報告常用 > 標出結論/總結) */
#canvas_report .prose blockquote, #canvas_report .md blockquote {
border-left: 3px solid var(--c-accent);
padding: .6rem 1rem; margin: 1rem 0;
background: rgba(var(--c-accent-rgb),.05);
color: var(--c-text-soft); font-style: normal;
}
#canvas_report .prose blockquote p, #canvas_report .md blockquote p { margin: 0; }
/* LLM 報告頁專用:模型常把「章節標題」寫成整行粗體而非真正的 ##/### 語法,
CSS 選不出「這行粗體是標題」還是「句中強調」,只能用「整行只有一顆粗體」
這個結構特徵做近似辨識,並非真正標題樣式的替代品 */
#canvas_report .md p:has(> strong:only-child),
#canvas_report .prose p:has(> strong:only-child) {
font-family: 'Crimson Pro', Georgia, serif;
font-size: 1.15rem; font-weight: 600;
color: var(--c-text);
margin: 1.4rem 0 .6rem;
}
/* 報告開頭「研究情境/時間」metadata 兩行,視覺上當副標題處理 */
#canvas_report .md > p:first-of-type,
#canvas_report .prose > p:first-of-type {
color: var(--c-text-muted); font-size: 0.85rem; margin-bottom: 1rem;
}
/* ── 12. LABELS ────────────────────────────────────────────────── */
/* 排除 Radio/Checkbox 選項的 <label><input>...<span>選項文字</span></label>:
這些「藥丸」本身有自己的底色(未選中時剛好跟 --c-text-subtle 深色版同一種
灰),套上這條「小字大寫、次要色」的欄位標籤樣式後文字顏色跟底色一樣,
選項文字整個看不見。用 :has(input) 排除選項本身,只保留純欄位標籤(不含
表單控制項的 label)套用這個樣式 */
.block > .wrap > label:not(:has(input)) > span,
.block > .label-wrap > span,
label:not(:has(input)) > span {
color: var(--c-text-subtle) !important;
font-weight: 700 !important;
font-size: 0.70rem !important;
text-transform: uppercase !important;
letter-spacing: 0.08em !important;
}
/* ── 13. DOWNLOAD SECTION ──────────────────────────────────────── */
.download-bar {
display: flex; align-items: center; gap: 10px;
background: linear-gradient(135deg,rgba(var(--c-accent-rgb),.06),rgba(var(--c-accent2-rgb),.06));
border: 1px solid rgba(var(--c-accent-rgb),.20);
border-radius: 12px;
padding: .8rem 1.1rem;
margin-top: 1rem;
}
.download-bar svg { flex-shrink: 0; }
/* ── 14. SCROLLBAR ─────────────────────────────────────────────── */
/* 2026-07-31(Batch E 第 3 項):業主回報「每個區塊都有 scroll bar 很
干擾視覺」,改成預設不顯示、滑鼠懸停在該區塊時才出現。
刻意不用 display:none 或改 overflow 值:那類做法會讓捲動容器的
內容寬度在有無捲軸間跳動(畫面撐一下縮一下),改成軌道/寬度全程
保持不變(width 維持 5px、track 維持 transparent),只把 thumb 顏色
在「未懸停」時設成 transparent、懸停該容器時才變回原本的
--c-scrollbar/--c-scrollbar-hover——thumb 本身仍佔用同樣的 5px
空間,只是視覺上透明,不會有任何版面位移。
`:hover::-webkit-scrollbar-thumb` 這個寫法是給「目前被滑鼠懸停的
那個元素」套用它自己的捲軸樣式,不需要額外掛在特定 class 上,
全站任何可捲動區塊懸停時都會生效(.nav-col 的項目捲動區、
.canvas-scroll、.main-chat 的訊息區、dataframe 內部捲動……)。 */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: transparent; border-radius: 3px; }
:hover::-webkit-scrollbar-thumb { background: var(--c-scrollbar); }
:hover::-webkit-scrollbar-thumb:hover { background: var(--c-scrollbar-hover); }
/* 2026-08-12(業主回報「表格欄位錯版」)──────────────────────────────
症狀:表格右側出現一條白色空帶,看起來像最後一欄被切掉。
逐項量過全部 16 張表(語料說明 2 張+五個結果頁),**表頭與資料列的欄寬
完全一致、沒有任何一欄真的錯位**,所以不是欄位對不齊,是捲軸佔掉的位置:
Gradio 給 dataframe 的內部容器 .virtual-table-viewport 設了
`max-height:500px; overflow-y:auto`,列數一多就出現垂直捲軸,佔掉 10px
版面寬度,而 .table-wrap 的底色是白的 → 那 10px 就是白帶。實測不捲動時
表格右緣只差 2px(框線),一捲動就跳到 12px,與業主截圖吻合。
踩坑(值得記):一開始想沿用本檔第 14 節那套「平常透明、懸停才出現」的
::-webkit-scrollbar 規則,寫了帶 class 的版本卻完全沒生效。真因不是
specificity——Gradio 在這個元件上設了**標準屬性** scrollbar-width:thin
與 scrollbar-color,而 Chrome 只要看到標準屬性,就會整組忽略 legacy 的
::-webkit-scrollbar 偽元素。所以要改這個元件的捲軸,只能用標準屬性。
**試過但不能用的修法(重要,別再走一次)**:把 max-height 拿掉讓表格
整條攤開、交給 canvas 捲(比照 .exec-log 那個「不要兩層捲動」的既有決定)。
實測會壞掉——Gradio 這個表格是**虛擬捲動**,只渲染視窗內的列,靠自己的
捲動位置決定要畫哪幾列。拿掉 max-height 之後它不再自己捲,虛擬化拿不到
捲動事件:敘述統計那張表變成「保留了 2832px 高度、卻只畫出 1 列」的空表。
所以這個元件的內部捲動必須留著。
最終修法:全部 dataframe 一律 scrollbar-width:none,捲軸不再佔版面寬度,
白帶完全消失(實測 10px→0、表格右緣只剩 2px 框線)。滾輪/觸控/鍵盤
照常可捲,虛擬化不受影響(已實測 scrollTop 有效、列照樣重新渲染)。
範圍是逐表量完才定的——一開始想「只有語料表單藏捲軸、結果頁保留橫向
提示」,量完發現方向剛好相反:會出現白帶的是敘述統計/差異性分析/
監督式學習那三張長表(垂直捲動、不需橫捲),而唯一需要橫向捲軸的語言
指標分析(76 欄)從來不出現垂直捲軸、也就沒有白帶。
代價講明:語言指標分析那條橫向捲軸也跟著隱形,使用者要靠「右緣被切一半
的欄位」+Shift+滾輪察覺還有欄位。覺得提示不夠時,把下面 #canvas_idx
那兩行註解解開即可單獨還原它的捲軸。
!important 是必要的:Gradio 在同一個元素上設 scrollbar-width:thin,
不加就吃不到(語料表單那條是靠 #metadata_zone 的 id 才贏過它)。 */
.virtual-table-viewport {
scrollbar-width: none !important;
}
/* 2026-08-14(業主回報「語言指標分析表格被截斷」):語言指標分析欄數多
(目前實際顯示 62 項指標,欄數隨 HIDDEN_INDICATOR_CODES 增減),橫向
捲軸被上面那條全站規則隱形後,使用者只能看到「右緣被切一半的欄位」,
沒有提示還能左右拖曳看完整表格。單獨還原這裡的橫向捲軸(沿用第 14 節
「預設透明、懸停才出現」那套視覺邏輯,只是這裡是 scrollbar-color 的
標準屬性版本,不是 ::-webkit-scrollbar 偽元素)。
!important 是必要的:上面那條全站規則本身也是 !important,CSS 的
important 優先於一般宣告、跟選擇器精確度無關,這裡不加就贏不了它
(實測套用後 scrollbar-width 電腦算出來還是 none,就是漏了這個)。
2026-08-15:業主回報差異性分析表格(欄數也不少:指標+每組 N/M/SD+
方法/統計量/p值/顯著/效果量名稱/效果量/效果大小)右側欄位一樣
被切掉,同一顆坑,範圍擴大成 #canvas_stat(統計比較分析——敘述統計/
差異性分析/監督式/非監督式共用這個容器)。敘述統計那張表欄數少、
本來就不會超出容器寬度,橫向捲軸不會出現,套用這條規則對它沒有副作用。 */
#canvas_idx .virtual-table-viewport,
#canvas_stat .virtual-table-viewport { scrollbar-width: thin !important; scrollbar-color: transparent transparent !important; }
#canvas_idx .virtual-table-viewport:hover,
#canvas_stat .virtual-table-viewport:hover { scrollbar-color: var(--c-scrollbar) transparent !important; }
/* ── 15. FOCUS & ACCESSIBILITY ─────────────────────────────────── */
:focus-visible {
outline: 2px solid var(--c-accent) !important;
outline-offset: 2px !important;
border-radius: 4px;
}
a { color: var(--c-accent-strong); transition: color .15s; }
a:hover { color: var(--c-link-hover); }
/* ── 16. REDUCED MOTION ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}
/* ── 17. RESPONSIVE ────────────────────────────────────────────── */
/* 三欄式工作區(nav-col/chat-col/canvas-col)僅限桌面寬度;手機或窄視窗
(≤900px)chat-col/canvas-col 改回上下堆疊,不再各自獨立捲動(避免
小螢幕出現多層巢狀捲軸),改用頁面本身捲動。nav-col 這裡刻意不列
進來:Phase 8-E 把它改成手機 off-canvas 抽屜(position:fixed,見
「5e. NAV-COL 收合/手機漢堡抽屜」區塊),跟著上下堆疊會跟抽屜的
fixed 定位互相打架。 */
@media (max-width: 900px) {
.main-layout-row { flex-direction: column !important; }
.main-layout-row .chat-col,
.main-layout-row .canvas-col {
width: 100% !important;
max-width: 100% !important;
flex: 1 1 auto !important;
height: auto !important;
overflow-y: visible !important;
}
.canvas-scroll { overflow-y: visible !important; min-height: 0 !important; }
}
@media (max-width: 768px) {
.app-header .title-line { font-size: 1.1rem !important; }
.tabitem { padding: 1rem .75rem !important; }
.app-header .steps { gap: 3px; }
.step-pill { font-size: 0.62rem; padding: 2px 7px; }
}
|