*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --bg: #0f1117; --surface: #1a1d27; --border: #2a2d3a; --accent: #7c3aed; --accent2: #a78bfa; --text: #e2e8f0; --muted: #64748b; --green: #22c55e; --red: #ef4444; --yellow: #f59e0b; --mono: 'Fira Code', 'Cascadia Code', 'Consolas', monospace; } body { font-family: system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; } .app { max-width: 920px; margin: 0 auto; padding: 2rem 1.5rem; } header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2rem; flex-wrap: wrap; gap: 1rem; } h1 { font-size: 1.6rem; font-weight: 700; color: var(--accent2); } nav { display: flex; gap: .5rem; } .tab-btn { padding: .45rem 1.1rem; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--muted); cursor: pointer; font-size: .9rem; transition: all .15s; } .tab-btn:hover { color: var(--text); border-color: var(--accent); } .tab-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); } .tab { display: none; } .tab.active { display: block; } /* ── FORM ───────────────────────────────────────────────────────────────────── */ .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; margin-bottom: 1.5rem; } .form-group { display: flex; flex-direction: column; gap: .4rem; } .form-group.full { grid-column: 1 / -1; } label { font-size: .85rem; color: var(--muted); font-weight: 500; } .hint { font-weight: 400; font-size: .78rem; color: var(--muted); } input[type=url], input[type=text], input[type=number], select, textarea { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: .55rem .75rem; font-size: .9rem; outline: none; transition: border-color .15s; width: 100%; } input:focus, select:focus, textarea:focus { border-color: var(--accent); } textarea { font-family: var(--mono); resize: vertical; } .range-row { display: flex; align-items: center; gap: .75rem; } .range-row input[type=range] { flex: 1; accent-color: var(--accent); } .range-row input[type=number] { width: 80px; flex-shrink: 0; } .btn-primary { background: var(--accent); color: #fff; border: none; border-radius: 8px; padding: .65rem 2rem; font-size: 1rem; font-weight: 600; cursor: pointer; transition: background .15s, opacity .15s; } .btn-primary:hover { background: #6d28d9; } .btn-primary:disabled { opacity: .5; cursor: not-allowed; } .btn-secondary { background: transparent; color: var(--accent2); border: 1px solid var(--accent); border-radius: 6px; padding: .4rem 1rem; font-size: .85rem; cursor: pointer; transition: background .15s; } .btn-secondary:hover { background: rgba(124,58,237,.15); } /* TOGGLE SWITCH */ .toggle-row { display: flex; align-items: center; gap: .75rem; margin-top: .15rem; } .toggle { display: flex; align-items: center; gap: .55rem; cursor: pointer; } .toggle input { display: none; } .toggle-track { width: 38px; height: 20px; background: var(--border); border-radius: 999px; position: relative; transition: background .2s; flex-shrink: 0; } .toggle-track::after { content: ''; position: absolute; width: 14px; height: 14px; background: #fff; border-radius: 50%; top: 3px; left: 3px; transition: transform .2s; } .toggle input:checked + .toggle-track { background: var(--accent); } .toggle input:checked + .toggle-track::after { transform: translateX(18px); } .toggle-label { font-size: .88rem; color: var(--text); } /* DEVICE TOGGLE */ .device-toggle { display: flex; gap: 0; margin-top: .15rem; } .device-toggle input[type=radio] { display: none; } .device-btn { flex: 1; text-align: center; padding: .45rem .75rem; font-size: .85rem; border: 1px solid var(--border); cursor: pointer; transition: background .15s, color .15s; color: var(--muted); background: transparent; } .device-btn:first-of-type { border-radius: 6px 0 0 6px; } .device-btn:last-of-type { border-radius: 0 6px 6px 0; border-left: none; } .device-toggle input[type=radio]:checked + .device-btn { background: var(--accent); color: #fff; border-color: var(--accent); } /* PILLS */ .pill { display: inline-block; font-size: .72rem; font-weight: 600; padding: .15rem .5rem; border-radius: 999px; letter-spacing: .04em; } .pill.cached { background: rgba(34,197,94,.12); color: #4ade80; } .pill.no-cache { background: rgba(245,158,11,.12); color: #fbbf24; } .pill.bust { background: rgba(239,68,68,.12); color: #f87171; } .pill.gzip { background: rgba(124,58,237,.15); color: var(--accent2); } .pill.no-gzip { background: rgba(100,116,139,.12); color: var(--muted); } .pill.desktop { background: rgba(56,189,248,.12); color: #38bdf8; } .pill.mobile { background: rgba(251,146,60,.12); color: #fb923c; } /* ── RESULT HEADER ──────────────────────────────────────────────────────────── */ .result-header { display: flex; align-items: center; gap: 1rem; margin: 2rem 0 1.5rem; } .result-header h2 { font-size: 1.1rem; } .badge { font-size: .75rem; font-weight: 600; padding: .25rem .65rem; border-radius: 999px; text-transform: uppercase; letter-spacing: .05em; } .badge.running { background: rgba(245,158,11,.15); color: var(--yellow); } .badge.completed { background: rgba(34,197,94,.15); color: var(--green); } .badge.failed { background: rgba(239,68,68,.15); color: var(--red); } /* ── GAUGE CIRCLES ──────────────────────────────────────────────────────────── */ #gauges-panel { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem 1rem 1.2rem; margin-bottom: 1rem; } .gauges-main { display: flex; justify-content: center; gap: 2.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; } .gauge-wrap { position: relative; width: 130px; flex-shrink: 0; } .gauge-svg { width: 130px; height: 130px; transform: rotate(-90deg); overflow: visible; } .gauge-track { fill: none; stroke: var(--border); stroke-width: 8; } .gauge-ring { fill: none; stroke: var(--border); stroke-width: 8; stroke-linecap: round; stroke-dasharray: 314.16; stroke-dashoffset: 314.16; transition: stroke-dashoffset .7s cubic-bezier(.4,0,.2,1), stroke .5s ease; filter: drop-shadow(0 0 5px currentColor); } /* Pulse animation while test is live */ @keyframes gauge-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .7; } } #gauges-panel.live .gauge-ring { animation: gauge-pulse 2s ease-in-out infinite; } .gauge-inner { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; } .gauge-value { font-size: 2rem; font-weight: 800; font-family: var(--mono); line-height: 1; } .gauge-sub { font-size: .75rem; color: var(--muted); margin-top: .1rem; } .gauge-title { font-size: .72rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: .35rem; } /* ── STATS STRIP ────────────────────────────────────────────────────────────── */ .stats-strip { display: grid; grid-template-columns: repeat(6, 1fr); gap: .5rem; border-top: 1px solid var(--border); padding-top: 1rem; } .stat-cell { display: flex; flex-direction: column; align-items: center; gap: .2rem; } .sc-val { font-size: 1.05rem; font-weight: 700; font-family: var(--mono); transition: color .4s; } .sc-lbl { font-size: .68rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; } /* ── TIMING WATERFALL ───────────────────────────────────────────────────────── */ .waterfall-wrap { margin-top: .75rem; border-top: 1px solid var(--border); padding-top: .9rem; } .waterfall-title { font-size: .78rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-bottom: .6rem; } .wf-hint { font-weight: 400; text-transform: none; letter-spacing: 0; } .wf-row { display: grid; grid-template-columns: 120px 1fr 70px; align-items: center; gap: .5rem; margin-bottom: .35rem; } .wf-label { font-size: .78rem; color: var(--muted); white-space: nowrap; } .wf-bar-wrap { background: var(--border); border-radius: 999px; height: 8px; overflow: hidden; } .wf-bar { height: 100%; border-radius: 999px; width: 0%; transition: width .6s cubic-bezier(.4,0,.2,1); } .wf-val { font-size: .78rem; font-family: var(--mono); color: var(--text); text-align: right; } /* Phase colours */ .wf-blocked { background: #818cf8; } /* indigo — DNS/blocked */ .wf-connecting { background: #38bdf8; } /* sky — TCP connect */ .wf-tls { background: #a78bfa; } /* purple — TLS */ .wf-sending { background: #34d399; } /* emerald — sending */ .wf-ttfb { background: #f59e0b; } /* amber — TTFB (most important) */ .wf-receiving { background: #60a5fa; } /* blue — receiving */ .wf-p95-row { display: flex; align-items: center; gap: .5rem; margin-top: .4rem; padding-top: .4rem; border-top: 1px dashed var(--border); flex-wrap: wrap; } .wf-p95-label { font-size: .72rem; color: var(--muted); } .wf-p95-val { font-size: .85rem; font-weight: 700; font-family: var(--mono); color: var(--yellow); } /* ── BANDWIDTH ROW ──────────────────────────────────────────────────────────── */ .bw-row { display: flex; align-items: center; gap: 1rem; padding: .8rem 1rem; margin-top: .75rem; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; flex-wrap: wrap; } .bw-item { display: flex; align-items: center; gap: .5rem; } .bw-label { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); } .bw-val { font-size: .95rem; font-weight: 700; font-family: var(--mono); transition: color .4s; } .bw-divider { width: 1px; height: 1.2rem; background: var(--border); flex-shrink: 0; } .bw-warning { font-size: .78rem; color: var(--yellow); background: rgba(245,158,11,.08); border: 1px solid rgba(245,158,11,.25); border-radius: 6px; padding: .25rem .65rem; margin-left: auto; } /* ── THRESHOLD BANNER ───────────────────────────────────────────────────────── */ #threshold-banner { border-radius: 8px; padding: .85rem 1rem; margin-top: 1rem; border: 1px solid; } .threshold-banner.pass { background: rgba(34,197,94,.08); border-color: rgba(34,197,94,.3); color: var(--green); } .threshold-banner.fail { background: rgba(239,68,68,.08); border-color: rgba(239,68,68,.3); color: var(--red); } .threshold-banner ul { list-style: none; margin-top: .4rem; padding: 0; } .threshold-banner li { font-size: .85rem; padding: .15rem 0; font-family: var(--mono); } .threshold-banner li.pass { color: var(--green); } .threshold-banner li.fail { color: var(--red); } /* ── COLLAPSIBLE LOG ────────────────────────────────────────────────────────── */ #log-details { margin-top: 1rem; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; } #log-details summary { padding: .6rem 1rem; cursor: pointer; font-size: .85rem; color: var(--muted); user-select: none; list-style: none; display: flex; align-items: center; gap: .5rem; } #log-details summary::before { content: '▶'; font-size: .7rem; transition: transform .15s; } #log-details[open] summary::before { transform: rotate(90deg); } #log-details summary:hover { color: var(--text); background: rgba(255,255,255,.02); } #output-log { background: #0a0c12; padding: 1rem; font-family: var(--mono); font-size: .78rem; line-height: 1.6; max-height: 380px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; color: #a5f3fc; border-top: 1px solid var(--border); } /* ── HISTORY ────────────────────────────────────────────────────────────────── */ .history-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem; } .history-item { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: .9rem 1.1rem; margin-bottom: .75rem; display: grid; grid-template-columns: 48px 1fr auto; gap: .85rem; align-items: center; cursor: pointer; transition: border-color .15s; } .history-item:hover { border-color: var(--accent); } .history-left { display: flex; align-items: center; justify-content: center; } .mini-gauge { width: 44px; height: 44px; overflow: visible; } .history-body { min-width: 0; } .history-body .row1 { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; } .history-body .url { font-weight: 600; word-break: break-all; font-size: .9rem; } .history-body .meta { font-size: .78rem; color: var(--muted); } .history-summary { font-size: .78rem; color: var(--muted); margin-top: .3rem; font-family: var(--mono); } .history-item .actions { display: flex; align-items: center; } .btn-del { background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: 1rem; padding: .25rem; border-radius: 4px; transition: color .15s; } .btn-del:hover { color: var(--red); } .empty { color: var(--muted); font-size: .9rem; } /* ── HISTORY DETAIL ─────────────────────────────────────────────────────────── */ #history-detail { background: var(--surface); border: 1px solid var(--accent); border-radius: 10px; padding: 1.2rem; margin-bottom: .75rem; } .detail-title { font-size: .95rem; font-weight: 600; margin-bottom: 1rem; color: var(--accent2); word-break: break-all; } .detail-gauges { display: flex; gap: 1.5rem; justify-content: center; flex-wrap: wrap; margin-bottom: 1rem; } .dg-wrap { position: relative; width: 80px; text-align: center; } .dg-svg { width: 80px; height: 80px; transform: rotate(-90deg); overflow: visible; } .dg-inner { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; } .dg-val { font-size: 1.2rem; font-weight: 800; font-family: var(--mono); line-height: 1; } .dg-unit { font-size: .65rem; color: var(--muted); } .dg-title { font-size: .6rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: .3rem; } .detail-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: .5rem; margin-bottom: 1rem; } .detail-stat { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: .5rem .6rem; display: flex; flex-direction: column; align-items: center; gap: .2rem; } .ds-val { font-size: .95rem; font-weight: 700; font-family: var(--mono); } .ds-lbl { font-size: .65rem; color: var(--muted); text-transform: uppercase; } #history-detail details { margin-top: .5rem; } #history-detail details summary { cursor: pointer; font-size: .82rem; color: var(--muted); padding: .3rem 0; user-select: none; list-style: none; } #history-detail details summary:hover { color: var(--text); } #history-detail pre { background: #0a0c12; border: 1px solid var(--border); border-radius: 6px; padding: .75rem; font-family: var(--mono); font-size: .75rem; max-height: 300px; overflow-y: auto; white-space: pre-wrap; color: #a5f3fc; margin-top: .5rem; } /* ── RESPONSIVE ─────────────────────────────────────────────────────────────── */ @media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } .form-group.full { grid-column: 1; } .stats-strip { grid-template-columns: repeat(3, 1fr); } .gauges-main { gap: 1.5rem; } .gauge-wrap, .gauge-svg { width: 110px; height: 110px; } .gauge-value { font-size: 1.6rem; } .history-item { grid-template-columns: 40px 1fr auto; } }