/* il-design-bridge.css — drop-in for web/static/
   ---------------------------------------------------------------------------
   Carries the parts of the Claude Design page that web/static/style.css does
   not have yet, written entirely in this app's own vocabulary: tokens from
   theme.js (--brand, --text-primary, --surface-*, --radius-*), state from
   [data-shape] / [data-mood] on <html>, and the existing .card / .btn /
   .ccard / tbody tr.clickable class names.

   Load AFTER style.css:
     <link rel="stylesheet" href="/static/style.css">
     <link rel="stylesheet" href="/static/il-design-bridge.css">
     <script src="/static/il-enter.js" defer></script>

   Nothing here uses a literal colour. Every value follows the twelve moods.
   --------------------------------------------------------------------------- */

/* ===========================================================================
   1. Entrance grammar — the shape animation replays on navigation
   ===========================================================================
   style.css boots .card once, on load. This makes the same per-shape motion
   available to anything that appears later: a tab pane, a table body after a
   sort, the watchlist, a drawer. Add [data-enter] to an element and it plays
   the CURRENT shape's entrance; il-enter.js restarts it on demand.          */

[data-shape="soft"]       { --il-enter: il-soft-pop;  --il-enter-dur: .52s; }
[data-shape="fluctlight"] { --il-enter: il-flc-boot;  --il-enter-dur: .62s; }
[data-shape="frame"]      { --il-enter: il-frame-snap;--il-enter-dur: .42s; }
[data-shape="blade"]      { --il-enter: il-blade-cut; --il-enter-dur: .38s; }
[data-shape="cardinal"]   { --il-enter: il-crd-boot;  --il-enter-dur: .55s; }
[data-shape="cardinal2"]  { --il-enter: il-crd2-boot; --il-enter-dur: .58s; }
[data-shape="system"]     { --il-enter: il-sys-boot;  --il-enter-dur: .54s; }

[data-enter] {
  animation: var(--il-enter, il-soft-pop) var(--il-enter-dur, .52s)
             cubic-bezier(.2, .85, .24, 1) backwards;
}
tbody tr[data-enter] { transform-origin: center top; }

@keyframes il-soft-pop {
  0%   { opacity: 0; transform: translateY(10px) scale(.985); }
  60%  { opacity: 1; transform: translateY(0) scale(1.004); }
  100% { opacity: 1; transform: none; }
}
@keyframes il-frame-snap {
  0%   { opacity: 0; transform: scale(.972); }
  55%  { opacity: 1; transform: scale(1.006); }
  100% { opacity: 1; transform: none; }
}
@keyframes il-blade-cut {
  0%   { opacity: 0; clip-path: inset(0 100% 0 0); }
  70%  { opacity: 1; clip-path: inset(0 0 0 0); }
  100% { opacity: 1; clip-path: inset(0 0 0 0); }
}
@keyframes il-crd-boot {
  0%   { opacity: 0; transform: scaleY(.04); filter: brightness(2.2) saturate(1.3); }
  44%  { opacity: 1; transform: scaleY(1.035); }
  72%  { transform: scaleY(.99); filter: none; }
  100% { transform: scaleY(1); }
}
@keyframes il-crd2-boot {
  0%   { opacity: 0; transform: scaleX(.05) scaleY(.42); filter: brightness(2.6) saturate(.2); }
  15%  { opacity: 1; transform: scaleX(1.006) scaleY(.42); }
  33%  { transform: scaleX(1) scaleY(.42); }
  62%  { transform: scaleX(1) scaleY(1.012); filter: none; }
  100% { transform: none; }
}
@keyframes il-sys-boot {
  0%   { opacity: 0; transform: scaleY(.02) scaleX(1.06); filter: brightness(3) saturate(0); }
  14%  { opacity: 1; transform: scaleY(.02) scaleX(1.06); }
  28%  { transform: scaleY(1.04) scaleX(1); }
  46%  { filter: none; }
  70%  { transform: scaleY(.995); }
  100% { transform: none; }
}
/* Fluctlight — scan open, then one settle glitch. The preferred shape;
   it should not fall back to soft. */
/* Condensing rather than wiping open. The old version clipped in from both
   sides at 2.6x brightness -- a console drawing a window. A fluctlight
   gathers: it arrives slightly large and soft, and resolves. */
@keyframes il-flc-boot {
  0%   { opacity: 0; transform: scale(1.035); filter: blur(5px) brightness(1.45); }
  38%  { opacity: .9; filter: blur(1.4px) brightness(1.12); }
  72%  { opacity: 1; transform: scale(.999); filter: blur(0) brightness(1.02); }
  100% { opacity: 1; transform: none; filter: none; }
}

