/*
 * ServoChain landing-app stylesheet.
 *
 * Theme strategy:
 *   - Base :root defines the LIGHT "Neutral Modern" theme (the design
 *     system OD uses for design runs). This is what the post-login
 *     dashboard, terminal, and login-all-of-them render with.
 *   - The OLD dark theme lives under `body.theme-dark` (a future opt-in
 *     for users who prefer it). Currently nothing in the templates sets
 *     this class, so the dark block is dead code; we keep it for emergency
 *     rollback by setting `<body class="theme-dark">`.
 *   - The login page used to be light, the dashboard dark. After this
 *     redesign, BOTH are light. The login-specific overrides are gone
 *     (the login no longer needs to flip any variables — they already
 *     match).
 */
:root {
  --bg: #FAFAFA;
  --bg-elev: #FFFFFF;
  --bg-elev-2: #F5F5F5;
  --border: #E5E5E5;
  --border-strong: #D4D4D4;
  --text: #111111;
  --muted: #6B6B6B;
  --muted-2: #9CA3AF;
  --accent: #2F6FEB;
  --accent-hover: #2563DB;
  --accent-active: #1D4ED8;
  --accent-soft: #EFF4FF;
  --accent-2: #22d3ee;
  --good: #17A34A;
  --good-soft: #DCFCE7;
  --bad: #DC2626;
  --bad-soft: #FEE2E2;
  --warn: #EAB308;
  --radius: 12px;
  --radius-sm: 8px;
  --shadow-card: 0 1px 2px rgba(17, 17, 17, 0.04), 0 1px 3px rgba(17, 17, 17, 0.04);
  --shadow-card-hover: 0 4px 12px rgba(17, 17, 17, 0.06), 0 2px 4px rgba(17, 17, 17, 0.04);
  --font-sans: 'Inter', -apple-system, system-ui, sans-serif;
}

body.theme-dark {
  --bg: #0b0d12;
  --bg-elev: #141823;
  --bg-elev-2: #1b2030;
  --border: #262b3a;
  --border-strong: #343a4d;
  --text: #e6e8ee;
  --muted: #9aa3b2;
  --muted-2: #6B7280;
  --accent: #7c5cff;
  --accent-hover: #8b6cff;
  --accent-active: #6b48f5;
  --accent-soft: rgba(124, 92, 255, 0.18);
  --accent-2: #22d3ee;
  --good: #22c55e;
  --good-soft: rgba(34, 197, 94, 0.10);
  --bad: #ef4444;
  --bad-soft: rgba(239, 68, 68, 0.10);
  --warn: #f59e0b;
  --radius: 14px;
  --shadow-card: 0 6px 20px rgba(0, 0, 0, 0.35);
  --shadow-card-hover: 0 8px 24px rgba(0, 0, 0, 0.45);
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 var(--font-sans);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* ---------- Brand hero strip ----------
   Full-bleed 1920×320 strip across the top of the page, with a ComfyUI-
   generated abstract image as the backdrop. A white-to-transparent overlay
   keeps the brand mark and headline readable on any image variant. The
   strip sits above the existing functional header (which still holds the
   user chip, sign-out button, and connection status). */
.brand-hero {
  position: relative;
  width: 100%;
  height: 220px;
  overflow: hidden;
  background: var(--bg-elev-2);
  border-bottom: 1px solid var(--border);
}
.brand-hero-img {
  position: absolute;
  inset: 0;
  background:
    /* Soft white wash on the left so the brand mark and text stay readable
       regardless of what the generated image happens to contain. */
    linear-gradient(90deg, rgba(250, 250, 250, 0.92) 0%, rgba(250, 250, 250, 0.55) 35%, rgba(250, 250, 250, 0) 70%),
    /* Soft white fade at the bottom edge so the strip transitions cleanly
       into the page background. */
    linear-gradient(180deg, rgba(250, 250, 250, 0) 60%, rgba(250, 250, 250, 0.5) 100%),
    url('/static/hero-bg.jpg') center/cover no-repeat;
}
.brand-hero-inner {
  position: relative;
  max-width: 1200px;
  height: 100%;
  margin: 0 auto;
  padding: 0 32px;
  display: flex;
  align-items: center;
  gap: 20px;
}
.brand-hero-mark {
  width: 80px; height: 80px; flex: 0 0 80px;
  display: grid; place-items: center;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 2px 6px rgba(17, 17, 17, 0.05);
}
.brand-hero-mark svg { width: 56px; height: 56px; }
.brand-hero-text { display: flex; flex-direction: column; gap: 4px; }
.brand-hero-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.1;
  color: var(--text);
}
.brand-hero-tagline {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--muted);
  font-weight: 400;
}

