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" />
  <title>Server Deployment Records</title>
  <style>
    :root{
      --bg: #f4f6f8;
      --panel: #ffffff;
      --text: #0f172a;
      --muted:#5b677a;
      --border:#d7dee7;
      --border2:#e6edf5;
      --shadow: 0 18px 50px rgba(16,24,40,.08);
      --rowHover:#f6f8fb;
      --rowSelected:#eaf2ff;

      --focus:#2563eb;
      --goodBg:#ecfdf3;
      --warnBg:#fffbeb;
      --badBg:#fff1f2;

      --goodText:#047857;
      --warnText:#92400e;
      --badText:#b91c1c;

      --chipBg: rgba(2,6,23,.04);
      --chipBorder: rgba(2,6,23,.09);
    }

    *{box-sizing:border-box}
    body{
      margin:0;
      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
      background: radial-gradient(1200px 700px at 20% 0%, #fbfdff 0%, var(--bg) 60%, #f2f4f7 100%);
      color:var(--text);
      display:flex;
      align-items:flex-start;
      justify-content:center;
      padding:40px 24px;
      min-height:100vh;
    }

    .stage{
      width: 980px;               /* natural component width for photography */
      max-width: 100%;
    }

    .card{
      background: var(--panel);
      border: 1px solid var(--border2);
      border-radius: 14px;
      box-shadow: var(--shadow);
      overflow:hidden;
    }

    .headerbar{
      display:flex;
      align-items:center;
      justify-content:space-between;
      padding:16px 16px 12px 16px;
      border-bottom:1px solid var(--border2);
      gap:16px;
    }

    .title{
      display:flex; flex-direction:column; gap:4px;
      min-width: 320px;
    }
    .title .h{
      font-weight: 690;
      letter-spacing: -0.02em;
      font-size: 14px;
      color:#0b1220;
    }
    .title .sub{
      font-size: 12px;
      color: var(--muted);
    }

    .controls{
      display:flex;
      align-items:center;
      gap:10px;
      color:var(--muted);
      font-size:12px;
      white-space:nowrap;
    }
    .pill{
      display:inline-flex; align-items:center; gap:8px;
      padding:8px 10px;
      background: rgba(2,6,23,.03);
      border:1px solid rgba(2,6,23,.06);
      border-radius: 999px;
    }
    .dot{
      width:9px; height:9px; border-radius: 50%;
      background: #2563eb;
      box-shadow: 0 0 0 3px rgba(37,99,235,.12);
    }

    .tableWrap{
      padding:0;
    }

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

    thead th{
      position:relative;
      text-align:left;
      font-size: 12px;
      color:#334155;
      font-weight: 650;
      padding:12px 12px;
      border-bottom:1px solid var(--border);
      background: linear-gradient(#fbfcfe, #f7f9fc);
      user-select:none;
    }
    thead th:first-child{padding-left:14px}
    thead th:last-child{padding-right:14px}

    .thBtn{
      display:flex;
      align-items:center;
      gap:8px;
      width:100%;
      cursor:pointer;
    }
    .sortState{
      margin-left:auto;
      width:14px;height:14px;
      flex:0 0 auto;
      opacity: .95;
    }
    .sortState svg{display:block}
    .activeSort{
      color:#0f3a84;
    }
    .activeSort .chev{
      fill:#0f3a84;
    }

    tbody td{
      padding:12px 12px;
      border-bottom:1px solid var(--border2);
      font-size: 13px;
      color:#0f172a;
      vertical-align:middle;
      overflow:hidden;
      text-overflow:ellipsis;
      white-space:nowrap;
    }
    tbody tr td:first-child{padding-left:14px}
    tbody tr td:last-child{padding-right:14px}

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

    tbody tr:hover td{
      background: var(--rowHover);
    }

    tbody tr.selected td{
      background: var(--rowSelected);
      border-bottom-color: rgba(37,99,235,.20);
    }

    .statusCell{
      display:flex;
      align-items:center;
      gap:10px;
      min-width: 120px;
    }
    .badge{
      display:inline-flex;
      align-items:center;
      gap:8px;
      padding:6px 10px;
      border-radius:999px;
      border:1px solid var(--chipBorder);
      background: var(--chipBg);
      font-size: 12px;
      font-weight: 700;
      letter-spacing: .01em;
      color:#0b1220;
    }
    .badge .sig{
      width:9px; height:9px; border-radius: 50%;
      flex:0 0 auto;
      border:1px solid rgba(2,6,23,.12);
      background:#475569;
    }
    /* Status styles that remain legible without color-only meaning */
    .badge[data-status="Success"]{
      background: var(--goodBg);
      border-color: rgba(4,120,87,.25);
      color: var(--goodText);
    }
    .badge[data-status="Success"] .sig{ background: var(--goodText); border-color: rgba(4,120,87,.35); }

    .badge[data-status="In Progress"]{
      background: var(--warnBg);
      border-color: rgba(146,64,14,.25);
      color: var(--warnText);
    }
    .badge[data-status="In Progress"] .sig{ background: var(--warnText); border-color: rgba(146,64,14,.35); }

    .badge[data-status="Failed"]{
      background: var(--badBg);
      border-color: rgba(185,28,28,.25);
      color: var(--badText);
    }
    .badge[data-status="Failed"] .sig{ background: var(--badText); border-color: rgba(185,28,28,.35); }

    .badge .label{
      position:relative;
      display:inline-flex;
      align-items:center;
      gap:8px;
      max-width: 160px;
      overflow:hidden;
      text-overflow:ellipsis;
    }
    /* Add non-color cues: prefix + symbol */
    .badge[data-status="Success"] .label::before{ content:"✓"; font-weight:900; }
    .badge[data-status="In Progress"] .label::before{ content:"⏳"; font-weight:900; }
    .badge[data-status="Failed"] .label::before{ content:"✕"; font-weight:900; }

    .col-service{width: 270px}
    .col-region{width: 150px}
    .col-deployer{width: 150px}
    .col-duration{width: 130px}
    .col-timestamp{width: 205px}

    .mono{
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      font-size: 12.5px;
      color:#0b1220;
    }
    .muted{color:var(--muted)}

    .footer{
      padding:12px 16px 14px 16px;
      display:flex;
      align-items:center;
      justify-content:space-between;
      gap:16px;
      color:var(--muted);
      font-size:12px;
    }
    .kbd{
      border:1px solid rgba(2,6,23,.12);
      background: rgba(2,6,23,.03);
      padding:4px 7px;
      border-radius: 8px;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      color:#0f172a;
    }

    /* Make the hovered row state visible even without real pointer hover */
    tbody tr.demoHover td{ background: var(--rowHover); }
    /* Keep selection distinct from hover if both apply; selection wins */
    tbody tr.selected.demoHover td{ background: var(--rowSelected); }

    /* Subtle row edge highlight for selected */
    tbody tr.selected{
      box-shadow: inset 0 0 0 1px rgba(37,99,235,.18);
      position:relative;
      z-index:1;
    }
  </style>
</head>
<body>
  <div class="stage">
    <div class="card" role="region" aria-label="Server deployment records">
      <div class="headerbar">
        <div class="title">
          <div class="h">Deployment Audit Log</div>
          <div class="sub">Eight recent server deployments across services and regions.</div>
        </div>
        <div class="controls" aria-label="Status summary">
          <span class="pill" title="Last refresh time">
            <span class="dot" aria-hidden="true"></span>
            Updated just now
          </span>
        </div>
      </div>

      <div class="tableWrap">
        <table aria-label="Data table: server deployment records">
          <colgroup>
            <col style="width:160px">
            <col class="col-service">
            <col class="col-region">
            <col class="col-deployer">
            <col class="col-duration">
            <col class="col-timestamp">
          </colgroup>
          <thead>
            <tr>
              <th scope="col">
                <div class="thBtn" aria-label="Sort by status">
                  <span>Status</span>
                </div>
              </th>
              <th scope="col" class="">
                <div class="thBtn" aria-label="Sort by service name">
                  <span>Service name</span>
                </div>
              </th>
              <th scope="col">
                <div class="thBtn" aria-label="Sort by region">
                  <span>Region</span>
                </div>
              </th>
              <th scope="col">
                <div class="thBtn" aria-label="Sort by deployed by">
                  <span>Deployed by</span>
                </div>
              </th>
              <th scope="col">
                <div class="thBtn activeSort" aria-label="Sort by duration (descending)">
                  <span>Duration</span>
                  <span class="sortState" aria-hidden="true">
                    <!-- descending caret -->
                    <svg width="14" height="14" viewBox="0 0 14 14" role="img" aria-hidden="true">
                      <path class="chev" d="M7 3.2c.25 0 .48.11.62.3l3 4.05c.29.39.2.95-.19 1.24-.15.11-.34.17-.53.17H3.1c-.5 0-.9-.41-.9-.92 0-.2.06-.38.17-.53l3-4.05c.14-.19.37-.3.62-.3Z" fill="#0f3a84"/>
                      <path class="chev" d="M7 10.3c-.25 0-.48-.11-.62-.3l-3-4.05c-.29-.39-.2-.95.19-1.24.15-.11.34-.17.53-.17h6.2c.5 0 .9.41.9.92 0 .2-.06.38-.17.53l-3 4.05c-.14.19-.37.3-.62.3Z" fill="#0f3a84" opacity=".9" transform="translate(0,0) scale(0.0001)"/>
                    </svg>
                  </span>
                  <span style="position:absolute;left:-9999px;">Sorted descending</span>
                </div>
              </th>
              <th scope="col">
                <div class="thBtn" aria-label="Sort by timestamp">
                  <span>Timestamp</span>
                </div>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Success" title="Deployment completed successfully">
                    <span class="label">Success</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="api-gateway">api-gateway</td>
              <td class="muted" title="us-east-1">us-east-1</td>
              <td title="Maya Chen"><span class="mono">maya.chen</span></td>
              <td class="mono" title="Duration">08m 42s</td>
              <td title="Timestamp">2026-07-26 09:12:31</td>
            </tr>

            <tr class="demoHover">
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="In Progress" title="Deployment still running">
                    <span class="label">In Progress</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="billing-worker">billing-worker</td>
              <td class="muted" title="eu-west-1">eu-west-1</td>
              <td title="Jordan Patel"><span class="mono">j.patel</span></td>
              <td class="mono" title="Duration">03m 10s</td>
              <td title="Timestamp">2026-07-26 09:15:04</td>
            </tr>

            <tr class="selected">
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Failed" title="Deployment failed; rollback may be available">
                    <span class="label">Failed</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="orders-service">orders-service</td>
              <td class="muted" title="ap-southeast-2">ap-southeast-2</td>
              <td title="Avery Nguyen"><span class="mono">avery.ng</span></td>
              <td class="mono" title="Duration">12m 05s</td>
              <td title="Timestamp">2026-07-26 09:16:42</td>
            </tr>

            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Success" title="Deployment completed successfully">
                    <span class="label">Success</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="auth-service">auth-service</td>
              <td class="muted" title="us-west-2">us-west-2</td>
              <td title="Sam Rivera"><span class="mono">s.rivera</span></td>
              <td class="mono" title="Duration">06m 28s</td>
              <td title="Timestamp">2026-07-26 09:13:57</td>
            </tr>

            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Success" title="Deployment completed successfully">
                    <span class="label">Success</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="search-indexer">search-indexer</td>
              <td class="muted" title="eu-central-1">eu-central-1</td>
              <td title="Kim Thompson"><span class="mono">kim.t</span></td>
              <td class="mono" title="Duration">09m 11s</td>
              <td title="Timestamp">2026-07-26 09:10:19</td>
            </tr>

            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="In Progress" title="Deployment still running">
                    <span class="label">In Progress</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="notifications-api">notifications-api</td>
              <td class="muted" title="sa-east-1">sa-east-1</td>
              <td title="Noah Brooks"><span class="mono">n.brooks</span></td>
              <td class="mono" title="Duration">02m 49s</td>
              <td title="Timestamp">2026-07-26 09:17:25</td>
            </tr>

            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Failed" title="Deployment failed; check logs">
                    <span class="label">Failed</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="recommendation-engine">recommendation-engine</td>
              <td class="muted" title="us-east-2">us-east-2</td>
              <td title="Priya Iyer"><span class="mono">priya.iyer</span></td>
              <td class="mono" title="Duration">10m 37s</td>
              <td title="Timestamp">2026-07-26 09:09:46</td>
            </tr>

            <tr>
              <td>
                <div class="statusCell">
                  <span class="badge" data-status="Success" title="Deployment completed successfully">
                    <span class="label">Success</span><span class="sig" aria-hidden="true"></span>
                  </span>
                </div>
              </td>
              <td title="image-processor">image-processor</td>
              <td class="muted" title="ca-central-1">ca-central-1</td>
              <td title="Ethan Foster"><span class="mono">ethan.f</span></td>
              <td class="mono" title="Duration">07m 54s</td>
              <td title="Timestamp">2026-07-26 09:14:38</td>
            </tr>
          </tbody>
        </table>
      </div>

      <div class="footer" aria-label="Table footer">
        <div>
          Showing <span class="kbd">8</span> records • Click headers to sort
        </div>
        <div class="muted">
          Hover: <span class="kbd">billing-worker</span> • Selected: <span class="kbd">orders-service</span>
        </div>
      </div>
    </div>
  </div>

  <script>
    // Optional: keep UI states as requested (demo hover + one selected).
    // Also make the sort header feel real (no external dependencies).
    (function(){
      const table = document.querySelector('table');
      if(!table) return;
      const ths = Array.from(table.querySelectorAll('thead th'));
      const tbody = table.querySelector('tbody');

      // Ensure only the demo hover row is visually hovered; prevent runtime hover from breaking the demo state.
      const hoverRow = tbody.querySelector('tr.demoHover');
      const selectedRow = tbody.querySelector('tr.selected');

      // Provide an interactive sort on header click for professionalism,
      // while leaving the visual "sorted descending" state on Duration.
      function sortBy(colIndex, direction){
        const rows = Array.from(tbody.querySelectorAll('tr'));
        const getCellText = (tr) => {
          const cell = tr.children[colIndex];
          return cell ? cell.textContent.trim() : '';
        };

        const typeHint = colIndex; // 4 duration, 5 timestamp
        function parseDuration(s){
          // "MMm SSs"
          const m = s.match(/(\d+)\s*m\s*(\d+)\s*s/i);
          if(!m) return 0;
          return (parseInt(m[1],10)*60 + parseInt(m[2],10)) * 1000;
        }
        function parseTimestamp(s){
          // "YYYY-MM-DD HH:MM:SS"
          // Convert to Date safely (assume local timezone)
          const cleaned = s.replace(' ', 'T');
          const d = new Date(cleaned);
          return d.getTime();
        }

        rows.sort((a,b)=>{
          const sa = getCellText(a);
          const sb = getCellText(b);

          let va, vb;
          if(typeHint === 4){
            va = parseDuration(sa);
            vb = parseDuration(sb);
          } else if(typeHint === 5){
            va = parseTimestamp(sa);
            vb = parseTimestamp(sb);
          } else {
            // status/service/region/deployer: case-insensitive
            va = sa.toLowerCase();
            vb = sb.toLowerCase();
          }

          if(va < vb) return direction === 'asc' ? -1 : 1;
          if(va > vb) return direction === 'asc' ? 1 : -1;
          return 0;
        });

        rows.forEach(r => tbody.appendChild(r));
      }

      // Initialize Duration header visual state already handled in CSS/markup.
      // Make clicks update ordering but keep demo hovered/selected rows intact if possible.
      const durationHeaderIndex = 4; // col index in table children
      let lastDir = 'desc';

      // Map headers to column indices
      // Columns: status(0), service(1), region(2), deployed by(3), duration(4), timestamp(5)
      const headerToCol = [0,1,2,3,4,5];

      ths.forEach((th, i)=>{
        th.style.cursor = 'pointer';
        th.addEventListener('click', ()=>{
          const col = headerToCol[i];
          let dir = 'asc';
          if(th.querySelector('.activeSort')) dir = (lastDir === 'asc') ? 'desc' : 'asc';

          // Visual toggle on active sort (minimal changes)
          // Remove activeSort class from all
          ths.forEach(x=>{
            const btn = x.querySelector('.thBtn');
            if(btn) btn.classList.remove('activeSort');
          });
          const clickedBtn = th.querySelector('.thBtn');
          if(clickedBtn) clickedBtn.classList.add('activeSort');

          // Sort actual data
          if(col === 4){
            // Duration
            lastDir = dir;
          } else {
            lastDir = 'asc';
          }
          // Use direction
          sortBy(col, dir);

          // Re-apply demo selected + hover after sorting, by matching service name
          const selectedName = selectedRow ? selectedRow.querySelector('td:nth-child(2)')?.textContent.trim() : null;
          const hoverName = hoverRow ? hoverRow.querySelector('td:nth-child(2)')?.textContent.trim() : null;

          const newSelected = selectedName ? Array.from(tbody.querySelectorAll('tr')).find(tr=>{
            const td = tr.querySelector('td:nth-child(2)');
            return td && td.textContent.trim() === selectedName;
          }) : null;
          const newHover = hoverName ? Array.from(tbody.querySelectorAll('tr')).find(tr=>{
            const td = tr.querySelector('td:nth-child(2)');
            return td && td.textContent.trim() === hoverName;
          }) : null;

          tbody.querySelectorAll('tr.selected').forEach(tr=>tr.classList.remove('selected'));
          tbody.querySelectorAll('tr.demoHover').forEach(tr=>tr.classList.remove('demoHover'));
          if(newHover) newHover.classList.add('demoHover');
          if(newSelected) newSelected.classList.add('selected');
        });
      });

      // Keep the specified states even if user moves mouse quickly.
      // If the user hovers over another row, revert to demo visuals shortly.
      let lock = false;
      document.addEventListener('mouseover', (e)=>{
        if(lock) return;
        const tr = e.target && e.target.closest ? e.target.closest('tr') : null;
        if(!tr) return;
        lock = true;
        setTimeout(()=>{
          lock = false;
          // Ensure demo hover and selected remain
          tbody.querySelectorAll('tr').forEach(r=>{
            r.classList.remove('demoHover');
          });
          if(hoverRow){
            const name = hoverRow.querySelector('td:nth-child(2)')?.textContent.trim();
            if(name){
              const rowNow = Array.from(tbody.querySelectorAll('tr')).find(r=>{
                const td = r.querySelector('td:nth-child(2)'); 
                return td && td.textContent.trim() === name;
              });
              if(rowNow) rowNow.classList.add('demoHover');
            }
          }
          // Ensure selected remains
          tbody.querySelectorAll('tr').forEach(r=>r.classList.remove('selected'));
          if(selectedRow){
            const name = selectedRow.querySelector('td:nth-child(2)')?.textContent.trim();
            if(name){
              const rowNow = Array.from(tbody.querySelectorAll('tr')).find(r=>{
                const td = r.querySelector('td:nth-child(2)'); 
                return td && td.textContent.trim() === name;
              });
              if(rowNow) rowNow.classList.add('selected');
            }
          }
        }, 20);
      }, {passive:true});
    })();
  </script>
</body>
</html>

request envelope — prompt redacted

{
  "model": "openai/gpt-5.4-nano",
  "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": "68b9ca2e-24f6-4b26-96f8-029a2a0acdf1"
}