data-table-component
the brief, redacted

Design a dense data table with sorting affordances and a selected row state. Tests whether a model can make information density feel calm rather than cramped.

559 chars · 82 words · fingerprint 3b4a4ef0b3ee

raw output — byte exact

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Deployments</title>
<style>
  :root {
    color-scheme: light;
    --bg: #f2f4f7;
    --surface: #ffffff;
    --line: #e3e7ec;
    --line-strong: #d4dbe3;
    --text: #202833;
    --muted: #697586;
    --subtle: #8a95a3;
    --blue: #2f6fed;
    --blue-soft: #eef4ff;
    --blue-line: #9ebbf8;
    --green: #2d8659;
    --amber: #a56c13;
    --red: #b44747;
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    min-width: 980px;
    background: var(--bg);
    color: var(--text);
    font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    font-size: 13px;
    line-height: 1.4;
  }

  .page {
    padding: 58px 44px 72px;
  }

  .table-card {
    width: 1080px;
    margin: 0 auto;
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 8px;
    box-shadow: 0 7px 20px rgba(32, 40, 51, .06);
  }

  .card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 76px;
    padding: 19px 24px 18px;
    border-bottom: 1px solid var(--line);
  }

  .eyebrow {
    margin: 0 0 3px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: .08em;
    text-transform: uppercase;
  }

  h1 {
    margin: 0;
    color: var(--text);
    font-size: 18px;
    font-weight: 650;
    letter-spacing: -.01em;
  }

  .header-meta {
    display: flex;
    align-items: center;
    gap: 14px;
    color: var(--muted);
    font-size: 12px;
  }

  .count {
    padding-right: 14px;
    border-right: 1px solid var(--line);
  }

  .sync {
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }

  .sync-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--green);
  }

  .table-wrap {
    overflow: hidden;
  }

  table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
  }

  col.status { width: 154px; }
  col.service { width: 235px; }
  col.region { width: 145px; }
  col.deployed { width: 170px; }
  col.duration { width: 126px; }
  col.timestamp { width: 250px; }

  th {
    height: 43px;
    padding: 0 16px;
    border-bottom: 1px solid var(--line-strong);
    background: #fafbfc;
    color: var(--muted);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: .045em;
    text-align: left;
    text-transform: uppercase;
  }

  th:first-child, td:first-child { padding-left: 24px; }
  th:last-child, td:last-child { padding-right: 24px; }

  th button {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-align: left;
    text-transform: inherit;
    cursor: pointer;
  }

  th button:hover { color: var(--text); }

  th[aria-sort="ascending"] button,
  th[aria-sort="descending"] button {
    color: var(--text);
  }

  .sort-icon {
    position: relative;
    display: inline-flex;
    width: 12px;
    height: 14px;
    align-items: center;
    justify-content: center;
    color: var(--blue);
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
  }

  .sort-icon::after {
    content: "↑";
  }

  th[aria-sort="descending"] .sort-icon::after {
    content: "↓";
  }

  td {
    height: 61px;
    padding: 0 16px;
    border-bottom: 1px solid var(--line);
    color: var(--text);
    vertical-align: middle;
    white-space: nowrap;
  }

  tbody tr:last-child td { border-bottom: 0; }

  tbody tr {
    transition: background .12s ease, box-shadow .12s ease;
  }

  tbody tr:hover,
  tbody tr.is-hovered {
    background: #f5f8fd;
    box-shadow: inset 3px 0 0 #8eafea;
  }

  tbody tr.is-selected {
    background: var(--blue-soft);
    box-shadow: inset 3px 0 0 var(--blue);
  }

  tbody tr.is-selected:hover,
  tbody tr.is-selected.is-hovered {
    background: #eaf2ff;
  }

  .status-cell {
    display: flex;
    align-items: center;
    gap: 9px;
    font-weight: 600;
  }

  .status-mark {
    position: relative;
    display: inline-flex;
    width: 18px;
    height: 18px;
    align-items: center;
    justify-content: center;
    border: 1px solid currentColor;
    border-radius: 50%;
    font-size: 11px;
    font-weight: 750;
  }

  .status-mark::before { content: "✓"; }
  .status-running { color: var(--green); }
  .status-running .status-mark { background: #edf8f1; }
  .status-pending { color: var(--amber); }
  .status-pending .status-mark { background: #fff8e9; }
  .status-pending .status-mark::before { content: "…"; margin-top: -3px; }
  .status-failed { color: var(--red); }
  .status-failed .status-mark { background: #fff0f0; }
  .status-failed .status-mark::before { content: "!"; }

  .service-name {
    display: block;
    color: var(--text);
    font-weight: 620;
  }

  .service-id {
    display: block;
    margin-top: 2px;
    color: var(--subtle);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
  }

  .region-name, .deployer { color: #394554; }
  .region-code {
    display: block;
    margin-top: 2px;
    color: var(--subtle);
    font-size: 11px;
  }

  .duration {
    color: #465363;
    font-variant-numeric: tabular-nums;
  }

  .timestamp {
    color: #4b5868;
    font-variant-numeric: tabular-nums;
  }

  .selected-box {
    width: 15px;
    height: 15px;
    margin: 0 10px 0 0;
    vertical-align: -3px;
    accent-color: var(--blue);
    cursor: pointer;
  }

  .table-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 43px;
    padding: 0 24px;
    border-top: 1px solid var(--line);
    background: #fcfdfe;
    color: var(--muted);
    font-size: 12px;
  }

  .selection-note {
    display: inline-flex;
    align-items: center;
    gap: 7px;
  }

  .selection-chip {
    width: 8px;
    height: 8px;
    border-radius: 2px;
    background: var(--blue);
  }

  .sort-note {
    color: var(--subtle);
  }
</style>
</head>
<body>
  <main class="page">
    <section class="table-card" aria-labelledby="deployments-title">
      <header class="card-header">
        <div>
          <p class="eyebrow">Infrastructure / Deployments</p>
          <h1 id="deployments-title">Server deployment records</h1>
        </div>
        <div class="header-meta">
          <span class="count">8 records</span>
          <span class="sync"><span class="sync-dot" aria-hidden="true"></span>Updated 2 min ago</span>
        </div>
      </header>

      <div class="table-wrap">
        <table aria-describedby="table-summary">
          <colgroup>
            <col class="status">
            <col class="service">
            <col class="region">
            <col class="deployed">
            <col class="duration">
            <col class="timestamp">
          </colgroup>
          <thead>
            <tr>
              <th scope="col"><button type="button" data-key="status">Status</button></th>
              <th scope="col"><button type="button" data-key="service">Service name</button></th>
              <th scope="col"><button type="button" data-key="region">Region</button></th>
              <th scope="col"><button type="button" data-key="deployed">Deployed by</button></th>
              <th scope="col"><button type="button" data-key="duration">Duration</button></th>
              <th scope="col" aria-sort="ascending">
                <button type="button" data-key="timestamp">Timestamp <span class="sort-icon" aria-hidden="true"></span></button>
              </th>
            </tr>
          </thead>
          <tbody id="deployment-rows">
            <tr data-timestamp="2025-04-18T08:42:00Z">
              <td><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">api-gateway</span><span class="service-id">v2.18.4 · 8f31c2a</span></td>
              <td><span class="region-name">US East</span><span class="region-code">us-east-1</span></td>
              <td class="deployer">Maya Chen</td>
              <td class="duration">4m 12s</td>
              <td class="timestamp">Apr 18, 2025 · 08:42 UTC</td>
            </tr>
            <tr data-timestamp="2025-04-18T08:35:00Z">
              <td><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">billing-worker</span><span class="service-id">v4.6.1 · 1b7d9e0</span></td>
              <td><span class="region-name">EU Central</span><span class="region-code">eu-central-1</span></td>
              <td class="deployer">Jon Bell</td>
              <td class="duration">2m 48s</td>
              <td class="timestamp">Apr 18, 2025 · 08:35 UTC</td>
            </tr>
            <tr class="is-selected" data-timestamp="2025-04-18T08:21:00Z" aria-selected="true">
              <td><input class="selected-box" type="checkbox" checked aria-label="Select search-indexer"><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">search-indexer</span><span class="service-id">v7.3.0 · 6c20a4f</span></td>
              <td><span class="region-name">US West</span><span class="region-code">us-west-2</span></td>
              <td class="deployer">Ari Patel</td>
              <td class="duration">6m 03s</td>
              <td class="timestamp">Apr 18, 2025 · 08:21 UTC</td>
            </tr>
            <tr data-timestamp="2025-04-18T08:04:00Z">
              <td><span class="status-cell status-pending"><span class="status-mark" aria-hidden="true"></span>Pending</span></td>
              <td><span class="service-name">notifications</span><span class="service-id">v3.11.2 · d9a6e12</span></td>
              <td><span class="region-name">Asia Pacific</span><span class="region-code">ap-southeast-1</span></td>
              <td class="deployer">Lena Ortiz</td>
              <td class="duration">—</td>
              <td class="timestamp">Apr 18, 2025 · 08:04 UTC</td>
            </tr>
            <tr data-timestamp="2025-04-18T07:51:00Z">
              <td><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">media-transcoder</span><span class="service-id">v1.9.8 · e31f0b6</span></td>
              <td><span class="region-name">EU West</span><span class="region-code">eu-west-1</span></td>
              <td class="deployer">Maya Chen</td>
              <td class="duration">3m 37s</td>
              <td class="timestamp">Apr 18, 2025 · 07:51 UTC</td>
            </tr>
            <tr class="is-hovered" data-timestamp="2025-04-18T07:38:00Z" aria-label="Hovered row preview">
              <td><span class="status-cell status-failed"><span class="status-mark" aria-hidden="true"></span>Failed</span></td>
              <td><span class="service-name">checkout-api</span><span class="service-id">v5.0.3 · 4a81c7d</span></td>
              <td><span class="region-name">US East</span><span class="region-code">us-east-1</span></td>
              <td class="deployer">Sam Rivera</td>
              <td class="duration">1m 19s</td>
              <td class="timestamp">Apr 18, 2025 · 07:38 UTC</td>
            </tr>
            <tr data-timestamp="2025-04-18T07:16:00Z">
              <td><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">fraud-detector</span><span class="service-id">v2.4.7 · 0fc2d81</span></td>
              <td><span class="region-name">US Central</span><span class="region-code">us-central-1</span></td>
              <td class="deployer">Jon Bell</td>
              <td class="duration">5m 26s</td>
              <td class="timestamp">Apr 18, 2025 · 07:16 UTC</td>
            </tr>
            <tr data-timestamp="2025-04-18T06:59:00Z">
              <td><span class="status-cell status-running"><span class="status-mark" aria-hidden="true"></span>Running</span></td>
              <td><span class="service-name">config-service</span><span class="service-id">v6.2.5 · a5e901b</span></td>
              <td><span class="region-name">EU Central</span><span class="region-code">eu-central-1</span></td>
              <td class="deployer">Ari Patel</td>
              <td class="duration">2m 06s</td>
              <td class="timestamp">Apr 18, 2025 · 06:59 UTC</td>
            </tr>
          </tbody>
        </table>
      </div>

      <footer class="table-footer" id="table-summary">
        <span class="selection-note"><span class="selection-chip" aria-hidden="true"></span>1 deployment selected</span>
        <span class="sort-note">Timestamp · newest first</span>
      </footer>
    </section>
  </main>

<script>
  const table = document.querySelector("table");
  const tbody = document.getElementById("deployment-rows");
  const headers = [...table.querySelectorAll("th")];

  headers.forEach((header, index) => {
    const button = header.querySelector("button");
    button.addEventListener("click", () => {
      const key = button.dataset.key;
      const current = header.getAttribute("aria-sort");
      const direction = current === "ascending" ? "descending" : "ascending";

      headers.forEach(h => h.removeAttribute("aria-sort"));
      header.setAttribute("aria-sort", direction);

      const rows = [...tbody.querySelectorAll("tr")];
      rows.sort((a, b) => {
        let av, bv;
        if (key === "timestamp") {
          av = new Date(a.dataset.timestamp).getTime();
          bv = new Date(b.dataset.timestamp).getTime();
        } else {
          av = a.cells[index].innerText.trim().toLowerCase();
          bv = b.cells[index].innerText.trim().toLowerCase();
        }
        if (av < bv) return direction === "ascending" ? -1 : 1;
        if (av > bv) return direction === "ascending" ? 1 : -1;
        return 0;
      });
      rows.forEach(row => tbody.appendChild(row));
    });
  });

  tbody.addEventListener("change", event => {
    if (!event.target.classList.contains("selected-box")) return;
    const row = event.target.closest("tr");
    row.classList.toggle("is-selected", event.target.checked);
    row.setAttribute("aria-selected", event.target.checked ? "true" : "false");
  });
</script>
</body>
</html>

request envelope — prompt redacted

{
  "model": "openai/gpt-5.6-luna",
  "system": "Respond with a single complete, self-contained HTML document rendering the component and nothing else. No markdown fences, no explanation. Inline all CSS and JavaScript, reference no external resource. Render the component at its natural size on a neutral background, with enough surrounding space that it can be photographed.",
  "messages": [
    {
      "role": "user",
      "content": "«REDACTED — 87 words, 559 chars»"
    }
  ],
  "maxOutputTokens": 32000,
  "gatewayStringId": "cb3bc47b-271f-4c9b-a222-cd70fff1d39b"
}