@media (max-width: 640px) {
  .brand-hero { height: 160px; }
  .brand-hero-mark { width: 56px; height: 56px; flex-basis: 56px; border-radius: 14px; }
  .brand-hero-mark svg { width: 40px; height: 40px; }
  .brand-hero-title { font-size: 24px; }
}

/* ---------- Page chrome (header / hero / footer) ----------
   The header is a soft off-white strip that visually disappears against
   the page background; only a thin bottom border and the brand mark
   carry visual weight. The h1 no longer uses a text gradient — that's a
   dark-theme trick that washes out on light backgrounds. */
/* Slim utility header. The lower title was deleted (the brand-hero
   strip above is now the sole owner of "ServoChain" + tagline), so
   this row only carries the signed-in chip, sign-out button, and
   connection status. Right-aligned via hero-inner's flex. */
.hero {
  padding: 14px 32px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.hero-inner {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  flex-wrap: wrap;
}
/* `.brand` and `.brand-mark` rules removed — the lower logo block
   was deleted; the upper brand-hero strip owns that visual now. */
h1 {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.02em;
  color: var(--text);
  line-height: 1.2;
}
/* (The `.tagline` rule that used to live here was removed when the
   duplicate "Control center for the ServoChain ecosystem" line under
   the H1 was deleted — the brand-hero strip is now the sole owner of
   that tagline, styled via .brand-hero-tagline above.) */

.hero-meta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: 12px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elev);
  font-family: var(--font-sans);
}
.dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 3px var(--good-soft);
}
.dot.dot-off { background: var(--bad); box-shadow: 0 0 0 3px var(--bad-soft); }

/* ---------- Main + section banner ---------- */
main {
  flex: 1;
  padding: 40px 32px 64px;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}
.tiles-section {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.section-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: 0;
}
.section-title h2 {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.section-title .section-meta {
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--muted-2);
  font-variant-numeric: tabular-nums;
}

/* ---------- Tiles (the main act) ----------
   The design target was "smaller modern tiles":
   - Min width 220px (was 280px) so 5-6 tiles fit on a 1440px display
   - Tighter internal padding (16px) and 12px radius
   - Subtle border + soft drop shadow; lift on hover
   - Icon in a 36px soft-cobalt tinted square (not a 40px purple/cyan gradient)
   - Title 14px semibold Inter, no letter-spacing hack
   - Status pill is tiny, monochrome, dot-only at the top-right corner
   - Footer is the start/launch button + meta, with the meta right-aligned */
.tiles-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
  /* Each tile sizes to its own content (no row-stretching), so a tile
     with only an Open-link is shorter than one with Start + meta. The
     thumb is fixed (96px), so the visual row alignment is preserved
     top-to-bottom; only the bottoms differ. */
  align-items: start;
  grid-auto-rows: max-content;
}

.tile {
  position: relative;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* No internal padding — the thumb bleeds to the edges; the body section
     has its own padding. */
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
  box-shadow: var(--shadow-card);
}
.tile:hover {
  border-color: var(--border-strong);
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
}
.tile.is-running {
  border-color: rgba(23, 163, 74, 0.30);
}

/* Top-right status dot. Sits over the thumb at its top-right corner. */
.tile::before {
  content: "";
  position: absolute;
  top: 12px; right: 12px;
  z-index: 3;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.10);
  transition: background 200ms ease, box-shadow 200ms ease;
}
.tile.is-running::before {
  background: var(--good);
  box-shadow: 0 0 0 3px rgba(23, 163, 74, 0.35);
  animation: pulse 2.4s ease-in-out infinite;
}

/* ---------- Tile thumb (image + overlaid icon/title/accent) ----------
   The thumb is the "image region". It now holds the icon, title,
   accent bar, AND the tagline/description text (moved up from the
   body so the tile height shrinks). A dark-to-transparent gradient
   along the lower portion keeps both the title and the explanatory
   text readable on any image variant. */
