/* ─────────────────────────────────────────────────────────
   alerts — the Runwise alerts tab, rebuilt from the Figma
   file "Random" (jsNih3Q5lmJ2LXjVRBrxsd), page "Alerts tab".

   Three frames are reproduced here at 1:1:

     .al-table    node 103:19692, "Table", 1120×736 — the
                  landed three-tier alert table. It is the
                  page hero, placed at 30,124 inside the
                  chrome in runwise-app.css, and it is the
                  one frame here that WORKS: the tabs and
                  the search filter it, the four column
                  headers sort it, and a row can be picked.
                  1120×736 is its arrival state, not its
                  only one — alerts.js writes the height and
                  the column rules on every render, because
                  a filtered table is shorter. See the note
                  above .al-body and .al-row for how the
                  moving parts are kept off the fixed ones.
     .al-sheet    node 103:38757, 440×880 — the landed alert
                  detail side sheet.
     .al-lockup   nodes 103:38824, 103:38272, 103:38490 and
                  103:38757 — four answers to the same
                  question about where the status goes.

   Every number in this file was read out of the file through
   get_metadata and get_design_context, never sampled off a
   screenshot. Token names are Runwise's own so a value here
   can be checked against the file: Grey/900 is #191918,
   Red/600 is #FF6262, Purple/600 is #6B41E3.

   Two things in here are deliberately NOT 1:1, and both are
   licensing, not laziness:

     The Font Awesome 6 PRO glyphs are drawn in alerts.js.
     Runwise holds that licence and this domain does not.
     Same icon, same box, same colour, different hand — the
     seven that were already verified for /building-health
     are reused unchanged, and the rest are drawn to the same
     512-unit body.

     The four boiler photographs in the troubleshooting steps
     are iStock frames licensed to Runwise. They are replaced
     with a plain 240×240 plate carrying a caption. The
     layout around them is exact; the picture is not there.

   Two inconsistencies are reproduced rather than corrected,
   because they are in the file: step 1 of the troubleshooting
   list uses a 24px light number chip against a 12px gap, and
   steps 2–4 use a 20px dark one against an 8px gap. Two of
   the icon squares also disagree about their hairline
   (#ce1717 on the component instance, #e83232 on a detached
   copy); the component value is used for both, which is a
   half-pixel of difference at full size.

   ── How this was checked ──────────────────────────────────
   Both frames were rendered at native size and diffed
   against Figma's own PNG export of the same node.

   The export carries the node's drop shadow as bleed, and it
   is not symmetric: the table's `0 1px 3px` + `0 1px 2px`
   puts 3px on each side, 2px on top and 4px on the bottom,
   so the 1120×736 frame exports at 1126×742 and the crop is
   (3, 2). Cropping at (3, 3) instead reads as a uniform 1px
   vertical offset across the whole table and makes every row
   look wrong — that is the export, not the build.

   Flatten the export before comparing. It is RGBA and a
   headless screenshot is RGB, and reading the two buffers at
   the same stride turns a 0.5% diff into a 10% one that
   looks like a real regression and is not.

   At the right offset, and after blurring both to cancel
   glyph antialiasing:

     table   0.47% of pixels differ
     sheet   0.6%, excluding the licensed photo plate

   Four things were wrong when the table first measured 0.7%,
   and re-running that diff against a fresh export found them:

     The column headers carry `sort`, not
     `arrow-up-arrow-down` (node 103:19708), and Troubleshoot
     carries `tools` — screwdriver-wrench — not a plain
     wrench (node 103:19713). Both were drawn from the wrong
     glyph, which is a different silhouette, not a rounding
     error.

     The icon square's drop shadow belongs to the GLYPH
     inside it, not to the square (node 103:16553). Hung on
     the square it put a halo under all four.

     Button / Small is 120 wide in the file and the tab group
     319. Sized to their own labels they come out 118.8 and
     316.5, because the licensed Latin-1 subset of Codec Warm
     sets tighter than the full family Figma has. The tab
     group's 2.5px landed on the search field, which takes
     the rest of the toolbar and so missed the column rule at
     359 that it sits on. Both are pinned to the file's
     numbers now, which moves the error inside a pill track
     where nothing aligns to it.

   What is left is glyph rasterisation: every hot row in the
   diff is a line of text. Nothing is displaced. Every
   measured element lands on the coordinate get_metadata
   reports, and the three pinned widths land exactly.

   Namespace: .al-* only. Nothing here reaches outside a
   figure stage.

   No inline styles, per _headers: geometry rides on SVG
   attributes and on element.style, which is a CSSOM write
   and not what style-src blocks.
   ───────────────────────────────────────────────────────── */

