/* arqu_ui — engine-owned layout + chrome styles.
 *
 * Hand-authored (UNLIKE styles.css, which is generated from the KTUI dist and
 * must not be edited). The admin/app layout chrome lives here so it survives a
 * styles.css regeneration: the vendored dist scopes the sidebar shell under a
 * non-semantic template class nothing in a real app carries — this file
 * re-authors that layout under the SEMANTIC .kt-admin-layout the shipped layouts
 * put on <body>, adds the fixed positioning the dist assumed but never declared,
 * wires the responsive off-canvas drawer, and defines the shared chrome regions
 * both engine layouts reference.
 *
 * Engine-owned responsiveness: the responsive drawer-enable (.kt-drawer-responsive)
 * and the drawer-toggle hide (.kt-drawer-toggle) are declared HERE with real
 * @media queries rather than relying on the dist's compiled Tailwind utilities
 * (lg:hidden, lg:[--kt-drawer-enable:false]) — those utilities are tree-shaken
 * from the generated dist and absent in a non-Tailwind host, so engine-critical
 * behavior must not depend on them.
 *
 * Single desktop breakpoint: 64rem. CSS @media cannot read a custom property in
 * its condition, so the literal is repeated across the queries in THIS file
 * only — change the breakpoint here.
 *
 * Cascade: loaded after styles.css, and the dist declares
 * `@layer theme, base, components, utilities`, so these @layer components rules
 * order after the dist's component rules (locked by a test asserting the dist
 * declares that layer). Light-only; theme tokens only.
 */

/* Accessibility token override — UNLAYERED on purpose.
 *
 * The generated styles.css sets the light-theme --primary to var(--color-blue-500)
 * (oklch 62.3% ≈ #2b7fff): white button text on it is 3.76:1, which FAILS WCAG 2.1
 * AA (needs 4.5:1 for normal text) — axe-core flags .kt-btn-primary as a serious
 * color-contrast violation across every consumer (#93). Darken --primary to
 * var(--color-blue-600) (oklch 54.6%), the value the dist already uses in its
 * other theme context, which clears AA with white text.
 *
 * This MUST be unlayered: styles.css declares --primary in an UNLAYERED :root
 * rule, and an unlayered declaration beats any @layer rule regardless of order —
 * so a layered override here (the rest of this file lives in @layer components)
 * would LOSE to the dist token. Being unlayered too, this override wins by source
 * order: _head loads styles.css then layout.css, so this later :root declaration
 * takes precedence. styles.css is generated/do-not-edit, so the fix lives here as
 * a token override — the README's sanctioned per-engine theming mechanism. */
:root {
  --primary: var(--color-blue-600);
}

/* Self-hosted Inter (#104) — UNLAYERED, like the --primary override above.
 *
 * Metronic's whole type scale is Inter. The engine used to load it from
 * fonts.googleapis.com via a _head <link> + two preconnects, but on a consumer's
 * auth/admin pages that leaks sign-in metadata off-origin and hard-depends on
 * Google for a security-sensitive surface (CodeRabbit, arqu-co/id#96). Post-#92
 * the engine owns the base + Inter, so we vendor the four static woff2 weights
 * Metronic uses (app/assets/fonts/arqu_ui/inter/, shipped by the gemspec's
 * app/**/* glob) and declare them here. Propshaft flattens the asset load path,
 * so a font at app/assets/fonts/arqu_ui/inter/Inter-Regular.woff2 is served at
 * arqu_ui/inter/Inter-Regular.woff2 — from this stylesheet (logical arqu_ui/)
 * the relative url("/assets/arqu_ui/inter/Inter-Regular-0c7c28ff.woff2") resolves there, mirroring exactly
 * how keenicons.css references its own vendored faces. font-display: swap paints
 * text immediately in the fallback then swaps to Inter once loaded. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/arqu_ui/inter/Inter-Regular-0c7c28ff.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/arqu_ui/inter/Inter-Medium-061ba9f2.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/arqu_ui/inter/Inter-SemiBold-adf8f9dc.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/arqu_ui/inter/Inter-Bold-ebe00c8d.woff2") format("woff2");
}

/* Make Inter the first family of the resolved sans stack. The generated
 * styles.css resolves the body through --default-font-family: var(--font-sans),
 * but its --font-sans is the bare Tailwind system stack (no Inter) — so without
 * this the self-hosted face would never paint and the body would fall back to
 * the system UA face, not Inter. UNLAYERED so it beats the dist's unlayered
 * :root --font-sans by source order (styles.css then layout.css), the same
 * precedence mechanism the --primary override above relies on. The tail mirrors
 * the dist's stack so a host with Inter unavailable still degrades gracefully. */