.tile-thumb {
  position: relative;
  width: 100%;
  /* Fixed height — independent of aspect ratio — so the tile body below
     starts at the same Y for every tile. Shorter than before (was
     132px) because the descriptive text now lives inside the thumb,
     so the image itself can be slimmer while still holding everything. */
  height: 96px;
  background:
    linear-gradient(135deg, var(--accent-soft), var(--bg-elev-2));
  overflow: hidden;
  border-bottom: 2px solid var(--tile-color, var(--accent));
}
.tile-thumb-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 400ms ease, filter 200ms ease;
}
.tile:hover .tile-thumb-img {
  transform: scale(1.04);
  filter: saturate(1.1);
}
/* The overlay is a dark gradient that ramps up earlier (because the
   tile-thumb-text now sits inside the upper portion of the thumb and
   needs a legibly dark backing). Still transparent at the very top so
   the image stays bright and clean. */
.tile-thumb-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0) 10%,
      rgba(0, 0, 0, 0.18) 45%,
      rgba(0, 0, 0, 0.55) 100%);
  pointer-events: none;
}
/* The head sits over the bottom-left of the image, on top of the overlay */
.tile-thumb-head {
  position: absolute;
  z-index: 2;
  left: 14px;
  right: 36px;  /* leave room for the status dot at top-right */
  bottom: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Tile icon: small white-tinted square on the image, so it stays visible
   regardless of the image's dominant colour. */
.tile-icon {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.92);
  display: grid; place-items: center;
  color: var(--tile-color, var(--accent));
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.10);
}
.tile-icon svg { width: 16px; height: 16px; }

.tile-titles { flex: 1; min-width: 0; }
.tile-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: #FFFFFF;
  /* Subtle text shadow so the title stays readable on any image even
     outside the gradient region. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  /* Single-line ellipsis — if a tile title is too long, truncate rather
     than wrap, so the layout stays predictable. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Per-tile accent bar (sits at the bottom of the thumb) ----------
   28px × 3px bar that anchors the bottom of the image region. It IS the
   "line" the user described: everything above this line is in the
   colourful image; everything below is on a soft moonlit gray
   background. The colour is set by app.js via --tile-color. */
.tile-accent {
  display: none;  /* legacy: now it's the border on .tile-thumb, not a child element */
}

/* ---------- Tile body (light gray region) ----------
   Now slimmer than before. The tagline + description moved up into
   .tile-thumb-text (above), so the body only has to host the action
   buttons + port meta. The background is still the soft moonlit gray
   — light enough to feel airy, just enough contrast against the white
   card to feel like a separate region. */
.tile-body {
  padding: 10px 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* The "moony" soft gray — desaturated cool gray that sits between
     the white card background and the page background. */
  background: #F4F5F7;
  /* Round only the bottom corners so it tucks under the rounded tile. */
  border-bottom-left-radius: calc(var(--radius) - 1px);
  border-bottom-right-radius: calc(var(--radius) - 1px);
}

/* ---------- Tile tagline inside the thumb ----------
   The tagline lives over the image so the tile itself stays compact.
   White text with a soft shadow keeps it readable on any image
   variant; the dark gradient overlay on .tile-thumb does the heavy
   lifting further down. The description (longer multi-sentence body)
   was removed on 2026-07-09 — see app.js + index.html for the
   matching change. */
.tile-thumb-text {
  position: absolute;
  left: 14px;
  right: 14px;
  /* Sit right above the icon-title row at the bottom of the thumb */
  bottom: 38px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Keep the tagline clear of the icon/title overlay at the bottom */
  padding-right: 28px;
}

.tile-tagline {
  margin: 0;
  color: #FFFFFF;
  font-size: 12px;
  font-weight: 600;
  font-family: var(--font-sans);
  line-height: 1.3;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  /* Clamp to one line so the thumb stays a fixed height regardless
     of tagline length. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The legacy `.badge` is now hidden — its content (the dot) is rendered
   as the ::before pseudo-element on the .tile root. The hidden attribute
   in the template is a backup for browsers that don't support ::before. */
.badge {
  display: none;
}

/* The .tile-desc rule was removed on 2026-07-09 — the per-tile
   description block is no longer rendered (see app.js for the
   matching JS change). The data still flows in tile.description
   from the Python TILES list, but the template no longer has a
   `.tile-desc` element, and the JS no longer populates one. If you
   re-introduce descriptions later, restore the rule below. */

.tile-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  /* No margin-top: auto — we want the body to hug its content (foot +
     any leftover flex space), not stretch to fill space. The tile-
     height difference within a row is absorbed by the body's natural
     padding, which is fine because the thumb is fixed. */
  flex-wrap: wrap;
  padding-top: 4px;
}
.tile-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.tile-meta {
  margin-left: auto;
  color: var(--muted-2);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  font-family: var(--font-sans);
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--good-soft); }
  50%      { box-shadow: 0 0 0 6px rgba(23, 163, 74, 0.05); }
}