/* Fluctlight: motes settling, not a scan sweeping.
   ---------------------------------------------------------------------------
   Chris, on what each shape's soul actually is: *"the cardinal 2.0 system is
   based on the system design [but] the fluctlight is supposed to be more
   inspired by particles and the world in general."*

   This was doing the opposite. A hard diagonal sweep across the panel plus a
   rim that flickered three times is **system** grammar -- a readout booting,
   a console drawing itself -- and it is the grammar Cardinal 2.0 and System
   already own. Two of the seven shapes were saying the same thing in the same
   voice, which is exactly the sameness he is asking to break.

   A fluctlight is a lightcube: the soul rendered as a lattice of light. The
   world it belongs to is forest and sky rather than a console. So the panel
   no longer sweeps -- it **condenses**. A soft radial bloom gathers from the
   centre and settles, and motes drift up across the rim and fade. Nothing
   snaps, nothing flickers, nothing scans.

   Kept: the same `[data-flc-scan]` hook and the same two pseudo-elements, so
   `il-enter.js` and every caller are untouched. Only the grammar changed. */
[data-shape="fluctlight"] [data-flc-scan] { position: relative; }
[data-shape="fluctlight"] [data-flc-scan]::after,
[data-shape="fluctlight"] [data-flc-scan]::before {
  content: ""; position: absolute; pointer-events: none;
  border-radius: inherit; opacity: 0;
}
/* The bloom: light gathering into the panel rather than crossing it. */
[data-shape="fluctlight"] [data-flc-scan]::after {
  inset: 0; z-index: 3;
  background: radial-gradient(120% 140% at 50% 58%,
    color-mix(in oklab, var(--brand-deep, var(--brand)) 34%, transparent) 0%,
    color-mix(in oklab, var(--brand-deep, var(--brand)) 12%, transparent) 38%,
    transparent 72%);
  animation: il-flc-settle 1.15s cubic-bezier(.22, .68, .24, 1) both;
}
/* The motes: four small points of light drifting up and out. A repeating
   radial-gradient background is four particles for the price of no elements,
   which matters because this runs on every row of a long table. */
[data-shape="fluctlight"] [data-flc-scan]::before {
  inset: -2px; z-index: 2;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(circle, color-mix(in oklab, var(--brand-deep, var(--brand)) 85%, transparent) 0 1.1px, transparent 1.6px),
    radial-gradient(circle, color-mix(in oklab, var(--gold, var(--brand)) 70%, transparent) 0 1px, transparent 1.5px),
    radial-gradient(circle, color-mix(in oklab, var(--brand-deep, var(--brand)) 65%, transparent) 0 1.2px, transparent 1.7px),
    radial-gradient(circle, color-mix(in oklab, var(--gold, var(--brand)) 55%, transparent) 0 .9px, transparent 1.4px);
  background-position: 18% 88%, 44% 96%, 71% 90%, 89% 98%;
  background-size: 100% 100%;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand-deep, var(--brand)) 28%, transparent);
  animation: il-flc-motes 1.5s cubic-bezier(.25, .6, .3, 1) both;
}
@keyframes il-flc-settle {
  0%   { opacity: 0; transform: scale(1.04); }
  24%  { opacity: .85; }
  62%  { opacity: .4; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}
/* Up and out. A fluctlight drifts; it does not travel left to right on a
   rail, which is what the sweep this replaces was doing. */
@keyframes il-flc-motes {
  0%   { opacity: 0; transform: translateY(6px); }
  18%  { opacity: 1; }
  70%  { opacity: .55; }
  100% { opacity: 0; transform: translateY(-14px); }
}

/* Scoped to :root without a recorded motion choice — the shape entrance is a
   product feature with its own Still/Calm/Surge control, and an explicit
   choice beats the OS default. Unscoped, this killed every shape's entrance
   grammar outright on a reduced-motion machine, which is why switching view
   was a hard cut with no animation at all. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion-choice="user"]) [data-enter] { animation: none !important; }
  :root:not([data-motion-choice="user"]) [data-shape="fluctlight"] [data-flc-scan]::after,
  :root:not([data-motion-choice="user"]) [data-shape="fluctlight"] [data-flc-scan]::before { animation: none !important; opacity: 0 !important; }
}

/* ===========================================================================
   2. Screener table
   ===========================================================================
   One grid template shared by the header row and every body row, held in
   --il-cols so the responsive re-cut happens in a single place. Hide a
   column by adding its key to the media query, not by editing markup.       */

.il-scr { --il-cols: 32px 84px minmax(0, 1fr) 62px 74px 132px 92px 86px 78px; }
.il-scr-head,
.il-scr-row { display: grid; grid-template-columns: var(--il-cols); gap: 16px; align-items: center; }
.il-scr-head {
  padding: 12px 24px; border-bottom: 1px solid var(--border);
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
}
/* --row-pad, not --il-row-pad: theme.js sets the former from the active
   shape's rowPad (Blade 10px, Soft 15px), and nothing has ever set the latter.
   Every other row in the app tightens and loosens with the shape; the screener
   rows sat at the 14px fallback in all seven. */
.il-scr-row {
  padding: var(--row-pad, 14px) 24px; cursor: pointer;
  border-bottom: 1px solid var(--border); transition: background .16s ease;
}
.il-scr-row:hover { background: color-mix(in oklab, var(--brand) 8%, transparent); }
.il-scr-num { font-family: "IBM Plex Mono", monospace; font-size: 13px; text-align: right; color: var(--text-secondary); }
.il-scr-co { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-secondary); }

