Stacje sejsmiczne — tabela telemetryczna
Wizualizacja danychPodgląd
Ładowanie podglądu…
Ciemny rejestr pomiarów z filtrem regionu, sortowaniem parametrów i czytelną paginacją.
Kod źródłowy
Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.
HTML
<main class="seis-shell" id="seismic-table">
<header class="seis-head"><div><p class="eyebrow">SIEĆ S-14 / ODCZYT NA ŻYWO</p><h1>Rejestr drgań</h1></div><div class="pulse" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div></header>
<section class="seis-controls" aria-label="Filtry rejestru">
<label>Znajdź stację lub miejsce<input type="search" data-search placeholder="np. Karpaty"></label>
<label>Region<select data-filter id="seis-region"><option value="">Wszystkie regiony</option><option>Europa</option><option>Azja</option><option>Oceania</option><option>Ameryka</option></select></label>
<button type="button" data-reset>Wyczyść</button>
</section>
<div class="seis-meta"><p role="status" aria-live="polite"></p><span>UTC+2 · 13.10.2026</span></div>
<div class="table-scroll" tabindex="0" aria-label="Tabela odczytów sejsmicznych — przewijaj poziomo na małym ekranie">
<table><caption>Ostatnie odczyty stacji sejsmicznych</caption><thead><tr>
<th scope="col"><button type="button" data-sort="station">Stacja <span data-sort-mark aria-hidden="true">↕</span></button></th>
<th scope="col"><button type="button" data-sort="place">Obszar <span data-sort-mark aria-hidden="true">↕</span></button></th>
<th scope="col"><button type="button" data-sort="mag">Magnituda <span data-sort-mark aria-hidden="true">↕</span></button></th>
<th scope="col"><button type="button" data-sort="depth">Głębokość <span data-sort-mark aria-hidden="true">↕</span></button></th>
<th scope="col"><button type="button" data-sort="time">Czas <span data-sort-mark aria-hidden="true">↕</span></button></th>
</tr></thead><tbody><tr><td colspan="5">Ładowanie odczytów…</td></tr></tbody></table>
</div>
<nav class="seis-pages" data-pages aria-label="Stronicowanie tabeli"></nav>
</main>
CSS
*{box-sizing:border-box}html{color-scheme:dark}body{margin:0;min-width:0;background:#07100e;color:#e8f0e7;font-family:"IBM Plex Mono","SFMono-Regular",Consolas,monospace}.seis-shell{min-height:100vh;padding:clamp(18px,4vw,48px);background:#07100e;border-top:6px solid #c9ff4a}.seis-head{display:flex;align-items:end;justify-content:space-between;gap:24px;border-bottom:1px solid #52615b;padding-bottom:18px}.eyebrow{margin:0 0 8px;color:#c9ff4a;font-size:12px;letter-spacing:.13em}.seis-head h1{margin:0;font-size:clamp(34px,7vw,76px);line-height:.9;font-weight:600;letter-spacing:-.06em}.pulse{display:flex;align-items:center;gap:4px;height:54px}.pulse i{display:block;width:4px;background:#c9ff4a}.pulse i:nth-child(1),.pulse i:nth-child(5){height:10px}.pulse i:nth-child(2),.pulse i:nth-child(4){height:28px}.pulse i:nth-child(3){height:52px}.seis-controls{display:grid;grid-template-columns:minmax(220px,1fr) minmax(170px,.45fr) auto;gap:12px;padding:18px 0}.seis-controls label{display:grid;gap:7px;color:#aab6b0;font-size:12px}.seis-controls input,.seis-controls select,.seis-controls button{min-height:44px;border:1px solid #52615b;border-radius:0;background:#101b17;color:#f6faf4;font:inherit;padding:0 12px}.seis-controls button{align-self:end;border-color:#c9ff4a;color:#c9ff4a;cursor:pointer}.seis-meta{display:flex;justify-content:space-between;gap:18px;color:#aab6b0;font-size:12px;padding:5px 0 10px}.seis-meta p{margin:0;color:#e8f0e7}.table-scroll{max-width:100%;overflow-x:auto;border:1px solid #52615b}.table-scroll:focus-visible{outline:3px solid #c9ff4a;outline-offset:3px}table{width:100%;min-width:720px;border-collapse:collapse}caption{text-align:left;padding:13px 14px;color:#c9ff4a;border-bottom:1px solid #52615b;font-weight:700}th,td{text-align:left;border-bottom:1px solid #33413c;padding:12px 14px;white-space:nowrap}thead{background:#101b17}th button{width:100%;min-height:44px;padding:0;border:0;background:transparent;color:#bdc8c2;font:inherit;text-align:left;cursor:pointer}th[aria-sort] button{color:#c9ff4a}tbody tr:hover{background:#111e19}tbody th{color:#c9ff4a;font-weight:600}td small{display:block;margin-top:3px;color:#8c9a93;font-size:10px}.mag{font-size:18px;font-weight:700}.empty-row{text-align:center;padding:38px;color:#bdc8c2}.seis-pages{display:flex;flex-wrap:wrap;gap:6px;padding-top:14px}.seis-pages button{min-width:44px;min-height:44px;border:1px solid #52615b;border-radius:0;background:transparent;color:#e8f0e7;font:inherit;padding:0 12px;cursor:pointer}.seis-pages button[aria-current="page"]{background:#c9ff4a;color:#07100e;border-color:#c9ff4a}.seis-pages button:disabled{opacity:.36;cursor:not-allowed}button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid #c9ff4a;outline-offset:2px}@media(max-width:620px){.seis-shell{padding:18px 14px}.seis-head{align-items:start}.pulse{transform:scale(.72);transform-origin:right top}.seis-controls{grid-template-columns:1fr}.seis-meta{align-items:start;flex-direction:column;gap:4px}.seis-pages button:first-child,.seis-pages button:last-child{flex:1}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
JavaScript
$(() => {
const records = [{"station":"BESK-04","region":"Europa","place":"Beskid Niski","mag":2.1,"depth":7,"time":"08:14"},{"station":"TATR-11","region":"Europa","place":"Tatry Zachodnie","mag":1.6,"depth":5,"time":"08:37"},{"station":"RILA-02","region":"Europa","place":"Bułgaria","mag":3.4,"depth":18,"time":"09:05"},{"station":"ETNA-07","region":"Europa","place":"Sycylia","mag":2.8,"depth":11,"time":"09:42"},{"station":"ALPY-09","region":"Europa","place":"Tyrol","mag":1.9,"depth":6,"time":"10:18"},{"station":"KARP-15","region":"Europa","place":"Karpaty Wschodnie","mag":3.1,"depth":24,"time":"11:06"},{"station":"PIND-03","region":"Europa","place":"Grecja","mag":2.5,"depth":13,"time":"11:49"},{"station":"ISLD-08","region":"Europa","place":"Islandia","mag":4.2,"depth":9,"time":"12:22"},{"station":"HONS-18","region":"Azja","place":"Honsiu","mag":4.8,"depth":36,"time":"07:58"},{"station":"JAVA-06","region":"Azja","place":"Jawa","mag":5.1,"depth":52,"time":"10:44"},{"station":"MIND-12","region":"Azja","place":"Mindanao","mag":4.5,"depth":41,"time":"12:03"},{"station":"NZ-SO1","region":"Oceania","place":"Wyspa Południowa","mag":3.7,"depth":16,"time":"08:53"},{"station":"ANDY-21","region":"Ameryka","place":"Andy Środkowe","mag":4.6,"depth":61,"time":"09:29"},{"station":"CASC-10","region":"Ameryka","place":"Kaskady","mag":2.9,"depth":12,"time":"11:31"}];
const numericColumns = new Set(["mag", "depth"]);
const collator = new Intl.Collator('pl', { numeric: true, sensitivity: 'base' });
const state = { query: '', sort: 'mag', direction: 'desc', page: 1 };
const pageSize = 4;
const $root = $('#seismic-table');
const $body = $root.find('tbody');
const $pages = $root.find('[data-pages]');
const $status = $root.find('[role="status"]');
function filteredRecords() {
const query = state.query.trim().toLocaleLowerCase('pl');
return records.filter((record) => {
const matchesQuery = !query || Object.values(record).join(' ').toLocaleLowerCase('pl').includes(query);
const region = $('#seis-region').val();
const matchesFilters = !region || record.region === region;
return matchesQuery && matchesFilters;
});
}
function compare(left, right) {
const key = state.sort;
const result = numericColumns.has(key)
? Number(left[key]) - Number(right[key])
: collator.compare(String(left[key]), String(right[key]));
return state.direction === 'asc' ? result : -result;
}
function rowTemplate(record) {
return `<tr><th scope="row">${record.station}</th><td>${record.place}<small>${record.region}</small></td><td class="mag">${record.mag.toFixed(1)}</td><td>${record.depth} km</td><td>${record.time}</td></tr>`;
}
function render() {
const result = filteredRecords().sort(compare);
const totalPages = Math.max(1, Math.ceil(result.length / pageSize));
state.page = Math.min(state.page, totalPages);
const visible = result.slice((state.page - 1) * pageSize, state.page * pageSize);
$body.html(visible.length
? visible.map(rowTemplate).join('')
: '<tr><td class="empty-row" colspan="5">Brak wyników. Zmień filtry lub wyczyść wyszukiwanie.</td></tr>');
$root.find('thead th').removeAttr('aria-sort');
$root.find('[data-sort]').each(function () {
const active = this.dataset.sort === state.sort;
$(this).find('[data-sort-mark]').text(active ? (state.direction === 'asc' ? '↑' : '↓') : '↕');
if (active) $(this).closest('th').attr('aria-sort', state.direction === 'asc' ? 'ascending' : 'descending');
});
const buttons = [];
buttons.push(`<button type="button" data-page="${state.page - 1}" ${state.page === 1 ? 'disabled' : ''}>Poprzednia</button>`);
for (let page = 1; page <= totalPages; page += 1) {
buttons.push(`<button type="button" data-page="${page}" ${page === state.page ? 'aria-current="page"' : ''} aria-label="Strona ${page}">${page}</button>`);
}
buttons.push(`<button type="button" data-page="${state.page + 1}" ${state.page === totalPages ? 'disabled' : ''}>Następna</button>`);
$pages.html(buttons.join(''));
const direction = state.direction === 'asc' ? 'rosnąco' : 'malejąco';
$status.text(`Wyniki: ${result.length} · Strona ${state.page} z ${totalPages} · Sortowanie: ${direction}.`);
}
$root.on('input', '[data-search]', function () {
state.query = this.value;
state.page = 1;
render();
});
$root.on('change', '[data-filter]', function () {
state.page = 1;
render();
});
$root.on('click', '[data-sort]', function () {
const key = this.dataset.sort;
state.direction = state.sort === key && state.direction === 'asc' ? 'desc' : 'asc';
state.sort = key;
state.page = 1;
render();
this.focus();
});
$root.on('click', '[data-page]', function () {
if (this.disabled) return;
state.page = Number(this.dataset.page);
render();
$pages.find(`[data-page="${state.page}"]`).focus();
});
$root.on('click', '[data-reset]', () => {
$root.find('[data-search]').val('');
$root.find('select[data-filter]').prop('selectedIndex', 0);
$root.find('input[type="radio"][data-filter]').first().prop('checked', true);
state.query = '';
state.sort = 'mag';
state.direction = 'desc';
state.page = 1;
render();
$root.find('[data-search]').focus();
});
render();
});
Edytuj swój pen
Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.