/* ── Codec Warm ───────────────────────────────────────────
   Runwise's face. The two subset faces already ship for
   /savings-card and /building-health; this page reuses them
   rather than adding a third copy, and the licence note in
   savings-card.css applies here unchanged.

   font-display: block, not swap. Codec Warm Bold is
   usWeightClass 500, so asking an installed copy of the
   family for 700 gets HEAVY; declaring the file at 700 pins
   it to the weight the Figma text styles name. */
@font-face {
  font-family: "Codec Warm";
  src: url("/figures/assets/codec-warm-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "Codec Warm";
  src: url("/figures/assets/codec-warm-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

/* ── Fit ──────────────────────────────────────────────────
   Draw at the native Figma width and scale the whole thing,
   so every proportion survives. alerts.js sets --al-k from
   the stage width and the frame's own width. */
.al-fit {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.al-fit > .al {
  position: absolute;
  top: 0;
  left: 0;
  transform: scale(var(--al-k, 1));
  transform-origin: top left;
}

/* ── The product's tokens ─────────────────────────────── */
.al {
  --al-ink: #191918; /* Grey/900 */
  --al-ink-2: #72716f; /* Grey/600 */
  --al-mute: #8e8d8c; /* Grey/500 */
  --al-ghost: #e3e3e2; /* Grey/200 */
  --al-hair: #f0f0f0; /* Grey/100 */
  --al-surface: #fafafa; /* Grey/050 */

  --al-red: #ff6262; /* Red/600 */
  --al-red-lt: #ff8181; /* top of the solid red square */
  --al-red-deep: #e83232; /* Red/700 */
  --al-red-edge: #ce1717;
  --al-red-100: #ffefef; /* Red/100 */
  --al-red-200: #ffe0e0;
  --al-red-300: #ffc0c0;

  --al-purple: #6b41e3; /* Purple/600 */
  --al-purple-100: #f0ecfc;
  --al-purple-200: #c4b3f4;
  --al-purple-300: #cec1f5;

  --al-grad-a: #5179f4; /* primary button, top */
  --al-grad-b: #7424ff; /* primary button, bottom */

  /* White Button/Default — three inner shadows, in the order
     the file stacks them. */
  --al-btn-shadow:
    inset 0 2px 0 0 #ffffff, inset 0 0 0 1px #f0f0f0,
    inset 0 -1px 1px 0 rgba(0, 0, 0, 0.05);

  position: relative;
  box-sizing: border-box;
  color: var(--al-ink);
  font-family: "Codec Warm", Inter, "Helvetica Neue", Arial, sans-serif;
  font-variant-ligatures: none;
  -webkit-font-smoothing: antialiased;
}
.al *,
.al *::before,
.al *::after {
  box-sizing: border-box;
}
.al p {
  margin: 0;
}

/* ── Type styles, by their Figma names ────────────────── */
.al-t-xl {
  /* Text-XL/500 */
  font-size: 20px;
  line-height: 28px;
  font-weight: 700;
}
.al-t-base {
  /* Text-Base/400 */
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
}
.al-t-base-b {
  /* Text-Base/500 */
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
.al-t-s {
  /* Text-S/500 */
  font-size: 14px;
  line-height: 20px;
  font-weight: 700;
  letter-spacing: -0.25px;
}
.al-t-s-r {
  font-size: 14px;
  line-height: 20px;
  font-weight: 400;
}
.al-t-xs {
  font-size: 12px;
  line-height: 16px;
  font-weight: 400;
}
.al-t-up {
  /* Text-XS/500 Uppercase */
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* .al-abs has to out-specify the components it is applied to
   — .al-sq and .al-chip both set position: relative further
   down, and at equal specificity the later rule wins. Two
   classes beat one. */
.al .al-abs {
  position: absolute;
}

/* ── Icon square, 36×36 ───────────────────────────────────
   One component, four fills. The glyph sits on a 16px body
   with the file's own drop shadow. */
.al-sq {
  position: relative;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex: none;
}
.al-sq::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow:
    inset 0 0 0 0.5px var(--al-sq-edge),
    inset 0 2px 2px 0 rgba(255, 255, 255, 0.25);
}
/* The drop shadow belongs to the GLYPH, not to the square.
   In the file it is on the v6-icon inside Icon square (node
   103:16553), which is one component with a colour prop, so
   every variant carries it. Hung on the square instead it
   put a soft grey halo under all four squares that the
   export does not have. */
.al-sq svg {
  position: relative;
  display: block;
  filter: drop-shadow(0 1px 1.5px rgba(27, 37, 80, 0.14));
}
.al-sq--critical {
  --al-sq-edge: var(--al-red-edge);
  background: linear-gradient(180deg, var(--al-red-lt), var(--al-red));
}
.al-sq--warning {
  --al-sq-edge: var(--al-red-300);
  background: linear-gradient(180deg, var(--al-red-100), var(--al-red-200));
}
.al-sq--snoozed {
  --al-sq-edge: var(--al-purple-200);
  background: linear-gradient(
    180deg,
    var(--al-purple-100),
    var(--al-purple-300)
  );
}
.al-sq--monitoring {
  --al-sq-edge: var(--al-ghost);
  background: linear-gradient(180deg, var(--al-surface), var(--al-hair));
}

/* ── Status chip ──────────────────────────────────────────
   radius 6, 8/2 padding. The four states are the whole
   argument of the case study, so they get four blocks. */
.al-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 6px;
  white-space: nowrap;
}
.al-chip::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 0.5px var(--al-chip-edge);
}
.al-chip--critical {
  --al-chip-edge: var(--al-red-deep);
  background: var(--al-red);
  color: #fff;
}
.al-chip--warning {
  --al-chip-edge: rgba(206, 23, 23, 0.25);
  background: var(--al-red-100);
  color: var(--al-red);
}
.al-chip--snoozed {
  --al-chip-edge: var(--al-purple-200);
  background: var(--al-purple-100);
  color: var(--al-purple);
}
.al-chip--monitoring {
  --al-chip-edge: var(--al-ghost);
  background: var(--al-surface);
  color: var(--al-mute);
}

/* Header chips in the side sheet: radius 8, 6/2, no hairline. */
.al-hchip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 2px 6px;
  border-radius: 8px;
  white-space: nowrap;
}
.al-hchip--active {
  background: var(--al-red-100);
  color: var(--al-red);
}
.al-hchip--meta {
  background: var(--al-hair);
  color: var(--al-mute);
}

/* ── Buttons ────────────────────────────────────────────── */
.al-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 32px;
  padding: 6px 8px;
  border-radius: 10px;
  background: var(--al-surface);
  color: var(--al-ink);
  overflow: hidden;
  white-space: nowrap;
}
.al-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: var(--al-btn-shadow);
}
.al-btn--icon {
  width: 32px;
  padding: 6px 8px;
}
.al-btn--round {
  border-radius: 100px;
}
/* Only the step buttons carry the asymmetric padding —
   "This worked" and "Contact support" are pl-8 pr-12. The
   Troubleshoot button in the table is px-8 like every other
   small button. */