:root, :host {
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}

/* Keenicon hide override — UNLAYERED on purpose.
 *
 * keenicons.css sets `display: inline-flex` on the icon base classes
 * (.ki-filled / .ki-duotone / .ki-outline / .ki-solid) with NO @layer, while the
 * generated styles.css emits Tailwind's `.hidden { display: none }` inside
 * `@layer utilities`. An UNLAYERED declaration beats ANY layered rule regardless
 * of specificity, so the icon's `display: inline-flex` wins over `.hidden` — a
 * keenicon tagged `hidden` still renders. That silently breaks every component
 * that toggles an icon's visibility with `.hidden`: CopyButtonComponent ships its
 * check icon as `ki-filled ki-check hidden` and swaps the two on copy, so without
 * this fix BOTH icons show at once and the checkmark leaks in the idle state.
 *
 * Restore the intent by re-asserting the hide UNLAYERED (so it is not outranked by
 * the unlayered icon rule) and at specificity 0,2,0 (`.ki-*.hidden` beats the
 * 0,1,0 `.ki-*` base), which wins on specificity among the two unlayered rules.
 * styles.css/keenicons.css are generated/do-not-edit, so the fix lives here. */
.ki-filled.hidden,
.ki-duotone.hidden,
.ki-outline.hidden,
.ki-solid.hidden {
  display: none;
}

