/* SPDX-License-Identifier: Apache-2.0 */
/* Interaction & polish layer, per design/design.md. Theme colors come from
   the daisyui custom themes defined in app.star (ace.style custom_themes);
   everything here uses theme variables so it adapts to light/dark. */

/* ---- Typography ---------------------------------------------------------
   Self-hosted brand type (SIL OFL, woff2 in static/fonts/, downloaded from
   fontsource): Space Grotesk for headings, Inter for body, JetBrains Mono
   for code and identifiers. The generated tailwind layer routes every font
   through --font-sans/--font-mono, so redefining the variables here covers
   the preflight body default and the font-sans/font-mono utilities alike.
   Weights follow usage: Inter 400/500/600 (body, font-medium,
   font-semibold), Space Grotesk 500/700 (card h2s, titles - the extrabold
   italic page title maps to 700 with a synthesized slant, Space Grotesk
   has no 800 and no italic), JetBrains Mono 400/500. The three faces used
   above the fold are preloaded in the head define. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-400.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-500.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-600.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/space-grotesk-500.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/space-grotesk-700.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/jetbrains-mono-500.woff2") format("woff2");
}

:root {
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
    "Noto Color Emoji";
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
    Consolas, "Liberation Mono", "Courier New", monospace;
}

/* Headings and heading-like labels carry the display face. This rule is
   UNLAYERED while the tailwind utilities live in a cascade layer, so it
   would silently beat .font-mono on the same element - mono headings (sync
   repo names, app path h2s) are excluded explicitly */
h1:not(.font-mono),
h2:not(.font-mono),
h3:not(.font-mono),
.page-title,
.stat-title {
  font-family: "Space Grotesk", var(--font-sans);
}

/* ---- Branded loading indicator ----------------------------------------
   A thin primary-colored progress line instead of a generic spinner. The
   element carries .htmx-indicator, so htmx fades it in during requests;
   the sweep communicates activity without blocking content. */
.progress-line {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  overflow: hidden;
  pointer-events: none;
  transition: opacity 200ms ease;
}
.progress-line::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  border-radius: 2px;
  background: var(--color-primary);
  animation: progress-sweep 1.1s var(--ease-out-soft) infinite;
}
@keyframes progress-sweep {
  from {
    transform: translateX(-110%);
  }
  to {
    transform: translateX(360%);
  }
}

/* ---- In-flight actions -----------------------------------------------
   Buttons disabled by htmx during their own request (hx-disabled-elt="this")
   show an inline spinner. Statically disabled buttons are unaffected. */
button[hx-disabled-elt][disabled] {
  pointer-events: none;
  opacity: 0.75;
}
button[hx-disabled-elt][disabled]::before {
  content: "";
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  flex: none;
  border-radius: 9999px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: btn-spin 0.5s linear infinite;
}
@keyframes btn-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- Sidebar navigation ------------------------------------------------
   Active item gets a primary accent bar that grows in; inactive items ease
   toward the content on hover. Felt, not seen. */
.menu li > a {
  position: relative;
  transition:
    background-color 150ms ease,
    color 150ms ease,
    translate 200ms var(--ease-out-soft);
}
.menu li > a:hover:not(.menu-active) {
  translate: 2px 0;
}
.menu li > a::before {
  content: "";
  position: absolute;
  left: -0.4rem;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: 2px;
  background: var(--color-primary);
  transform: translateY(-50%);
  transition: height 250ms var(--ease-out-soft);
}
.menu li > a.menu-active::before {
  height: 55%;
}

/* ---- Status dots ---------------------------------------------------------
   Healthy state breathes gently; errors pulse a little more insistently to
   direct attention (200-400ms attention band, slowed to stay calm). */
.status-success {
  animation: status-breathe 3s ease-in-out infinite;
}
@keyframes status-breathe {
  0%,
  100% {
    box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-success) 35%, transparent);
  }
  50% {
    box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-success) 10%, transparent);
  }
}
.status-error {
  animation: status-alert 1.6s ease-in-out infinite;
}
@keyframes status-alert {
  0%,
  100% {
    box-shadow: 0 0 0 0 color-mix(in oklab, var(--color-error) 45%, transparent);
  }
  50% {
    box-shadow: 0 0 0 5px color-mix(in oklab, var(--color-error) 12%, transparent);
  }
}

/* ---- Field focus ---------------------------------------------------------
   Focused input glows softly in the brand color and grows slightly wider
   (width utility transition set in markup). */
label.input {
  transition:
    border-color 200ms ease,
    box-shadow 200ms ease;
}
label.input:focus-within {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary) 15%, transparent);
}

/* Visible keyboard focus everywhere, in the brand color */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* ---- Page titles ------------------------------------------------------
   Main list-page headings carry a brand accent bar, giving each screen a
   strong anchor without contrast concerns. The width matches the "OpenRun"
   prefix of the titles (em-based, so it tracks the heading size); the bar
   fades in from nothing up to the brand light green. */
.page-title::after {
  content: "";
  display: block;
  width: 4.3em;
  height: 3px;
  margin-top: 0.3rem;
  margin-bottom: 0.6rem;
  border-radius: 2px;
  background: linear-gradient(90deg, color-mix(in oklch, var(--color-secondary) 5%, transparent), var(--color-secondary));
}

/* ---- Cards --------------------------------------------------------------
   Content cards lift very slightly on hover, hinting at their grouping
   without implying every card is clickable. */
.card.border {
  transition:
    box-shadow 200ms var(--ease-out-soft),
    border-color 200ms var(--ease-out-soft);
}
.card.border:hover {
  box-shadow: 0 2px 10px -4px color-mix(in oklab, var(--color-secondary) 24%, transparent);
  border-color: color-mix(in oklab, var(--color-secondary) 24%, var(--color-base-300));
}

/* ---- HTMX swap polish ---------------------------------------------------
   Swapped-in content settles with a soft fade/slide, so filters, actions
   and auto-refreshes feel alive instead of snapping. */
@keyframes fade-slide-in {
  from {
    opacity: 0.4;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.htmx-settling {
  animation: fade-slide-in 250ms var(--ease-out-soft);
}

/* ---- Tables ---------------------------------------------------------------
   Crisp column headers in the brand secondary color over a brand-tinted
   rule; rows respond immediately to hover. */
.table thead th {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-secondary);
  border-bottom: 1px solid color-mix(in oklab, var(--color-secondary) 30%, transparent);
}
.table tbody tr {
  transition: background-color 150ms ease;
}

/* ---- Logo mark -------------------------------------------------------------
   A small moment of delight, discovered on hover. */
/* Cross-document view transitions: same-origin page navigation (e.g. the
   sidebar links) crossfades instead of flashing */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

/* ---- Navigation progress -----------------------------------------------
   Fixed top bar shown between clicking a link and the next page rendering,
   so slow pages (containers, detail views) give immediate feedback. */
.nav-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 100;
  display: none;
  overflow: hidden;
  background: color-mix(in oklab, var(--color-primary) 20%, transparent);
}
.nav-progress::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  background: var(--color-primary);
  animation: nav-progress-sweep 1s ease-in-out infinite;
}
.nav-progress.active {
  display: block;
}
@keyframes nav-progress-sweep {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(350%);
  }
}

.logo-mark {
  transition: transform 250ms var(--ease-out-soft);
}
.logo-mark:hover {
  transform: scale(1.05);
}

/* ---- Accessibility ---------------------------------------------------------
   All motion is removed for users who prefer reduced motion. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