/* ---------- Buttons (modern light) ----------
   The dashboard's primary CTA is now a solid cobalt pill (no gradient — the
   purple→indigo gradient was dark-theme flavor). Secondary buttons are
   light, danger stays saturated red, warning is amber-on-light. The .btn
   base is the workhorse, all variants extend it. */
.btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-elev);
  color: var(--text);
  padding: 7px 12px;
  border-radius: 8px;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background 120ms ease, border-color 120ms ease, transform 80ms ease, color 120ms ease;
}
.btn[hidden] { display: none; }
.btn:hover { border-color: var(--border-strong); background: var(--bg-elev-2); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #FFFFFF;
  box-shadow: 0 1px 2px rgba(47, 111, 235, 0.20);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-primary:active { background: var(--accent-active); border-color: var(--accent-active); }

.btn-danger {
  background: #DC2626;
  border-color: #DC2626;
  color: #FFFFFF;
  box-shadow: 0 1px 2px rgba(220, 38, 38, 0.20);
}
.btn-danger:hover { background: #B91C1C; border-color: #B91C1C; }

.btn-warning {
  background: #F59E0B;
  border-color: #F59E0B;
  color: #422006;
  box-shadow: 0 1px 2px rgba(245, 158, 11, 0.20);
}
.btn-warning:hover { background: #D97706; border-color: #D97706; }
.btn-warning:disabled {
  background: var(--bg-elev);
  color: var(--muted);
  border-color: var(--border);
  box-shadow: none;
  opacity: 1;
}

.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--text); background: var(--bg-elev); border-color: transparent; }

/* ---------- Page-level footer ---------- */
/* Scoped to the OUTER footer (the page-level Auto-managed by line)
   so it doesn't bleed into <footer class="tile-foot"> inside each tile
   — that selector was previously matching the tile foot and adding
   20-28px of wasted padding to every tile. */
body > footer {
  padding: 20px 32px 28px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-family: var(--font-sans);
}
footer .sep { opacity: 0.4; }
footer code {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  padding: 1px 6px;
  border-radius: 6px;
  color: var(--text);
  font-size: 11px;
}

/* ============================================================
   MOBILE LAYOUT (≤ 640px viewport)
   ============================================================
   On mobile, the entire tile structure flips from a vertical
   "image-on-top, body-on-bottom" card into a horizontal
   "image-on-left, body-on-right" list item (a Spotify / Apple
   Music style). This:
     - keeps the tile tall enough to be thumb-friendly (~120px),
     - shows the title + icon next to the image (so the user
       doesn't have to read white text on a generated image),
     - hides the desktop header's user chip and sign-out button
       (they're moved to a small footer bar that scrolls with
       the page),
     - compresses the brand hero into a compact strip.

   The desktop rules above stay untouched; the mobile rules
   only apply below 640px, so the PC design is unchanged. */
@media (max-width: 640px) {

  /* ---- Brand hero: compact strip ---- */
  .brand-hero { height: 110px; }
  .brand-hero-inner { padding: 0 18px; gap: 14px; }
  .brand-hero-mark {
    width: 52px; height: 52px; flex-basis: 52px; border-radius: 12px;
    box-shadow: 0 1px 3px rgba(17, 17, 17, 0.06);
  }
  .brand-hero-mark svg { width: 36px; height: 36px; }
  .brand-hero-title { font-size: 20px; }
  .brand-hero-tagline { font-size: 12px; }

  /* ---- Functional header: minimal on mobile ---- */
  /* The brand hero above already shows "ServoChain" with the tagline, so
     the functional header just needs the connection status pill — no
     duplicate brand block, no user chip, no sign-out (those are in the
     mobile footer). */
  .hero { padding: 10px 18px; }
  .user-chip { display: none; }                 /* moved to footer */
  .logout-form { display: none; }              /* moved to footer */
  .hero-meta { padding: 4px 10px; font-size: 11px; }
  .hero-meta .dot { width: 6px; height: 6px; }
  /* Hide the section meta timestamp on mobile (saves space) */
  .section-title .section-meta { display: none; }

  /* ---- Main: tighter padding so tiles go edge-to-edge with breathing room ---- */
  main { padding: 20px 14px 24px; }

  /* ---- Section banner: smaller, tucked ---- */
  .section-title { padding: 0 4px; }
  .section-title h2 { font-size: 11px; letter-spacing: 0.08em; }
  .section-title .section-meta { font-size: 11px; }

  /* ---- Tiles: vertical card on mobile ---- */
  /* The mobile layout mirrors the desktop pattern (image on top, body
     below) so the title stays overlaid on the image. The key difference
     from desktop: the image is wider and shorter, and the upper section
     gets a per-tile colour tint as the background (visible behind the
     image and as a fallback while the image loads). The accent bar
     becomes a thin 2px line at the very bottom of the image area, with
     a "color" class on the tile body for the soft tint. */
  .tiles-grid {
    grid-template-columns: 1fr;
    gap: 14px;
  }

  /* Tile: vertical flex again, like desktop. The thumb spans the full
     width and holds the title overlay. */
  .tile {
    flex-direction: column;
    border-radius: 14px;
  }

  /* Thumb: small banner at the top, "just a bit bigger than the title".
     The title is 16px font (line-height ~24px with padding), and we now
     also overlay a tagline + 2-line description above it, so we give the
     band enough height for all of that text without truncating the
     description. The per-tile colour is the dominant visual — the
     ComfyUI image is just a small detail bleeding through. */
  .tile-thumb {
    flex: 0 0 auto;
    width: 100%;
    height: 92px;                  /* tall enough for title + tagline + 2 lines desc */
    min-height: 0;
    aspect-ratio: auto;
    border-bottom: 2px solid var(--tile-color, var(--accent));
    border-right: none;
    border-top-left-radius: 13px;
    border-top-right-radius: 13px;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    /* Per-tile colour as the dominant background; the image sits
       over it at low opacity for a subtle texture. */
    background:
      linear-gradient(180deg, var(--tile-color, var(--accent)) 0%, var(--tile-color, var(--accent)) 100%);
  }
  /* Image: covers the slim banner, but at lower opacity than before
     since the band is so small the colour should dominate. */
  .tile-thumb-img {
    opacity: 0.55;
  }

  /* Reveal the title overlay on the image (hidden in the previous
     horizontal-list layout) */
  .tile-thumb-head { display: flex; }

  /* Stronger overlay on mobile: the title is white on a coloured
     background + a transparent image. The band now holds tagline +
     description too, so the gradient ramps up earlier. */
  .tile-thumb-overlay {
    background:
      linear-gradient(180deg,
        rgba(0, 0, 0, 0) 0%,
        rgba(0, 0, 0, 0) 5%,
        rgba(0, 0, 0, 0.25) 45%,
        rgba(0, 0, 0, 0.70) 100%);
  }
  /* Slim banner overlay positioning: tighter padding because the
     band is short. The icon and title sit at the bottom where the
     gradient is strongest. */
  .tile-thumb-head {
    left: 14px;
    right: 36px;
    bottom: 6px;
    gap: 8px;
  }
  /* Tagline + description overlay positioning: sit above the
     icon-title row, with extra bottom-padding so the two layers
     (text + title) don't crowd each other. */
  .tile-thumb-text {
    left: 14px;
    right: 14px;
    bottom: 32px;
    padding-right: 22px;
  }
  .tile-icon {
    width: 26px; height: 26px; flex-basis: 26px;
  }
  .tile-icon svg { width: 14px; height: 14px; }

  /* Body: light gray region below the image. flex: 0 so it sizes to its
     content (otherwise the body would stretch to fill any extra space
     in the tile, which on mobile just makes the body taller than it
     needs to be). */
  .tile-body {
    flex: 0 1 auto;
    min-width: 0;
    padding: 10px 14px 12px;
    gap: 4px;
    border-bottom-left-radius: 13px;
    border-bottom-right-radius: 13px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
  }

  /* Tile title: visible over the image. The desktop rules already
     make it white with text-shadow; we don't override here, we just
     make sure the overlay is shown. */
  .tile-title {
    font-size: 15px;
  }

  /* Accent bar: hidden again on mobile (the border on .tile-thumb
     is the line, same as desktop). */
  .tile-accent { display: none; }

  /* Tagline + description: both live inside the thumb now (over the
     image), so on mobile we just shrink the desktop white-text styles
     a touch. */
  .tile-tagline {
    font-size: 11px;
  }
  .tile-desc {
    font-size: 10px;
    -webkit-line-clamp: 2;
  }

  /* Footer row: button + meta on the same line. Tighter gap on
     mobile to keep the row a single line wherever possible. */
  .tile-foot { gap: 6px; padding-top: 4px; }
  .tile-meta { font-size: 11px; }

  /* The status dot stays at the top-right of the image (it overlays
     the thumb, same as desktop). */
  .tile::before {
    top: 12px; right: 12px;
  }

  /* ---- Page footer ---- */
  /* On mobile the footer becomes a two-row layout: the brand on the
     first row, the user bar on the second (the .footer-user-bar is
     hidden on desktop). The "Auto-managed by" line is hidden on mobile
     to save space — the connection dot in the header already conveys
     status. */
  footer {
    padding: 14px 18px 24px;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    font-size: 11px;
  }
  footer > span:first-child { display: none; }   /* hide "Auto-managed by" */
  .footer-user-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
  }
  .user-chip--footer {
    font-size: 12px;
    padding: 5px 10px;
  }
  .logout-btn { font-size: 12px; padding: 5px 10px; }

  /* Reveal the mobile-only user bar at the bottom of the page */
  .footer-user-bar { display: flex; }

}