@layer components {
  /* ---- admin sidebar-shell layout (semantic .kt-admin-layout scope) ------- */
  .kt-admin-layout {
    --sidebar-transition-duration: 0.3s;
    --sidebar-transition-timing: ease;
    --sidebar-width: 280px;
    --header-height: 70px;
  }
  @media (max-width: 64rem) {
    .kt-admin-layout {
      --header-height: 60px;
    }
  }
  .kt-admin-layout .kt-header {
    height: var(--header-height);
    transition: inset-inline-start var(--sidebar-transition-duration) var(--sidebar-transition-timing);
  }
  .kt-admin-layout .kt-sidebar {
    width: var(--sidebar-width);
  }
  .kt-admin-layout .kt-wrapper {
    transition: padding-inline-start var(--sidebar-transition-duration) var(--sidebar-transition-timing);
  }

  /* Desktop (>=64rem): pin the sidebar + header and offset the content. The dist
     shipped the offsets (padding/inset) but never the fixed positioning they
     assume, so the chrome never actually pinned. */
  @media (min-width: 64rem) {
    .kt-admin-layout.kt-sidebar-fixed .kt-sidebar {
      position: fixed;
      inset-block: 0;
      inset-inline-start: 0;
      z-index: 20;
      transition: width var(--sidebar-transition-duration) var(--sidebar-transition-timing);
    }
    .kt-admin-layout.kt-header-fixed .kt-header {
      position: fixed;
      inset-block-start: 0;
      inset-inline-end: 0;
      z-index: 10;
    }
    .kt-admin-layout .kt-sidebar .kt-sidebar-header {
      height: var(--header-height);
    }
    .kt-admin-layout.kt-sidebar-fixed .kt-wrapper {
      padding-inline-start: var(--sidebar-width);
    }
    .kt-admin-layout.kt-sidebar-fixed.kt-header-fixed .kt-header {
      inset-inline-start: var(--sidebar-width);
    }
    /* Reserve the fixed header's height. Scoped to the desktop breakpoint where
       the header is actually position: fixed — below it the header is in normal
       flow, so a base-scope padding-top is a dead gap above the header on mobile
       (#101). Keyed on .kt-header-fixed (not sidebar-fixed): the offset pairs
       with the fixed header regardless of the sidebar's fixed state. */
    .kt-admin-layout.kt-header-fixed .kt-wrapper {
      padding-top: var(--header-height);
    }
    /* Fill the viewport below the fixed header so the footer pins to the bottom
       of the page on short content. The header is fixed here (out of flow) and
       the wrapper reserves its height via padding-top, so the content column
       fills exactly the remaining viewport: 100dvh - header. */
    .kt-admin-layout.kt-header-fixed .kt-sidebar-wrapper {
      min-block-size: calc(100dvh - var(--header-height));
    }
  }

  /* Sidebar as a flex column with a scrollable content region; this is also the
     off-canvas drawer surface below the breakpoint (KTDrawer adds
     .kt-drawer/.kt-drawer-start, which styles.css positions + slides). */
  .kt-admin-layout .kt-sidebar {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background-color: var(--background);
    border-inline-end: 1px solid var(--border);
  }
  /* Brand header: center the brand mark with the page padding, like Metronic's
     kt-sidebar-header (flex items-center px-6). Without this the brand sits at
     the sidebar's top-left and clips. shrink-0 keeps it from collapsing. */
  .kt-admin-layout .kt-sidebar-header {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    padding-inline: 1.5rem;
  }
  .kt-admin-layout .kt-sidebar-content {
    flex: 1 1 auto;
    overflow-y: auto;
  }
  /* The content column: the sidebar's sibling that stacks the header + main.
     Engine-owned here (the dist only defined a same-named class as a .kt-sidebar
     descendant — a different DOM position — and that inert rule was curated out
     of styles.css). min-width:0 lets long content shrink instead of overflowing. */
  .kt-admin-layout .kt-sidebar-wrapper {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }
  /* The main region grows to fill the content column, so a short page still
     pushes the footer to the bottom instead of letting it float up under the
     content (Metronic's kt-wrapper is `flex grow flex-col`; the engine content
     column mirrors that growth here). */
  .kt-admin-layout .kt-admin-main {
    flex: 1 0 auto;
    padding: calc(var(--spacing) * 5);
  }

  /* ---- engine-owned responsive drawer primitives (no dist Tailwind) -------- */
  /* KTDrawer reads --kt-drawer-enable off the element: enabled (live off-canvas
     drawer) below the breakpoint, disabled (static chrome) at/above it. Replaces
     the dist's lg:[--kt-drawer-enable:false] utility so it survives regen and
     works without Tailwind in the host. */
  .kt-drawer-responsive {
    --kt-drawer-enable: true;
  }
  @media (min-width: 64rem) {
    .kt-drawer-responsive {
      --kt-drawer-enable: false;
    }
  }
  /* The drawer toggle (hamburger) is only needed while the drawer is off-canvas;
     hide it once the sidebar is the static fixed chrome. Replaces lg:hidden. */
  @media (min-width: 64rem) {
    .kt-drawer-toggle {
      display: none;
    }
  }

  /* ---- shared chrome: referenced by BOTH the admin and app layouts --------- */
  .kt-admin-layout .kt-header,
  .kt-app-layout .kt-header {
    display: flex;
    align-items: center;
    background-color: var(--background);
    border-block-end: 1px solid var(--border);
  }
  .kt-admin-layout .kt-header-container,
  .kt-app-layout .kt-header-container {
    display: flex;
    align-items: center;
    gap: calc(var(--spacing) * 2);
    width: 100%;
  }
  /* The header's primary nav region (the optional :header_nav slot) sits in the
     header flex row, left of the auto-pushed user menu. */
  .kt-admin-layout .kt-header-nav,
  .kt-app-layout .kt-header-nav {
    display: flex;
    align-items: center;
  }
  .kt-admin-layout .kt-header-user-menu,
  .kt-app-layout .kt-header-user-menu {
    margin-inline-start: auto;
  }
  .kt-brand {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--spacing) * 2);
    font-weight: 600;
    color: var(--foreground);
  }
  .kt-brand-name {
    font-weight: 600;
  }
  .kt-brand-logo {
    height: calc(var(--spacing) * 8);
    width: auto;
  }

  /* ---- app-layout-only chrome: the top-nav brand + horizontal primary nav ----
     The app (top-nav) layout references kt-header-brand, kt-header-nav and
     kt-app-main; the shared chrome above styles the header shell, but these
     app-only regions need their own structural rules or they render unstyled. */
  .kt-app-layout {
    --header-height: 70px;
  }
  @media (max-width: 64rem) {
    .kt-app-layout {
      --header-height: 60px;
    }
  }
  .kt-app-layout .kt-header {
    height: var(--header-height);
  }
  /* The app layout is a fixed top-nav surface (no sidebar/drawer), so — unlike
     the admin header, which only pins at the desktop breakpoint — the app header
     pins at every width when kt-header-fixed is set, and the content region
     offsets by the header height so nothing hides behind it. */
  .kt-app-layout.kt-header-fixed .kt-header {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 10;
  }
  .kt-app-layout .kt-header-brand {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    margin-inline-end: calc(var(--spacing) * 4);
  }
  /* The horizontal primary nav: space the top-level items so host-supplied
     links (or a horizontal MenuComponent) read as a nav row, not a run-on. */
  .kt-app-layout .kt-header-nav {
    gap: calc(var(--spacing) * 5);
  }
  /* The content region below the top bar: vertical breathing room to match the
     admin main (kt-container-fixed already supplies the centered max-width +
     horizontal padding). */
  .kt-app-layout .kt-app-main {
    padding-block: calc(var(--spacing) * 5);
  }
  /* When the header is fixed it leaves the flow, so the content must clear it:
     offset the top by the header height plus the same breathing room. */
  .kt-app-layout.kt-header-fixed .kt-app-main {
    padding-top: calc(var(--header-height) + var(--spacing) * 5);
  }
}