.al-btn--step {
  padding-left: 8px;
  padding-right: 12px;
}
.al-btn--primary {
  background: linear-gradient(180deg, var(--al-grad-a), var(--al-grad-b));
  color: #fff;
}
/* Color Button/Default. The file names the ring opaque black
   and does not render it that way: the top edge of the
   Troubleshoot button exports as rgb(49, 90, 242), which is
   the gradient made more saturated, not the gradient made
   dark — the blue channel barely moves (244 → 242) while the
   red nearly halves. No black-at-alpha reproduces that, and
   swept against the export the error is flat from 0 to 0.3
   and 3.4x worse at 1. So it is set to what the frame shows
   rather than to what the effect is called, at the same
   value building-health-app.css arrived at independently for
   this component. */
/* Button.module.scss animates a black overlay in over 180ms
   on the gradient button, and swaps the white/#f5f5f5
   surface's inset shadow on the light one. Both are behind a
   fine-pointer query: a tap should not leave a button
   looking hovered. */
@media (hover: hover) and (pointer: fine) {
  .al-btn::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: rgba(0, 0, 0, 0);
    pointer-events: none;
    transition: background 180ms cubic-bezier(0.25, 0, 0.06, 1);
  }
  .al-btn--primary:hover::before {
    background: rgba(0, 0, 0, 0.15);
  }
  .al-btn--icon:hover::after {
    box-shadow:
      inset 0 0 20px 0 rgba(0, 0, 0, 0.05),
      inset 0 -1px 0 0 rgba(0, 0, 0, 0.08),
      inset 0 0 0 1px rgba(0, 0, 0, 0.05),
      inset 0 2px 0 0 #fff;
  }
}
@media (prefers-reduced-motion: reduce) {
  .al-btn::before {
    transition: none;
  }
}
.al-table .al-actions .al-btn {
  cursor: pointer;
}
.al-table .al-actions .al-btn:focus-visible {
  outline: 2px solid var(--al-purple);
  outline-offset: 2px;
}