/* Sortable header: a real button, so it is keyboard reachable. */
.il-sort {
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit;
  background: none; border: 0; padding: 0; cursor: pointer;
  display: flex; align-items: center; gap: 5px; white-space: nowrap;
}
.il-sort:hover { color: var(--brand); }
.il-sort[data-align="right"] { justify-content: flex-end; }

/* Rank pill and delta take their colour from an attribute, never inline. */
[data-rank] {
  font-family: "IBM Plex Mono", monospace; font-size: 12px; font-weight: 600;
  justify-self: start; padding: 3px 9px; border-radius: var(--radius-sm, 6px);
}
[data-rank="S"] { background: color-mix(in oklab, var(--brand) 16%, transparent);    color: var(--brand-deep); }
[data-rank="A"] { background: color-mix(in oklab, var(--good) 16%, transparent);     color: var(--good); }
[data-rank="B"] { background: color-mix(in oklab, var(--text-primary) 9%, transparent); color: var(--text-secondary); }
[data-rank="C"] { background: color-mix(in oklab, var(--gold) 16%, transparent);     color: var(--gold); }
[data-rank="D"],
[data-rank="E"] { background: color-mix(in oklab, var(--critical) 14%, transparent); color: var(--critical); }
[data-rank="E"] { opacity: .82; }
[data-dir="up"]   { color: var(--good); }
[data-dir="down"] { color: var(--critical); }

/* Watchlist star. Must call stopPropagation() so it does not open the row. */
.il-star { background: none; border: 0; padding: 3px; cursor: pointer; font-size: 14px; line-height: 1; justify-self: start; }
.il-star[data-on="true"]  { color: var(--brand); }
.il-star[data-on="false"] { color: var(--text-muted); opacity: .45; }
.il-scr-row:hover .il-star[data-on="false"] { opacity: 1; }

@media (max-width: 1080px) {
  .il-scr { --il-cols: 30px 76px minmax(0, 1fr) 58px 70px 82px 76px; }
  [data-col="moat"], [data-col="rev"] { display: none; }
}
@media (max-width: 760px) {
  .il-scr { --il-cols: 28px 66px minmax(0, 1fr) 52px 64px 70px; }
  [data-col="fcf"] { display: none; }
  .il-scr-head, .il-scr-row { padding-left: 15px; padding-right: 15px; gap: 12px; }
}
@media (max-width: 560px) {
  .il-scr { --il-cols: 26px 58px minmax(0, 1fr) 48px 60px; }
  [data-col="chg"] { display: none; }
}

/* ===========================================================================
   3. Company drawer
   =========================================================================== */

.il-scrim { position: fixed; inset: 0; z-index: 70; background: color-mix(in oklab, var(--plane) 24%, #000 76%); opacity: .38; }
.il-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 71;
  width: min(440px, 94vw); overflow-y: auto;
  display: flex; flex-direction: column; gap: 22px; padding: 24px 26px 34px;
  background: var(--surface-2); backdrop-filter: blur(26px) saturate(1.2);
  border-left: 1px solid var(--border-strong);
  box-shadow: -34px 0 80px -34px color-mix(in oklab, var(--plane) 20%, #000 80%);
  animation: il-drawer-in .34s cubic-bezier(.2, .85, .24, 1) both;
}
@keyframes il-drawer-in { from { transform: translateX(28px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .il-drawer { animation: none; } }

/* ===========================================================================
   4. Rank ladder + input popover
   ===========================================================================
   The popover is a strip inside the ladder grid, not an absolutely positioned
   bubble: it cannot clip at the viewport edge on a narrow window, and it
   reflows with the grid. The caret is placed by --il-caret.                  */

/* .il-ladder / .il-rank-strip were ported from the canvas, where a rank tile
   expands a strip below the grid. This app never rendered that markup --
   rankLadder() in app.js builds .rankladder/.rankladder-tile instead, styled in
   style.css -- so these rules matched nothing, and --il-caret, which the
   comment above said placed the strip's caret, was never set by anything.
   Removed rather than left as a decoy for the next person reading this file.

   The question they were for -- "which are the A names" -- is answered by the
   screener's Rank filter instead, which acts on the rows actually on screen
   rather than linking one view's universe into another's. */

/* ===========================================================================
   5. Type helpers used across the design
   =========================================================================== */

.il-eyebrow {
  font-family: "IBM Plex Mono", monospace; font-size: 10.5px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-muted);
}
.il-display { font-family: "Instrument Serif", Georgia, serif; font-weight: 400; line-height: 1.1; color: var(--text-primary); }
.il-h1 { font-size: clamp(40px, 6.4vw, 70px); line-height: 1.03; letter-spacing: -.5px; }
.il-h2 { font-size: clamp(28px, 4.2vw, 40px); }
.il-figure { font-family: "IBM Plex Mono", monospace; font-weight: 500; color: var(--text-primary); }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 4px; }