/* Visually-hidden utility — re-authored here because the generated styles.css is
 * Tailwind-tree-shaken from the design-system dist, whose curated templates never
 * use `.sr-only`, so the utility is absent from the shipped stylesheet. But engine
 * components DO depend on it: CopyButtonComponent hides its live-region status text
 * (and an icon-only button's label) with `.sr-only`, and PaginationComponent labels
 * its prev/next controls the same way. Without this rule those nodes render visibly
 * (e.g. a duplicated "Copied" next to the button, a leaked "Previous"/"Next"),
 * breaking both fidelity and the accessible-name contract. Same precedent as the
 * tree-shaken responsive utilities re-authored above: the engine owns what the dist
 * dropped. This is the standard accessible-hiding rule (Tailwind's `.sr-only`);
 * placed in @layer utilities so it composes with the dist's utility layer. */
@layer utilities {
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
  }
}

/* Read-only input styling — engine-owned because the dist ships none.
 *
 * The generated styles.css styles `.kt-input:disabled` (muted: opacity 60% +
 * cursor not-allowed) but declares NO read-only rule, so a readonly field —
 * which FormFieldComponent(readonly: true) emits, and which stays focusable and
 * form-submitting unlike a disabled one — would render identical to an editable
 * input, giving no signal it can't be typed into. The DS field is the opinionated
 * default, so it owns that affordance: mirror the dist's own non-editable
 * treatment (opacity + not-allowed) for read-only inputs.
 *
 * Target the `[readonly]` ATTRIBUTE, not the `:read-only` pseudo-class: the
 * pseudo-class also matches inputs that are inherently non-editable regardless of
 * the attribute — notably `type="file"`, which the dist explicitly styles as
 * `.kt-input[type=file]` — so `.kt-input:read-only` would wrongly mute a normal
 * file input. `[readonly]` matches only a control the component actually marked
 * readonly. Same precedent as the rules above: the engine owns what the dist
 * dropped. @layer components so it orders after (and matches the specificity
 * origin of) the dist's `.kt-input:disabled` rule in that same layer. */
@layer components {
  .kt-input[readonly] {
    cursor: not-allowed;
    opacity: 60%;
  }
}

/* FormFieldComponent presentation + layout intents — engine-owned because the
 * dist ships no modifier for any of them (#126). Same precedent as the read-only
 * rule above: the opinionated DS field owns the treatments its callers need, as
 * first-class named intents rather than a per-app `**attributes`/utility splat.
 * @layer components so they order after the dist's `.kt-input`/`.kt-textarea`
 * component rules in that same layer and can override the base font/alignment.
 * The selectors are class-only (not element-qualified) so a monospace multiline
 * field — `kt-textarea kt-input-code` — is covered by the same rule. */
@layer components {
  /* variant: :code — the monospace treatment for identifier-ish values (URIs,
     scopes, keys). Monospace disambiguates l/1/O/0 and keeps structure legible. */
  .kt-input-code {
    font-family: var(--font-mono);
  }

  /* variant: :otp — the one-time-code treatment: centered, wide-tracking,
     larger monospace digits for a short numeric code. */
  .kt-input-otp {
    text-align: center;
    letter-spacing: 0.5em;
    font-family: var(--font-mono);
    font-size: var(--text-lg);
    font-weight: var(--font-weight-medium);
  }

  /* layout: :inline — let the field grow inside a `flex flex-wrap` toolbar row
     (its own basis, sharing/filling leftover space) instead of the default
     full-width block a stacked kt-form-item renders as. min-width:0 lets the
     control shrink so a long placeholder can't overflow the toolbar. */
  .kt-form-item-inline {
    flex: 1 1 auto;
    min-width: 0;
  }
}