.al-btn--primary::after {
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.14),
    inset 0 2px 0 0 rgba(255, 255, 255, 0.15);
}

/* Bottom-bar buttons: radius 12, 16/10 padding, 44 tall. */
.al-btn-lg {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: 12px;
  background: var(--al-surface);
  color: var(--al-ink);
  overflow: hidden;
  white-space: nowrap;
}
.al-btn-lg::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: var(--al-btn-shadow);
}

/* ── The table ────────────────────────────────────────────
   1120×736, radius 20, on the two-layer card shadow the file
   carries. Every child is placed at the coordinate
   get_metadata reports, because that is what the frame is:
   a set of coordinates. */
.al-table {
  width: 1120px;
  height: 736px;
  background: #fff;
  border-radius: 20px;
  overflow: hidden;
  box-shadow:
    0 1px 3px 0 rgba(0, 0, 0, 0.1),
    0 1px 2px 0 rgba(0, 0, 0, 0.06);
}

/* Placed in the page panel at the coordinate get_metadata
   reports for node 103:19692 — 30 in, 20 under the header. */
.al-table--placed {
  position: absolute;
  top: 124px;
  left: 30px;
}

/* Everything that moves when a filter or a sort changes. The
   toolbar and the column header sit outside it because they
   never move. */
.al-body {
  position: absolute;
  inset: 0;
}

/* Toolbar: tab group at 20,20 and the search field beside it. */
.al-toolbar {
  position: absolute;
  left: 0;
  top: 0;
  width: 1120px;
  padding: 20px;
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
/* Figma measures this group at 44 tall: 32 for the item plus
   12 of padding, with the hairline drawn inside. A CSS border
   would make it 46, so the stroke rides on an inset shadow.
   Same reason the sheet's tab underline and the bottom bar's
   top rule are shadows below. */
.al-tabs {
  display: flex;
  gap: 4px;
  padding: 6px;
  border-radius: 12px;
  background: var(--al-surface);
  box-shadow: inset 0 0 0 1px var(--al-hair);
  flex: none;
  position: relative;
  /* The file measures this group at 319. Left to size to its
     own labels it comes out 316.5, because the licensed
     Latin-1 subset of Codec Warm sets tighter than the full
     family Figma has. Three tabs' worth of that error is
     small; where it showed was the SEARCH FIELD, which takes
     the rest of the toolbar and so started 2.5px early — off
     the column rule at 359 that it is supposed to sit on.
     Pinning the width the file states puts the rule back and
     leaves the 2.5px inside the track, where nothing aligns
     to it. */
  width: 319px;
}
.al-tab {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 6px 12px;
  border-radius: 8px;
  color: var(--al-mute);
  white-space: nowrap;
}
.al-tab--on {
  color: var(--al-ink);
}

/* The white pill is one element that slides, not a
   background that jumps. TabGroup.module.scss transitions
   its left and width over 300ms on a cubic-bezier, and turns
   the transition off under prefers-reduced-motion. It is
   hidden until alerts.js has measured a tab, so it never
   flashes at 0,0 on the first paint. */
.al-tab-chip {
  position: absolute;
  top: 6px;
  bottom: 6px;
  left: 0;
  width: 0;
  border-radius: 8px;
  background: #fff;
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.1))
    drop-shadow(0 1px 1px rgba(0, 0, 0, 0.06));
  opacity: 0;
  pointer-events: none;
  transition:
    left 300ms cubic-bezier(0.215, 0.61, 0.355, 1),
    width 300ms cubic-bezier(0.215, 0.61, 0.355, 1);
}
.al-tab-chip--set {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .al-tab-chip {
    transition: none;
  }
}
.al-search {
  flex: 1 0 0;
  min-width: 1px;
  height: 44px;
  border-radius: 12px;
  background: var(--al-surface);
  border: 1px solid var(--al-hair);
  display: flex;
  align-items: center;
  gap: 8px;
  padding-left: 11px;
  color: var(--al-mute);
  overflow: hidden;
}