/* ---------- Login page ----------
   The base :root is already light, so the login only needs the body-level
   background and the grid-centering layout. All the per-element colors come
   from the shared variables. */
body.login-body {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 24px;

  /* Generated abstract gradient, served from the same origin so the request
     is cacheable and the page never blocks on a third-party CDN. Falls back
     to a flat off-white if the image fails to load. */
  background:
    linear-gradient(180deg, rgba(250, 250, 250, 0.30), rgba(250, 250, 250, 0.65)),
    url('/static/login-bg.jpg') center/cover no-repeat,
    var(--bg);
  background-attachment: fixed;
}
.login-shell { width: 100%; max-width: 420px; }
.login-card {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px 28px;
  box-shadow: var(--shadow-card);
}
.login-brand { text-align: center; margin-bottom: 24px; }
.login-brand svg { width: 56px; height: 56px; display: block; margin: 0 auto 14px; }
.login-brand h1 { font-size: 26px; }
.login-subtitle { margin: 6px 0 0; color: var(--muted); font-size: 14px; }
.login-error {
  background: #FEF2F2;
  border: 1px solid #FCA5A5;
  color: #991B1B;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
  margin-bottom: 16px;
}
.login-form { display: flex; flex-direction: column; gap: 14px; }
.login-form .field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text); font-weight: 500; }
.login-form input {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-elev-2);
  color: var(--text);
  padding: 11px 12px;
  border-radius: 8px;
  font-size: 15px;
  font-family: inherit;
  outline: none;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.login-form input::placeholder { color: #9CA3AF; }
.login-form input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(47, 111, 235, 0.15); }
.login-submit { justify-content: center; padding: 11px 14px; font-size: 15px; margin-top: 4px; }
.login-foot { margin: 18px 0 0; color: var(--muted); font-size: 12px; text-align: center; }
.login-foot code {
  background: #F3F4F6;
  border: 1px solid var(--border);
  padding: 1px 6px;
  border-radius: 6px;
  color: var(--text);
  font-size: 11px;
}

/* ---------- User chip + logout (in the hero) ---------- */
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elev);
  color: var(--muted);
  font-size: 12px;
  font-family: var(--font-sans);
}
.user-chip strong { color: var(--text); font-weight: 600; }
.logout-form { display: inline; margin: 0; }
.logout-btn {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--muted);
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 12px;
  font-family: var(--font-sans);
  font-weight: 500;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
.logout-btn:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-elev-2); }
.hero-actions { display: flex; align-items: center; gap: 10px; }

/* The footer user bar (mobile-only): hidden on desktop, revealed on
   mobile via the @media block below. */
.footer-user-bar { display: none; }
