| 1 | // Shared page shell + UI kit. Visual language mirrors site/src/styles/global.css |
| 2 | // — white, blue accent, large radii; crash stacks use the site's dark terminal. |
| 3 | |
| 4 | export function esc(s: unknown): string { |
| 5 | return String(s).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`); |
| 6 | } |
| 7 | |
| 8 | const LOGO = `<svg viewBox="0 0 64 64" class="logo"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#4f9dff"/><stop offset=".55" stop-color="#7a6bff"/><stop offset="1" stop-color="#c46bff"/></linearGradient></defs><rect width="64" height="64" rx="15" fill="url(#g)"/><text x="32" y="46" font-family="Inter,Segoe UI,Arial,sans-serif" font-size="42" font-weight="800" fill="#fff" text-anchor="middle">R</text></svg>`; |
| 9 | |
| 10 | const CSS = ` |
| 11 | :root{--accent:oklch(0.55 0.17 257);--accent-soft:color-mix(in oklch,var(--accent) 8%,white); |
| 12 | --bg-soft:oklch(0.976 0.0025 247);--ink:oklch(0.25 0.006 260);--ink-2:oklch(0.47 0.008 260); |
| 13 | --ink-3:oklch(0.6 0.008 260);--line:oklch(0.925 0.004 247);--term-bg:oklch(0.25 0.006 260); |
| 14 | --shadow-1:0 1px 2px oklch(0.35 0.01 260/0.18),0 1px 3px 1px oklch(0.35 0.01 260/0.08); |
| 15 | --sans:"Outfit","Helvetica Neue","PingFang SC","Microsoft YaHei",sans-serif; |
| 16 | --mono:"JetBrains Mono","SF Mono",Menlo,Consolas,monospace} |
| 17 | *{margin:0;padding:0;box-sizing:border-box} |
| 18 | html{scroll-behavior:smooth} |
| 19 | body{font-family:var(--sans);background:#fff;color:var(--ink);-webkit-font-smoothing:antialiased} |
| 20 | .wrap{max-width:1060px;margin:0 auto;padding:0 28px 64px} |
| 21 | header{display:flex;align-items:center;gap:10px;height:68px} |
| 22 | .logo{width:28px;height:28px;border-radius:8px} |
| 23 | header .brand{font-size:17px;font-weight:600;color:var(--ink);text-decoration:none} |
| 24 | header .crumb{color:var(--ink-3);font-size:15px} |
| 25 | header .nav{display:flex;align-items:center;gap:14px} |
| 26 | .navlink{color:var(--accent);text-decoration:none;font-size:14px;font-weight:500} |
| 27 | .navlink:hover{text-decoration:underline} |
| 28 | .site-nav{display:flex;align-items:center;gap:6px;margin:4px 0 10px} |
| 29 | .site-nav a{display:inline-flex;align-items:center;min-height:34px;padding:7px 13px;border-radius:999px;color:var(--ink-2);text-decoration:none;font-size:14px;font-weight:600} |
| 30 | .site-nav a:hover{background:var(--bg-soft);color:var(--ink)} |
| 31 | .site-nav a.active,.site-nav a[aria-current="page"]{background:var(--accent-soft);color:var(--accent)} |
| 32 | .chip{display:inline-flex;align-items:center;gap:8px;font-size:13.5px;color:var(--ink-2)} |
| 33 | .lang-switch{margin-left:auto;display:inline-flex;align-items:center;gap:2px;padding:3px;border:1px solid var(--line);border-radius:12px;background:var(--bg-soft)} |
| 34 | .lang-switch button{font:inherit;font-size:12.5px;font-weight:700;color:var(--ink-2);background:transparent;border:0;border-radius:9px;padding:5px 10px;cursor:pointer} |
| 35 | .lang-switch button.active{background:#fff;color:var(--accent);box-shadow:var(--shadow-1)} |
| 36 | body[data-lang="en"] [data-i18n="zh"],body[data-lang="zh"] [data-i18n="en"]{display:none!important} |
| 37 | .badge{font-size:11px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;padding:2px 8px;border-radius:999px} |
| 38 | .badge.pending{background:oklch(0.95 0.04 80);color:oklch(0.5 0.12 80)} |
| 39 | .badge.viewer{background:var(--accent-soft);color:var(--accent)} |
| 40 | .badge.admin{background:oklch(0.95 0.05 150);color:oklch(0.48 0.13 150)} |
| 41 | h1{font-size:30px;font-weight:600;letter-spacing:-0.02em;margin:18px 0 6px} |
| 42 | .sub{color:var(--ink-2);font-size:14.5px;margin-bottom:28px} |
| 43 | .hero-line{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-top:6px} |
| 44 | .hero-line .sub{margin-bottom:18px} |
| 45 | .segmented{display:inline-flex;align-items:center;gap:3px;padding:3px;border:1px solid var(--line);border-radius:13px;background:var(--bg-soft);white-space:nowrap} |
| 46 | .segmented a{display:inline-flex;align-items:center;justify-content:center;min-height:30px;padding:6px 11px;border-radius:10px;color:var(--ink-2);text-decoration:none;font-size:12.5px;font-weight:700} |
| 47 | .segmented a.active,.segmented a[aria-current="true"]{background:#fff;color:var(--accent);box-shadow:var(--shadow-1)} |
| 48 | .overview-grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin:0 0 20px} |
| 49 | .overview-card{border:1px solid var(--line);border-radius:16px;background:linear-gradient(180deg,#fff,var(--bg-soft));padding:14px 15px;min-width:0;text-decoration:none} |
| 50 | .overview-card:hover{border-color:color-mix(in oklch,var(--accent) 36%,var(--line));transform:translateY(-1px)} |
| 51 | .overview-card span,.overview-card small{display:block;color:var(--ink-3);font-size:12.5px;line-height:1.3} |
| 52 | .overview-card strong{display:block;color:var(--ink);font-family:var(--mono);font-size:24px;line-height:1.15;margin:6px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
| 53 | .overview-card.good{border-color:oklch(0.88 0.05 150);background:oklch(0.985 0.015 150)} |
| 54 | .overview-card.warn{border-color:oklch(0.9 0.07 80);background:oklch(0.985 0.018 80)} |
| 55 | .overview-card.bad{border-color:oklch(0.89 0.06 25);background:oklch(0.985 0.016 25)} |
| 56 | .module-nav{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 20px} |
| 57 | .module-link{display:grid;grid-template-columns:1fr auto;grid-template-areas:"label value" "note note";gap:4px 10px;border:1px solid var(--line);border-radius:16px;background:#fff;color:var(--ink);text-decoration:none;padding:13px 14px;box-shadow:var(--shadow-1)} |
| 58 | .module-link:hover{border-color:color-mix(in oklch,var(--accent) 36%,var(--line));background:var(--accent-soft)} |
| 59 | .module-link.active,.module-link[aria-current="page"]{border-color:color-mix(in oklch,var(--accent) 45%,var(--line));background:var(--accent-soft)} |
| 60 | .module-link span{grid-area:label;color:var(--ink-2);font-size:13px;font-weight:700} |
| 61 | .module-link b{grid-area:value;font-family:var(--mono);font-size:13px;color:var(--accent)} |
| 62 | .module-link small{grid-area:note;color:var(--ink-3);font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
| 63 | .grid{display:grid;grid-template-columns:1fr 1fr;gap:20px} |
| 64 | .card{background:#fff;border:1px solid var(--line);border-radius:24px;padding:24px 28px;box-shadow:var(--shadow-1)} |
| 65 | .card.full{grid-column:1/-1} |
| 66 | .card h2{font-size:15px;font-weight:600;color:var(--ink-2);margin-bottom:16px} |
| 67 | .card h2 b{color:var(--ink)} |
| 68 | .module-card{scroll-margin-top:18px} |
| 69 | .module-card+.module-card{margin-top:2px} |
| 70 | .module-head{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:16px} |
| 71 | .module-head span{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-3);font-weight:800;margin-bottom:4px} |
| 72 | .module-head h2{margin:0;color:var(--ink);font-size:19px} |
| 73 | .module-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;flex-wrap:wrap} |
| 74 | .module-action{color:var(--accent);text-decoration:none;font-size:13.5px;font-weight:700;white-space:nowrap} |
| 75 | .module-action:hover{text-decoration:underline} |
| 76 | .module-panel{border:1px solid var(--line);border-radius:18px;background:var(--bg-soft);padding:16px 18px;margin-top:12px;min-width:0} |
| 77 | .module-panel.wide{margin-top:0} |
| 78 | .module-panel h3{font-size:13px;font-weight:700;color:var(--ink-2);margin:0 0 12px} |
| 79 | .module-panel h3 b{color:var(--ink)} |
| 80 | .module-split{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start} |
| 81 | .module-split.wide{grid-template-columns:minmax(0,1fr) minmax(0,1fr)} |
| 82 | .card-title-row{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:16px} |
| 83 | .card-title-row h2{margin:0} |
| 84 | .empty{color:var(--ink-3);font-size:14px;padding:14px 0} |
| 85 | svg.chart{width:100%;height:auto;display:block} |
| 86 | .bars-list{min-width:0} |
| 87 | .row{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(76px,1fr) minmax(3.8em,max-content);align-items:center;gap:12px;padding:7px 0;font-size:14px;min-width:0} |
| 88 | .row+.row{border-top:1px solid var(--line)} |
| 89 | .row-label{display:flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 90 | .row-bar{min-width:0} |
| 91 | .row .bar{height:10px;border-radius:999px;background:linear-gradient(90deg,#4f9dff,#7a6bff);min-width:4px} |
| 92 | .row .n{text-align:right;font-family:var(--mono);font-size:13px;color:var(--ink-2);white-space:nowrap} |
| 93 | .bucket-prefix{display:inline-flex;align-items:center;margin-right:6px;padding:1px 6px;border-radius:8px;background:var(--accent-soft);color:var(--accent);font-size:12px;font-weight:700} |
| 94 | .bucket-main{display:inline-block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 95 | .bars-more{margin-top:6px} |
| 96 | .bars-more summary{display:inline-flex;align-items:center;min-height:30px;padding:5px 9px;border:1px dashed var(--line);border-radius:10px;background:#fff;color:var(--accent);font-size:12.5px;font-weight:700;cursor:pointer;list-style:none} |
| 97 | .bars-more summary::-webkit-details-marker{display:none} |
| 98 | .bars-more summary:hover{border-color:color-mix(in oklch,var(--accent) 34%,var(--line))} |
| 99 | .bars-more .more-open{display:none} |
| 100 | .bars-more[open] .more-closed{display:none} |
| 101 | .bars-more[open] .more-open{display:inline} |
| 102 | .bars-more-list{max-height:260px;overflow-y:auto;overflow-x:hidden;margin-top:7px;padding:5px 8px;border:1px solid var(--line);border-radius:12px;background:#fff} |
| 103 | table{border-collapse:collapse;width:100%;font-size:14px} |
| 104 | th{text-align:left;color:var(--ink-3);font-weight:500;font-size:12.5px;padding:0 14px 8px 0} |
| 105 | td{padding:8px 14px 8px 0;border-top:1px solid var(--line);vertical-align:middle} |
| 106 | td.n{font-family:var(--mono);font-size:13px} |
| 107 | td.summary{max-width:480px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--mono);font-size:12.5px} |
| 108 | .reg-table{table-layout:fixed} |
| 109 | .reg-table .c-pkg{width:32%}.reg-table .c-kind{width:8%}.reg-table .c-pub{width:14%} |
| 110 | .reg-table .c-ver{width:16%}.reg-table .c-sub{width:10%}.reg-table .c-act{width:20%} |
| 111 | .rowlinks{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:6px} |
| 112 | .muted{color:var(--ink-3)} |
| 113 | p.summary{font-family:var(--mono);font-size:14px;margin:2px 0 6px;word-break:break-word} |
| 114 | a.fp{font-family:var(--mono);font-size:13px;color:var(--accent);text-decoration:none} |
| 115 | a.fp:hover{text-decoration:underline} |
| 116 | .pill{display:inline-block;font-size:12px;font-weight:500;padding:2px 10px;border-radius:999px;background:var(--accent-soft);color:var(--accent)} |
| 117 | .pill.crash{background:oklch(0.95 0.03 25);color:oklch(0.55 0.18 25)} |
| 118 | .pill.open{background:oklch(0.96 0.025 250);color:var(--ink-2)} |
| 119 | .pill.resolved{background:oklch(0.95 0.05 150);color:oklch(0.48 0.13 150)} |
| 120 | .pill.ignored{background:var(--bg-soft);color:var(--ink-3)} |
| 121 | .back{display:inline-block;margin:18px 0 0;color:var(--accent);text-decoration:none;font-size:14px} |
| 122 | .report{margin-top:20px} |
| 123 | .report .meta{display:flex;flex-wrap:wrap;gap:8px 18px;font-size:13px;color:var(--ink-2);margin-bottom:10px} |
| 124 | .report .meta b{color:var(--ink);font-weight:500} |
| 125 | pre{background:var(--term-bg);color:#e6e8f0;border-radius:12px;padding:16px 18px;font-family:var(--mono); |
| 126 | font-size:12.5px;line-height:1.55;overflow:auto;white-space:pre-wrap;word-break:break-word} |
| 127 | .metrics{display:grid;grid-template-columns:1fr 1fr;gap:8px 28px} |
| 128 | .metric-block h3{display:flex;align-items:center;justify-content:space-between;gap:8px;font-family:var(--mono);font-size:12.5px;font-weight:600;color:var(--ink-2);margin:6px 0 4px} |
| 129 | .metric-block h3 span{font-family:var(--mono);font-size:11px;color:var(--ink-3);font-weight:600} |
| 130 | .preference-dashboard{display:flex;flex-direction:column;gap:14px} |
| 131 | .preference-compare{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-items:start} |
| 132 | .preference-panel{background:#fff} |
| 133 | .preference-panel.active{border-color:color-mix(in oklch,var(--accent) 42%,var(--line));box-shadow:0 0 0 3px var(--accent-soft)} |
| 134 | .pref-section{border:1px solid var(--line);border-radius:14px;background:#fff;padding:14px 15px} |
| 135 | .pref-section>h3,.pref-section summary h3{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;font-weight:700;color:var(--ink);margin:0 0 10px} |
| 136 | .pref-section>h3 span,.pref-section summary h3 span{font-size:11.5px;color:var(--ink-3);font-weight:700;white-space:nowrap} |
| 137 | .pref-section summary{cursor:pointer;list-style:none} |
| 138 | .pref-section summary::-webkit-details-marker{display:none} |
| 139 | .pref-section summary h3{margin:0} |
| 140 | .pref-section summary h3::after{content:"+";font-family:var(--mono);font-size:13px;color:var(--accent);line-height:1} |
| 141 | .pref-section[open] summary h3::after{content:"-"} |
| 142 | .pref-section[open] summary h3{margin-bottom:10px} |
| 143 | .pref-section-collapsed summary:hover h3{color:var(--accent)} |
| 144 | .pref-section .metric-block h3{color:var(--ink-3)} |
| 145 | .pref-metrics{grid-template-columns:1fr;gap:12px} |
| 146 | .pref-metrics .metric-block{min-width:0} |
| 147 | .pref-metrics .row{grid-template-columns:minmax(7rem,1.2fr) minmax(5rem,.9fr) minmax(4.4em,max-content)} |
| 148 | .health-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px} |
| 149 | .health-card{border:1px solid var(--line);border-radius:16px;background:var(--bg-soft);padding:14px 15px;min-width:0} |
| 150 | .health-card.good{background:oklch(0.985 0.015 150);border-color:oklch(0.88 0.05 150)} |
| 151 | .health-card.warn{background:oklch(0.985 0.018 80);border-color:oklch(0.9 0.07 80)} |
| 152 | .health-card.bad{background:oklch(0.985 0.016 25);border-color:oklch(0.89 0.06 25)} |
| 153 | .health-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px} |
| 154 | .health-top span{font-size:12.5px;color:var(--ink-2);font-weight:700} |
| 155 | .health-top b{font-size:11px;text-transform:uppercase;color:var(--ink-3);font-weight:800} |
| 156 | .health-card strong{display:block;font-family:var(--mono);font-size:22px;color:var(--ink);margin-bottom:4px} |
| 157 | .health-card small{display:block;color:var(--ink-3);font-family:var(--mono);font-size:11.5px;margin-bottom:9px} |
| 158 | .health-card p{color:var(--ink-2);font-size:12.5px;line-height:1.35;word-break:break-word} |
| 159 | .filter-card{margin-top:12px;padding:16px 18px;border:1px solid var(--line);border-radius:18px;background:#fff;position:sticky;top:10px;z-index:2} |
| 160 | .filter-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px} |
| 161 | .filter-head h2{margin:0} |
| 162 | .filter-head span{font-family:var(--mono);font-size:12.5px;color:var(--ink-3);white-space:nowrap} |
| 163 | .filter-tabs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:18px} |
| 164 | .filter-tab{display:inline-flex;align-items:center;min-height:34px;padding:7px 13px;border-radius:12px;border:1px solid var(--line); |
| 165 | background:var(--bg-soft);color:var(--ink-2);text-decoration:none;font-size:13.5px;font-weight:600} |
| 166 | .filter-tab:hover{border-color:color-mix(in oklch,var(--accent) 34%,var(--line));color:var(--accent)} |
| 167 | .filter-tab.active{background:var(--accent);border-color:var(--accent);color:#fff} |
| 168 | .facet-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px} |
| 169 | .facet-grid section{min-width:0} |
| 170 | .facet-grid h3{font-size:12px;font-weight:600;color:var(--ink-3);margin:0 0 8px;text-transform:uppercase} |
| 171 | .facet-list{display:flex;flex-wrap:wrap;gap:7px;min-width:0} |
| 172 | .facet-chip{display:inline-grid;grid-template-columns:minmax(0,auto) auto;align-items:center;gap:8px;max-width:100%;min-width:0; |
| 173 | padding:6px 9px;border:1px solid var(--line);border-radius:11px;background:#fff;color:var(--ink-2);text-decoration:none;font-size:13px} |
| 174 | .facet-chip:hover{border-color:color-mix(in oklch,var(--accent) 30%,var(--line));color:var(--accent)} |
| 175 | .facet-chip.active{background:var(--accent-soft);border-color:color-mix(in oklch,var(--accent) 45%,var(--line));color:var(--accent)} |
| 176 | .facet-chip b{font-family:var(--mono);font-size:12px;color:inherit} |
| 177 | .facet-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0} |
| 178 | .facet-more{flex-basis:100%;min-width:0} |
| 179 | .facet-more summary{display:inline-flex;align-items:center;min-height:32px;padding:6px 10px;border:1px dashed var(--line);border-radius:11px;background:var(--bg-soft);color:var(--accent);font-size:13px;font-weight:700;cursor:pointer;list-style:none} |
| 180 | .facet-more summary::-webkit-details-marker{display:none} |
| 181 | .facet-more summary:hover{border-color:color-mix(in oklch,var(--accent) 34%,var(--line))} |
| 182 | .facet-more-list{display:flex;flex-wrap:wrap;gap:7px;max-height:150px;overflow:auto;margin-top:8px;padding:8px;border:1px solid var(--line);border-radius:13px;background:#fff} |
| 183 | .filter-empty{font-size:13px;color:var(--ink-3)} |
| 184 | .crash-card{overflow:hidden} |
| 185 | .crash-list{display:flex;flex-direction:column;gap:2px} |
| 186 | .crash-head,.crash-item{display:grid;grid-template-columns:minmax(300px,1fr) minmax(185px,.55fr) 150px 62px;gap:16px;align-items:center} |
| 187 | .crash-head{padding:0 12px 9px;color:var(--ink-3);font-size:12.5px;font-weight:600;border-bottom:1px solid var(--line)} |
| 188 | .crash-item{margin:0 -12px;padding:14px 12px;border-radius:14px;color:var(--ink);text-decoration:none;border-bottom:1px solid var(--line)} |
| 189 | .crash-item:hover{background:var(--bg-soft)} |
| 190 | .crash-fingerprint{display:flex;flex-direction:column;gap:3px;min-width:0} |
| 191 | .crash-fingerprint b{font-family:var(--mono);font-size:13px;color:var(--accent);font-weight:700} |
| 192 | .crash-fingerprint small,.crash-scope small{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 193 | .crash-summary{display:flex;flex-direction:column;gap:5px;min-width:0} |
| 194 | .crash-summary>span{font-family:var(--mono);font-size:12.8px;line-height:1.45;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 195 | .crash-summary small{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} |
| 196 | .crash-summary em{font-size:12px;font-style:normal;color:oklch(0.55 0.18 25)} |
| 197 | .crash-scope{display:flex;flex-direction:column;gap:4px;min-width:0} |
| 198 | .crash-health{display:flex;flex-wrap:wrap;gap:6px;align-items:center} |
| 199 | .crash-count{font-family:var(--mono);font-size:14px;font-weight:700;color:var(--ink);text-align:right} |
| 200 | .group-hero{padding:28px 0 22px} |
| 201 | .group-nav{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px} |
| 202 | .group-nav .back{margin:0} |
| 203 | .group-title{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:8px} |
| 204 | .group-title h1{margin:0;font-family:var(--mono);font-size:34px} |
| 205 | .group-summary{font-size:17px;margin:0 0 12px} |
| 206 | .group-tags{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 18px} |
| 207 | .group-tags span{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:11px;background:#fff;padding:6px 9px;font-size:13px;color:var(--ink-2)} |
| 208 | .group-tags b{font-size:12px;color:var(--ink-3);font-weight:600} |
| 209 | .crash-list.compact .crash-head{display:none} |
| 210 | .crash-list.compact .crash-item{padding:12px;margin:0 -12px;grid-template-columns:minmax(260px,1fr) minmax(160px,.5fr) 140px 52px} |
| 211 | .group-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px} |
| 212 | .group-metrics div{border:1px solid var(--line);border-radius:14px;background:var(--bg-soft);padding:12px 13px;min-width:0} |
| 213 | .group-metrics span{display:block;font-size:12px;color:var(--ink-3);margin-bottom:5px} |
| 214 | .group-metrics b{display:block;font-family:var(--mono);font-size:13px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
| 215 | .group-note{margin-top:12px;color:var(--ink-2);font-size:13.5px} |
| 216 | .sample-card h2{display:flex;align-items:center;justify-content:space-between;gap:12px} |
| 217 | .sample-list{display:flex;flex-direction:column;gap:10px} |
| 218 | .sample{border:1px solid var(--line);border-radius:16px;background:#fff;overflow:hidden} |
| 219 | .sample summary{display:grid;grid-template-columns:140px minmax(0,1fr) 170px;gap:14px;align-items:center;padding:14px 16px;cursor:pointer;list-style:none} |
| 220 | .sample summary::-webkit-details-marker{display:none} |
| 221 | .sample summary:hover{background:var(--bg-soft)} |
| 222 | .sample[open] summary{border-bottom:1px solid var(--line)} |
| 223 | .sample-id{display:flex;flex-direction:column;gap:3px;min-width:0} |
| 224 | .sample-id b{font-family:var(--mono);font-size:14px;color:var(--ink)} |
| 225 | .sample-id small,.sample-time{font-family:var(--mono);font-size:12px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
| 226 | .sample-title{font-family:var(--mono);font-size:12.8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} |
| 227 | .sample-time{text-align:right} |
| 228 | .sample-body{padding:14px 16px 16px} |
| 229 | .sample-meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px} |
| 230 | .sample-meta span{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:10px;padding:5px 8px;color:var(--ink-2);font-size:12.5px} |
| 231 | .sample-meta b{color:var(--ink-3);font-weight:600} |
| 232 | .sample-actions{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px} |
| 233 | .sample-more{border:1px dashed var(--line);border-radius:16px;background:var(--bg-soft);padding:10px} |
| 234 | .sample-more>summary{cursor:pointer;list-style:none;color:var(--accent);font-size:13.5px;font-weight:700;padding:4px 6px} |
| 235 | .sample-more>summary::-webkit-details-marker{display:none} |
| 236 | .sample-more-list{display:flex;flex-direction:column;gap:10px;margin-top:10px} |
| 237 | .copy-btn[data-state="copied"]{background:oklch(0.95 0.05 150);color:oklch(0.48 0.13 150)} |
| 238 | .copy-btn[data-state="copied"] .copy-label{display:none} |
| 239 | .copy-btn[data-state="copied"]::after{content:"Copied"} |
| 240 | body[data-lang="zh"] .copy-btn[data-state="copied"]::after{content:"已复制"} |
| 241 | .copy-btn[data-state="failed"]{background:oklch(0.96 0.03 25);color:oklch(0.55 0.18 25)} |
| 242 | .copy-btn[data-state="failed"] .copy-label{display:none} |
| 243 | .copy-btn[data-state="failed"]::after{content:"Copy failed"} |
| 244 | body[data-lang="zh"] .copy-btn[data-state="failed"]::after{content:"复制失败"} |
| 245 | .sample-nested{margin-top:10px} |
| 246 | .sample-nested summary{cursor:pointer;color:var(--accent);font-size:13px;margin-bottom:8px} |
| 247 | .sample-nested pre{margin-top:8px} |
| 248 | .manage-card{margin-top:20px} |
| 249 | .manage-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px} |
| 250 | .manage-head h2{margin:0} |
| 251 | .manage-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end} |
| 252 | .manage-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px} |
| 253 | .manage-form{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:end} |
| 254 | .manage-form.wide{grid-column:1/-1} |
| 255 | .manage-form label{display:flex;flex-direction:column;gap:6px;font-size:12.5px;color:var(--ink-3);font-weight:600} |
| 256 | .manage-form input,.manage-form select{min-width:0} |
| 257 | form.inline{display:inline} |
| 258 | .authwrap{max-width:400px;margin:6vh auto 0} |
| 259 | .authcard{background:#fff;border:1px solid var(--line);border-radius:24px;padding:32px 34px;box-shadow:var(--shadow-1)} |
| 260 | .authcard h1{font-size:23px;margin:0 0 4px} |
| 261 | .authcard .sub{margin-bottom:22px} |
| 262 | .field{display:block;margin-bottom:14px} |
| 263 | .field label{display:block;font-size:13px;color:var(--ink-2);margin-bottom:6px} |
| 264 | .field input,select{width:100%;font:inherit;font-size:14.5px;color:var(--ink);background:#fff;border:1px solid var(--line);border-radius:12px;padding:10px 13px;outline:none} |
| 265 | .field input:focus,select:focus{border-color:var(--accent)} |
| 266 | .btn{font:inherit;font-size:14.5px;font-weight:600;color:#fff;background:var(--accent);border:none;border-radius:12px;padding:10px 18px;cursor:pointer} |
| 267 | .btn:hover{filter:brightness(1.05)} |
| 268 | .btn.block{width:100%} |
| 269 | .btn.ghost{background:var(--accent-soft);color:var(--accent)} |
| 270 | .btn.danger{background:oklch(0.95 0.03 25);color:oklch(0.55 0.18 25)} |
| 271 | .btn.sm{font-size:13px;padding:6px 12px;border-radius:10px} |
| 272 | .notice{font-size:13.5px;padding:10px 14px;border-radius:12px;margin-bottom:16px} |
| 273 | .notice.err{background:oklch(0.96 0.03 25);color:oklch(0.5 0.18 25)} |
| 274 | .notice.ok{background:oklch(0.95 0.05 150);color:oklch(0.45 0.13 150)} |
| 275 | .alt{margin-top:18px;font-size:13.5px;color:var(--ink-3);text-align:center} |
| 276 | .alt a{color:var(--accent);text-decoration:none} |
| 277 | .actions{display:flex;gap:8px;align-items:center} |
| 278 | td select{width:auto;padding:6px 10px;border-radius:10px} |
| 279 | .note-edit{margin-top:12px;display:flex;gap:8px;max-width:560px} |
| 280 | .note-edit input{flex:1} |
| 281 | @media(max-width:1100px){.overview-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.module-nav{grid-template-columns:repeat(2,minmax(0,1fr))}.health-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.module-split,.module-split.wide,.preference-compare{grid-template-columns:1fr}} |
| 282 | @media(max-width:900px){.hero-line{flex-direction:column}.facet-grid{grid-template-columns:1fr}.crash-head{display:none}.crash-item,.crash-list.compact .crash-item{grid-template-columns:1fr;gap:10px} |
| 283 | .crash-health{justify-content:flex-start}.crash-count{text-align:left}.filter-head,.module-head{align-items:flex-start;flex-direction:column;gap:8px}.module-actions{justify-content:flex-start} |
| 284 | .group-metrics{grid-template-columns:1fr 1fr}.sample summary{grid-template-columns:1fr}.sample-time{text-align:left}.manage-head{flex-direction:column}.manage-actions{justify-content:flex-start}.manage-grid{grid-template-columns:1fr}.manage-form.wide{grid-column:auto}} |
| 285 | @media(max-width:760px){header{height:auto;min-height:68px;flex-wrap:wrap;padding:14px 0}.lang-switch{margin-left:0}.nav{width:100%;flex-wrap:wrap;gap:8px}.site-nav{overflow-x:auto;padding-bottom:2px}.site-nav a{white-space:nowrap}.chip{min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}.grid{grid-template-columns:1fr}.overview-grid,.module-nav,.health-grid{grid-template-columns:1fr}.metrics,.pref-metrics{grid-template-columns:1fr}.row{grid-template-columns:minmax(0,1fr) minmax(70px,.8fr) 3.2em}.card-title-row{align-items:flex-start;flex-direction:column}.filter-card{position:static}.wrap{padding:0 16px 48px}} |
| 286 | `; |
| 287 | |
| 288 | const LANG_SWITCH = `<span class="lang-switch" role="group" aria-label="Language"><button type="button" data-lang-option="zh">中文</button><button type="button" data-lang-option="en">EN</button></span>`; |
| 289 | |
| 290 | const LANG_SCRIPT = `<script>(()=>{const k="reasonix.stats.lang";const b=[...document.querySelectorAll("[data-lang-option]")];const pick=()=>{try{return localStorage.getItem(k)}catch{return""}};const fallback=()=>((navigator.language||"").toLowerCase().startsWith("zh")?"zh":"en");const set=(v)=>{const lang=v==="en"?"en":"zh";document.body.dataset.lang=lang;b.forEach((x)=>{const on=x.dataset.langOption===lang;x.classList.toggle("active",on);x.setAttribute("aria-pressed",String(on))});try{localStorage.setItem(k,lang)}catch{}};b.forEach((x)=>x.addEventListener("click",()=>set(x.dataset.langOption||"zh")));set(pick()||fallback())})();(()=>{const fallback=(t)=>{const a=document.createElement("textarea");a.value=t;a.setAttribute("readonly","");a.style.position="fixed";a.style.opacity="0";document.body.appendChild(a);a.select();let ok=false;try{ok=document.execCommand("copy")}catch{}a.remove();return ok};document.addEventListener("click",async(e)=>{const b=e.target.closest("[data-copy]");if(!b)return;const t=b.dataset.copy||"";let ok=false;try{if(navigator.clipboard){await navigator.clipboard.writeText(t);ok=true}}catch{}if(!ok)ok=fallback(t);b.dataset.state=ok?"copied":"failed";setTimeout(()=>{delete b.dataset.state},1400)})})()</script>`; |
| 291 | |
| 292 | const STATS_ROUTE_SCRIPT = `<script>(()=>{if(location.pathname!=="/stats"||!location.hash)return;const m={diagnostics:"diagnostics",usage:"",preferences:"preferences",health:"health"};const key=location.hash.slice(1);if(m[key]===undefined)return;location.replace("/stats"+(m[key]?"/"+m[key]:"")+location.search)})()</script>`; |
| 293 | |
| 294 | export function page(title: string, crumb: string, body: string, nav = ""): string { |
| 295 | return `<!doctype html><html lang="en"><head><meta charset="utf-8"> |
| 296 | <meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"> |
| 297 | <title>${esc(title)}</title><style>${CSS}</style></head><body><div class="wrap"> |
| 298 | <header><a class="brand" href="https://reasonix.io">${LOGO}</a><a class="brand" href="https://reasonix.io">Reasonix</a><span class="crumb">/ ${esc(crumb)}</span>${LANG_SWITCH}${nav ? `<span class="nav">${nav}</span>` : ""}</header> |
| 299 | ${body}</div>${LANG_SCRIPT}${STATS_ROUTE_SCRIPT}</body></html>`; |
| 300 | } |
| 301 | |
| 302 | export function html(body: string): Response { |
| 303 | return new Response(body, { headers: { "content-type": "text/html; charset=utf-8" } }); |
| 304 | } |
| 305 | |
| 306 | export function redirect(location: string, cookie?: string): Response { |
| 307 | const headers: Record<string, string> = { location }; |
| 308 | if (cookie) headers["set-cookie"] = cookie; |
| 309 | return new Response(null, { status: 303, headers }); |
| 310 | } |
| 311 |