/* Column header and the two section bands share one shape:
   32 tall, Grey/050, hairlines top and bottom. */
.al-band {
  position: absolute;
  left: 0;
  width: 1120px;
  height: 32px;
  background: var(--al-surface);
  border-top: 1px solid var(--al-hair);
  border-bottom: 1px solid var(--al-hair);
}
.al-band-label {
  position: absolute;
  top: 8px;
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--al-mute);
}
.al-band-label--section {
  left: 20px;
  gap: 6px;
}

/* Hairlines. Horizontal ones span the full width; the four
   vertical ones fall on the column boundaries. */
.al-hr {
  position: absolute;
  left: 0;
  width: 1120px;
  height: 1px;
  background: var(--al-hair);
}
/* 652 is 736 − 84 in the file. The height is written by
   alerts.js on every render, because a filtered table is
   shorter and the rules have to stop with it. */
.al-vr {
  position: absolute;
  top: 84px;
  width: 1px;
  height: 652px;
  background: var(--al-hair);
}

/* The name cell: icon square, then title over category. */
.al-name {
  position: absolute;
  left: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.al-name-t {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.al-name-t b {
  font-weight: 700;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: -0.25px;
  text-decoration: underline;
  text-underline-position: from-font;
}
.al-name-t span {
  font-size: 12px;
  line-height: 16px;
  color: var(--al-mute);
}
.al-name--live b {
  color: var(--al-ink);
}
.al-name--quiet b,
.al-name--quiet span {
  color: var(--al-mute);
}

.al-cell {
  position: absolute;
  font-size: 14px;
  line-height: 20px;
  white-space: nowrap;
}
.al-cell--live {
  color: var(--al-ink);
}
.al-cell--quiet {
  color: var(--al-mute);
}

.al-actions {
  position: absolute;
  right: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Same story as .al-tabs. Button / Small is a 120-wide
   component in the file (node 103:19713) and sizing it to
   "Troubleshoot" gives 118.84, which pushed the whole
   164-wide actions frame 1.16px right of the 936 the file
   puts it at. */
.al-table .al-actions .al-btn--primary {
  width: 120px;
}

/* ── The parts that move ──────────────────────────────────
   A row is a 1120×60 band that carries the tint; the cells
   sit inside it at the same x the file gives them, and their
   y is now relative to the row rather than to the frame.

   The hit area stops at 916 — the last column rule — so the
   two buttons in the actions column stay reachable and no
   control is nested inside another. Everything between is
   pointer-transparent so a click anywhere on the name, the
   chip or a date lands on the row.

   The tint is Grey/050 on hover and Grey/100 when picked:
   the two greys the frame already uses for its bands, so a
   picked row reads as part of the same surface rather than
   as a colour the product does not have. */
.al-row {
  position: absolute;
  left: 0;
  width: 1120px;
  height: 60px;
}
/* Only a row with a real alert behind it gets one, so the
   pointer lands only where the product's does. */
.al-row-hit {
  position: absolute;
  top: 0;
  left: 0;
  width: 916px;
  height: 60px;
  border-radius: 0;
  cursor: pointer;
}
.al-row .al-name,
.al-row .al-chip,
.al-row .al-cell {
  pointer-events: none;
}
.al-row .al-name {
  top: 12px;
}
.al-row .al-actions {
  top: 14px;
}
/* Grey/050 on hover, on every row in the body whether it
   opens or not — ScrollableTable.module.scss puts that rule
   on `tbody tr:hover` with no clickable qualifier.

   Grey/100 for the picked row is NOT the product. There, a
   click opens the detail drawer and the row underneath is
   left alone; here there is no drawer, so the highlight is
   what stands in for "this is the one you opened". It is the
   one interaction on this figure that the product does not
   have, and it is a Grey the frame already uses rather than
   a colour invented for it. */
.al-row--on {
  background: var(--al-hair);
}
@media (hover: hover) and (pointer: fine) {
  .al-row:hover {
    background: var(--al-surface);
  }
  .al-row--on:hover {
    background: var(--al-hair);
  }
}
/* The product's rows are not focusable at all — the tab stop
   is the Link in the ALERT cell — so there is no ring to
   copy. Purple/600 anyway: it is the focus colour everywhere
   else in the product, and a row you can reach by keyboard
   should not announce itself in a colour nothing else uses. */
.al-row-hit:focus-visible {
  outline: 2px solid var(--al-purple);
  outline-offset: -2px;
}

/* No match. One row's worth of height so the frame does not
   collapse to a toolbar and a rule. */
.al-none {
  position: absolute;
  left: 20px;
  color: var(--al-mute);
  line-height: 60px;
}

/* ── Controls ─────────────────────────────────────────────
   The tab and the column header are buttons now and the
   search field has a real input in it. Each one is reset to
   the shape the file draws, so the geometry is the same as
   it was when they were divs and a span. */
/* :where() and not `.al button`, which is the whole point.
   A control has to be stripped of the UA's font, colour and
   background, and every one of those is also set by a class
   on the same element — .al-t-s for the type, .al-tab for
   the grey, .al-tab--on for the white pill. `.al button` is
   one class plus a type, so it OUT-SPECIFIES all three and
   silently wins: the tab group renders at 16px regular, in
   ink, with no pill, and runs 37px wide of the file.

   :where() contributes nothing to specificity, so this rule
   still beats the UA stylesheet — author origin does that,
   not specificity — and loses to every class that follows. */
:where(.al button, .al input) {
  margin: 0;
  /* Padding belongs in this list. Left out of it once, the
     UA's default 1px 6px on a <button> stayed on the column
     headers and pushed every label and sort caret 6px right
     of the coordinate the file gives it. Any element here
     that wants padding states its own. */
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
}
/* An unchecked tab darkens Grey/500 → Grey/900 on hover and
   transitions the colour over 200ms. TabGroup.module.scss
   puts that transition behind a fine-pointer query and drops
   it under prefers-reduced-motion, so both guards are here
   too. The checked tab is excluded: it is already ink. */
.al-tab {
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .al-tab {
    transition: color 200ms ease;
  }
  .al-tab:hover:not(.al-tab--on) {
    color: var(--al-ink);
  }
}
@media (prefers-reduced-motion: reduce) {
  .al-tab {
    transition: none;
  }
}

/* Purple/600, inset by 2 — the ring Table.module.scss puts
   on .sortTarget. It was ink and sitting outside the cell. */
.al-tab:focus-visible,
.al-band-label:focus-visible {
  outline: 2px solid var(--al-purple);
  outline-offset: -2px;
}
.al-band-label {
  cursor: pointer;
}
/* Grey/200 at rest, the way the file draws an unsorted
   header; ink once the column is the one sorting. The
   descending state flips the same glyph rather than
   swapping in a second one. */
.al-band--head .al-band-label {
  color: var(--al-ghost);
}
.al-band--head .al-band-label > span {
  color: var(--al-mute);
}
/* Purple/600 on the column that is sorting — the product's
   --table-sort-active. The label beside it stays Grey/500;
   only the caret carries the state. alerts.js swaps the
   glyph for direction, so nothing here rotates. */
/* Scoped to the head band so it out-specifies the Grey/200
   resting rule two lines up, which is also two classes. */
.al-band--head .al-band-label--sorted {
  color: var(--al-purple);
}
.al-sort-slot {
  display: flex;
  align-items: center;
}
.al-search-in {
  flex: 1 0 0;
  min-width: 1px;
  height: 100%;
  padding: 0 11px 0 0;
  color: var(--al-ink);
}
.al-search-in::placeholder {
  color: var(--al-mute);
  opacity: 1;
}
.al-search-in::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.al-search-in:focus {
  outline: 0;
}
/* SearchInput.module.scss: Purple/600 border and a 4px
   purple halo on focus, eased over 300ms. This was a Grey/200
   border, which read as no focus state at all. */
.al-search {
  transition:
    border-color 300ms ease-in-out,
    box-shadow 300ms ease-in-out;
}
.al-search:focus-within {
  border-color: var(--al-purple);
  box-shadow: 0 0 0 4px rgba(106, 76, 231, 0.25);
}
@media (prefers-reduced-motion: reduce) {
  .al-search {
    transition: none;
  }
}

/* ── The side sheet ───────────────────────────────────────
   440×860, radius 30. The content frame inside it is 1700
   tall and overflows, which is what the file does too — the
   steps run past the bottom and are clipped by the sheet. */
.al-sheet {
  width: 440px;
  height: 860px;
  background: #fff;
  border-radius: 30px;
  overflow: hidden;
  box-shadow:
    0 1px 3px 0 rgba(0, 0, 0, 0.1),
    0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.al-sheet-title {
  position: absolute;
  left: 20px;
  width: 400px;
  color: var(--al-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.al-sheet-desc {
  position: absolute;
  left: 20px;
  width: 400px;
  color: var(--al-ink-2);
}
.al-sheet-tabs {
  position: absolute;
  left: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.al-sheet-tab {
  display: flex;
  align-items: center;
  gap: 4px;
  padding-bottom: 6px;
  color: var(--al-mute);
  white-space: nowrap;
}
.al-sheet-tab--on {
  color: var(--al-ink);
  box-shadow: inset 0 -2px 0 0 var(--al-ink);
}
.al-sheet-rule {
  position: absolute;
  left: 20px;
  width: 400px;
  height: 1px;
  background: var(--al-hair);
}
.al-sheet-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 440px;
  height: 84px;
  padding: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  box-shadow: inset 0 1px 0 0 var(--al-hair);
}
.al-sheet-bar .al-btn-lg {
  flex: 1 0 0;
  min-width: 1px;
}
.al-sheet-bar .al-btn-lg--icon {
  flex: none;
  width: 44px;
  height: 44px;
}
/* The sheet's own scrollbar, drawn in the file as a 198-long
   line at x=429. */
.al-sheet-scroll {
  position: absolute;
  left: 429px;
  top: 94px;
  width: 4px;
  height: 198px;
  border-radius: 2px;
  background: var(--al-ghost);
}

/* Troubleshooting steps. */
.al-steps {
  position: absolute;
  left: 20px;
  width: 400px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* The gap is not uniform in the file. Step one puts its
   content frame at x=36 behind a 24px number box (gap 12);
   steps two to four put theirs at x=28 behind a 20px box
   (gap 8). Reproduced, not reconciled. */
.al-step {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}
.al-step--first {
  gap: 12px;
}
.al-step-n {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
}
/* Step 1 in the file is a 24px light chip; 2–4 are 20px dark
   ones. Reproduced, not reconciled. */
.al-step-n--first {
  width: 24px;
  height: 24px;
  background: var(--al-surface);
  border: 0.5px solid var(--al-hair);
  color: var(--al-mute);
  font-size: 16px;
  line-height: 24px;
  font-weight: 700;
}
.al-step-n--rest {
  width: 20px;
  height: 20px;
  margin-top: 2px;
  background: var(--al-ink);
  color: #fff;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
}
.al-step-body {
  flex: 1 0 0;
  min-width: 1px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.al-step-copy {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.al-step-copy b {
  font-weight: 700;
  color: var(--al-ink);
}
.al-step-copy span {
  color: var(--al-ink-2);
}
.al-step-hr {
  height: 1px;
  background: var(--al-hair);
}
/* The photo plate. The frame holds a 240×240 iStock still
   licensed to Runwise; this is the same box, empty. */
.al-plate {
  width: 240px;
  height: 240px;
  border-radius: 8px;
  background: var(--al-surface);
  border: 1px dashed var(--al-ghost);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px;
  color: var(--al-mute);
  font-size: 12px;
  line-height: 16px;
}

/* ── The lockup study ─────────────────────────────────────
   Four crops of the same header at 440 wide. They differ
   only in where the status goes, which is the point. */
.al-lockups {
  display: grid;
  grid-template-columns: repeat(2, 440px);
  gap: 24px;
  width: 904px;
}
.al-lockup {
  position: relative;
  width: 440px;
  /* Tall enough for the deepest of the four — the boxed
     status table, which ends at 404. */
  height: 470px;
  background: #fff;
  border-radius: 30px 30px 8px 8px;
  overflow: hidden;
  box-shadow: 0 0 0 1px var(--al-hair);
}
.al-lockup-tag {
  position: absolute;
  left: 20px;
  bottom: 12px;
  color: var(--al-mute);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
}
/* The labelled-table variant, node 103:38824 and 103:38272. */
.al-meta {
  position: absolute;
  left: 20px;
  width: 400px;
}
.al-meta--boxed {
  background: var(--al-surface);
  border: 1px solid var(--al-hair);
  border-radius: 12px;
  padding: 8px 12px;
}
.al-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 36px;
}
.al-meta-row dt {
  margin: 0;
  color: var(--al-mute);
  font-size: 16px;
  line-height: 24px;
}
.al-meta-row dd {
  margin: 0;
  color: var(--al-ink);
  font-size: 16px;
  line-height: 24px;
}
.al-meta--plain .al-meta-row dd {
  margin-left: 100px;
}
.al-meta--plain .al-meta-row {
  justify-content: flex-start;
}
.al-meta--plain .al-meta-row dt {
  width: 100px;
  flex: none;
}
.al-eyebrow {
  position: absolute;
  left: 20px;
  color: var(--al-red);
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.al-eyebrow em {
  font-style: normal;
  color: var(--al-mute);
}
.al-subtitle {
  position: absolute;
  left: 20px;
  font-size: 14px;
  line-height: 20px;
  color: var(--al-mute);
}
.al-subtitle b {
  color: var(--al-red);
  font-weight: 400;
}

/* ── The status ladder ────────────────────────────────────
   A small figure, not a frame: the four chips the status
   column resolved to, beside the two columns they replaced.
   Built from the same chip component as the table. */
.al-ladder {
  /* The chips are the table's own component, so the tokens
     they read have to be in scope here too. The ladder is
     not a Figma frame, so it does not carry .al itself —
     .al sets a font and a position this figure does not
     want. */
  --al-ink: #191918;
  --al-mute: #8e8d8c;
  --al-ghost: #e3e3e2;
  --al-hair: #f0f0f0;
  --al-surface: #fafafa;
  --al-red: #ff6262;
  --al-red-deep: #e83232;
  --al-red-100: #ffefef;
  --al-purple: #6b41e3;
  --al-purple-100: #f0ecfc;
  --al-purple-200: #c4b3f4;
  font-family: "Codec Warm", Inter, "Helvetica Neue", Arial, sans-serif;

  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  width: 100%;
  border: 1px solid var(--line, #deded9);
  border-radius: 4px;
  overflow: hidden;
  background: #fff;
}
.al-ladder > section {
  padding: 20px;
}
.al-ladder > section + section {
  border-left: 1px solid var(--line, #deded9);
}
.al-ladder h4 {
  margin: 0 0 4px;
  font: inherit;
  font-weight: 700;
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink, #181817);
}
.al-ladder p.al-ladder-note {
  margin: 0 0 16px;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--muted, #6c6c67);
}
/* Chips on one line, the gloss under them. Side by side the
   gloss gets about a hundred pixels in the reading column and
   wraps to four lines, which reads worse than a second row. */
.al-ladder-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 10px 0;
}
.al-ladder-row + .al-ladder-row {
  border-top: 1px solid var(--al-hair);
}
.al-ladder-row .al-ladder-say {
  flex: 1 0 100%;
  font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--muted, #6c6c67);
}
/* The "two columns" side needs its pair to stay a pair, so
   the status chip gets a fixed width and the severity chip
   sits beside it rather than wrapping under. */
.al-ladder-row .al-chip {
  flex: none;
}
.al-ladder-row .al-chip:first-child {
  min-width: 84px;
}
/* Severity as the old table drew it: a 6px dot and a word in
   body colour, never a chip. */
.al-ladder-sev {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
  min-width: 96px;
  font-size: 14px;
  line-height: 20px;
  color: var(--al-ink);
}
.al-ladder-sev .al-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}
.al-sev--critical .al-dot {
  background: #ff4d4d;
}
.al-sev--warning .al-dot {
  background: #f2c14a;
}

@media (max-width: 40rem) {
  .al-ladder {
    grid-template-columns: minmax(0, 1fr);
  }
  .al-ladder > section + section {
    border-left: 0;
    border-top: 1px solid var(--line, #deded9);
  }
}
