/* The site's own styles, for what unfold's compiled classes cannot express.
 *
 * Every other style on the site is a class that unfold compiled. The template
 * class check enforces that, and it reads this file too. A rule earns a place
 * here only where a class cannot do the job, such as a pseudo-element or a
 * transition from `display: none`. A utility that unfold left out also earns
 * a rule, where a template asks for it by name. Such a rule takes Tailwind's
 * name for the utility, so the two agree if unfold compiles it later.
 *
 * This file loads before unfold's stylesheet. A rule here that ties with one
 * of unfold's on specificity loses.
 */

/* A changelist's row links read as links, with an underline and the
 * open-in-new glyph from the icon font unfold loads. Unfold paints the link
 * `text-important`, the colour of the text around it, so nothing else marks
 * it as a link.
 *
 * A `@display(header=True)` column puts a `<strong>` title and a `<p>` caption
 * in one link. There, only the title gets the style. Otherwise the underline
 * runs through the caption and the glyph goes onto a line of its own. */
#result_list a.text-important:not(:has(strong)),
#result_list a.text-important strong {
  text-decoration: underline;
  text-decoration-color: color-mix(in oklch, currentColor 40%, transparent);
  text-underline-offset: 3px;
}

#result_list a.text-important:hover:not(:has(strong)),
#result_list a.text-important:hover strong {
  text-decoration-color: currentColor;
}

#result_list a.text-important:not(:has(strong))::after,
#result_list a.text-important strong::after {
  content: "open_in_new";
  font-family: "Material Symbols Outlined";
  font-size: 0.875em;
  line-height: 1;
  margin-left: 0.25em;
  vertical-align: -0.1em;
}

/* A sidebar group's list eases in when the group opens. Alpine's `x-show`
 * changes `display`, and only `@starting-style` lets a transition start from
 * `display: none`. Closing stays instant. Alpine sets `display: none` before a
 * transition can run, so an animation on close needs a change to the group's
 * markup, not a rule. */
ol[x-show="navigationOpen || filter"] {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 150ms ease-out,
    transform 150ms ease-out,
    display 150ms allow-discrete;

  @starting-style {
    opacity: 0;
    transform: translateY(-0.25rem);
  }
}

/* List markers. Tailwind's preflight removes them, and unfold compiled no
 * `list-*` utility. Without these rules, a set of rules or a numbered
 * procedure must write its own bullets or digits. Indent with `pl-*`, which
 * unfold did compile. */
.list-disc {
  list-style-type: disc;
}

.list-decimal {
  list-style-type: decimal;
}

/* A stacking context for the preview tables. Unfold does not compile
 * `isolate`, and the templates use it. The sticky `<thead>` of a table is
 * `z-100` and the sidebar is `z-60`. Both values are unfold's. Without a
 * stacking context, the header paints over the sidebar below `xl`. */
.isolate {
  isolation: isolate;
}

/* The sidebar slides in and out. It does not appear and vanish. Unfold shows
 * and hides it with `x-show` on a wrapper, and sets the wrapper's width
 * inline. A transition from `display: none` needs `@starting-style`, which
 * also runs the slide on every page load. So the wrapper stays rendered.
 *
 * These rules find Alpine's inline `display: none` by attribute and override
 * it. A closed wrapper has no width. The wrapper is a spacer beside the
 * content, so its width moves the page. The panel, `#nav-sidebar`, is fixed
 * inside the wrapper. When the sidebar closes, the panel slides off to the
 * left and is hidden from focus. The open width and the `display` are inline
 * styles, so the override needs `!important`. No transition runs at first
 * paint, so a page loads with the sidebar where it was. */
div:has(> div > #nav-sidebar) {
  transition: width 200ms ease-out;
}

div:has(> div > #nav-sidebar)[style*="display: none"] {
  display: block !important;
  width: 0 !important;
}

/* Unfold's script treats a window 1280px wide or less as narrow. There the
 * sidebar starts shut and opens over the page. Its `xl:relative` puts the
 * sidebar in the page flow from 1280px up. Here the sidebar overlays the page
 * at 1280px too, as the script expects. `static/js/sidebar.js` stops the
 * script shutting the sidebar when only the page's height changes. */
@media (max-width: 1280px) {
  div:has(> div > #nav-sidebar) {
    position: fixed;
  }
}

#nav-sidebar {
  transition:
    transform 200ms ease-out,
    visibility 200ms;
}

div[style*="display: none"] > div > #nav-sidebar {
  transform: translateX(-100%);
  visibility: hidden;
}

/* A grid header cell keeps its primary tint in dark mode. Unfold's table
 * component paints every `<th>` `dark:bg-white/[.02]`. That rule comes later
 * in unfold's stylesheet than `dark:bg-primary-500/20`, so the grid's tint
 * loses the tie on a `<th>`. This selector matches the cell by both, so its
 * higher specificity wins. The value is the one unfold's utility paints. */
.dark th[class~="dark:bg-primary-500/20"] {
  background-color: color-mix(in oklab, var(--color-primary-500) 20%, transparent);
}

/* An explainer is a native `<dialog>`. The browser centres an open dialog
 * with `margin: auto`. Tailwind's preflight sets every margin to zero, so this
 * rule puts the margin back. No class reaches the backdrop, because it is a
 * pseudo-element. It gets the tint and the blur of unfold's own modal
 * overlay. */
dialog {
  margin: auto;
}
dialog::backdrop {
  background-color: rgb(0 0 0 / 0.2);
  backdrop-filter: blur(4px);
}
.dark dialog::backdrop {
  background-color: rgb(0 0 0 / 0.6);
}

/* A popover takes the colour of the text around it. The browser paints a
 * popover's text `CanvasText`, which follows `color-scheme`. Unfold's dark
 * mode is a `.dark` class and leaves `color-scheme` at `normal`, so in dark
 * mode that text is black on a dark panel. */
[popover] {
  color: inherit;
}

/* A column filter opens under its head's button, with their left edges in
 * line, as unfold's dropdowns do. Near an edge of the window it flips. Only
 * one filter is open at a time, and `sortAndFilter` marks its button
 * `aria-expanded`, so one anchor name serves every head. A browser without
 * anchor positioning centres the filter in the window. */
[data-filter-head][aria-expanded="true"] {
  anchor-name: --open-filter;
}

th > form[popover] {
  margin: auto;

  @supports (anchor-name: --a) {
    position-anchor: --open-filter;
    position-area: bottom span-right;
    position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
    inset: auto;
    margin: 0.25rem 0 0;
  }
}

/* The Loader Lens row menu opens under its Actions button, with their right
 * edges in line, as unfold's row actions menu does. It is placed as a
 * column filter is. */
[data-actions][aria-expanded="true"] {
  anchor-name: --open-row-menu;
}

nav[popover][aria-label="Actions"] {
  margin: auto;

  @supports (anchor-name: --a) {
    position-anchor: --open-row-menu;
    position-area: bottom span-left;
    position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
    inset: auto;
    margin: 0.25rem 0 0;
  }
}

/* A Config Tweaker help panel opens under its info button, with their left
 * edges in line, or their right edges with `data-align="end"`. No script marks
 * the open button, so `:has` finds it by the open panel after it. */
[popovertarget]:has(+ :popover-open) {
  anchor-name: --open-help;
}

[popovertarget] + div[popover] {
  margin: auto;

  @supports (anchor-name: --a) {
    position-anchor: --open-help;
    position-area: bottom span-right;
    position-try-fallbacks: flip-inline, flip-block, flip-block flip-inline;
    inset: auto;
    margin: 0.25rem 0 0;
  }
}

[popovertarget] + div[popover][data-align="end"] {
  @supports (anchor-name: --a) {
    position-area: bottom span-left;
  }
}

/* A column filter's list of values scrolls when a column has many values,
 * such as Provider. Unfold compiled no `max-h` of this size. */
.max-h-64 {
  max-height: 16rem;
}

/* Unfold compiles no `indeterminate:` variant, so a part-ticked box looks
 * unticked. The permission grid's tick-all boxes use the state. */
input[type="checkbox"]:indeterminate {
  background-color: var(--color-primary-600);
  border-color: var(--color-primary-600);
}
input[type="checkbox"]:indeterminate::after {
  color: var(--color-white);
  content: "remove";
}

/* The Loader Lens files dialog marks a file that arrived but did not load.
 * Unfold compiles no yellow background. */
.bg-yellow-500 {
  background-color: var(--color-yellow-500);
}

/* A table box as tall as the window, less the toolbar above it. */
.max-h-\[calc\(100vh-4rem\)\] {
  max-height: calc(100vh - 4rem);
}

/* A sticky head loses the border under its row, because a collapsed border
 * belongs to the table, not to the cell. A shadow moves with the cell. */
thead.sticky th {
  box-shadow: inset 0 -1px 0 var(--color-base-200);
}
.dark thead.sticky th {
  box-shadow: inset 0 -1px 0 var(--color-base-800);
}

/* The Loader Lens dims its body while htmx fetches a new list.
 * htmx marks the request with a class that no utility can match. */
#loader-lens-body.htmx-request {
  opacity: 0.5;
  transition: opacity 150ms ease-out;
}

/* The Loader Lens strip is an SVG. Unfold compiles no stroke colours. */
.stroke-green-500 {
  stroke: var(--color-green-500);
}
.stroke-yellow-500 {
  stroke: var(--color-yellow-500);
}
.stroke-red-500 {
  stroke: var(--color-red-500);
}
.stroke-base-300 {
  stroke: var(--color-base-300);
}
.dark .dark\:stroke-base-600 {
  stroke: var(--color-base-600);
}

/* Whitespace marks are Unicode's control pictures, such as `␠` and `␊`, and
 * `⍽`. Few system fonts draw them. Without a font, a browser draws an empty
 * box, and Chromium on Linux takes about 0.1 ms to search for one per mark.
 * The font is Noto Sans Symbols 2, unmodified, from the Noto release
 * `NotoSansSymbols2-v2.008` at github.com/notofonts/symbols, file
 * `unhinted/otf/NotoSansSymbols2-Regular.otf`. Its licence is the SIL Open
 * Font License, beside it. `unicode-range` limits it to the marks, so every
 * other character keeps the font around it. */
@font-face {
  font-family: "Noto Sans Symbols 2";
  src: url("../fonts/NotoSansSymbols2-Regular.235194f6af5a.otf") format("opentype");
  unicode-range: U+237D, U+2400-2426;
  font-display: swap;
}

input[data-marks],
#parsing-log {
  font-family: "Noto Sans Symbols 2", var(--font-sans);
}

[data-mark] {
  font-family: "Noto Sans Symbols 2", var(--font-mono);
}

/* The Service Health ring empties over the wait before the next check. htmx
 * swaps in a new ring with each answer, so the animation starts over. The
 * template sets the duration, so it stays equal to the wait. */
.service-health-countdown circle[pathLength] {
  stroke-dasharray: 1;
  animation-name: service-health-countdown;
  animation-timing-function: linear;
  animation-fill-mode: forwards;
}

@keyframes service-health-countdown {
  to {
    stroke-dashoffset: 1;
  }
}

/* A failed check marks the element that asked, and only a script can. The
 * ring hides, because the retry does not restart it. */
.service-health-failed {
  display: none;
}

[data-failed] .service-health-failed {
  display: inline;
}

[data-failed] .service-health-countdown {
  visibility: hidden;
}

/* The keyword and statement tables read as one table of rows, not a stack of
 * boxes. The boxes touch and share their borders. The inputs are unfold's
 * widgets, which carry their own rounding and a `max-w-2xl` on the input and
 * on its wrapper, so the style is here. Without the cap, a box is as wide as
 * the "New" box under it. */
[data-joined-rows] td {
  padding-bottom: 0;
  padding-top: 0;
}

[data-joined-rows] input[type="text"] {
  border-radius: 0;
  margin-top: -1px;
  max-width: none;
}

[data-joined-rows] .max-w-2xl {
  max-width: none;
}

/* A row of a Config Editor table marks a change once, on its left edge, for
 * any box in it. Each box sets `data-changed`. The bar lies over the edge of
 * the row's first box, so nothing moves when it shows. */
:is(#field-groups, #columns-board) tbody > tr > td:first-child {
  position: relative;
}

:is(#field-groups, #columns-board) tbody > tr:has([data-changed]) > td:first-child::before {
  background: var(--color-primary-500);
  bottom: 0;
  content: "";
  left: 0;
  position: absolute;
  top: 0;
  width: 3px;
  z-index: 1;
}

/* A row's switch shows its focus ring only for the keyboard. A click focuses
 * the box too, and unfold's `focus-within` would ring it. */
[data-switch]:has(input:focus-visible) {
  outline: 2px solid var(--color-primary-600);
  outline-offset: -2px;
}

/* The Config Editor's sections sit side by side, so the editor is shorter.
 * A section wraps to the next row once it would be under 28rem wide. Each
 * section grows to fill its row. */
#field-groups {
  column-gap: 2.5rem;
}

#field-groups > section {
  flex: 1 1 28rem;
  min-width: 0;
}

/* The File Parsing grid. Two label/value pairs share a row where the pane is
 * wide enough: Delimitter and TextQualifier, TextFileSpacing and
 * ExcelSheetname, HeaderRow and DataRowStart. Complex takes a row of its own.
 * The grid draws its top and left lines, and each cell its right and bottom
 * lines, so no line is doubled. In a narrow pane, the grid gives up the gap at
 * its right, so its value column stays at least 10rem wide. */
#field-group-reading [data-label-value] {
  border-left: 1px solid var(--color-base-200);
  border-top: 1px solid var(--color-base-200);
  display: grid;
  grid-template-columns: repeat(auto-fill, 9rem minmax(8rem, 1fr));
  width: max(calc(100% - 2rem), min(100%, 22rem));
}

#field-group-reading [data-label-value] > * > * {
  border-bottom: 1px solid var(--color-base-200);
  border-right: 1px solid var(--color-base-200);
}

.dark #field-group-reading [data-label-value],
.dark #field-group-reading [data-label-value] > * > * {
  border-color: var(--color-base-700);
}

#field-group-reading [data-whole-row] {
  grid-column: 2 / -1;
}

#field-group-matching table,
#field-group-options table {
  width: 100%;
}

/* The File Parsing grid and the Columns board draw their grid with their
 * cells. Their boxes are unfold's widgets, so their border, rounding, shadow,
 * `max-w-2xl` and `min-w-20` go here. A box wider than its cell would paint
 * over the cell's right border. The box keeps unfold's focus outline, which
 * sits inside the cell. */
:is([data-label-value], [data-grid]) input[type="text"] {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  max-width: none;
  min-width: 0;
}

/* A box in a table or a grid is slim, so more rows fit on a short screen. */
#config-tweaker :is(table, [data-label-value]) input[type="text"] {
  padding-bottom: calc(var(--spacing) * 1);
  padding-top: calc(var(--spacing) * 1);
}

:is([data-label-value], [data-grid]) .max-w-2xl {
  max-width: none;
}

#columns-board table {
  width: min(100%, 72rem);
}

/* The column in the file holds a chip and a note, so it is the widest. */
#columns-board col[data-file-column] {
  width: 40%;
}

/* A column of the file fits in a row of the board. Unfold compiles no
 * `py-0.5`. */
[data-chip] {
  padding: calc(var(--spacing) * 0.5) calc(var(--spacing) * 2) calc(var(--spacing) * 0.5) calc(var(--spacing) * 0.5);
}

/* The columns no pair uses stay in view while the pairs scroll, so a drag
 * from them is short. The tray takes the page's colour, so the rows do not
 * show through it. Its legend sits half above the border, outside the
 * background, so the shadow fills that strip too. */
#columns-board [data-tray] {
  background-color: var(--color-base-100);
  border: 1px dashed var(--color-base-300);
  box-shadow: 0 -0.5rem 0 var(--color-base-100);
  position: sticky;
  top: 0;
  z-index: 10;
}

.dark #columns-board [data-tray] {
  background-color: var(--color-base-950);
  border-color: var(--color-base-600);
  box-shadow: 0 -0.5rem 0 var(--color-base-950);
}

/* The Config Tweaker has no cards, so its boxes and grids sit straight on the
 * page, in the page's own colour. Their borders are one step stronger than
 * unfold's, so they stand apart from the page.
 *
 * The rules are in unfold's utilities layer, so a variant such as a field's red
 * error border still wins. An unlayered rule would beat it. This file loads
 * before unfold's, so the statement gives unfold's layer order. Without it,
 * `utilities` would be the first layer and lose to unfold's base styles. The
 * `html` makes each rule beat its unfold class, which comes later in the layer,
 * but not a class with a variant. */
@layer properties, theme, base, components, utilities;

@layer utilities {
  html .border-base-200:where(#config-tweaker *) {
    border-color: var(--color-base-300);
  }

  html .dark\:border-base-700:where(.dark #config-tweaker *) {
    border-color: var(--color-base-600);
  }

  html .dark\:border-base-800:where(.dark #config-tweaker *) {
    border-color: var(--color-base-700);
  }
}

/* The split between the editor and the previews is one line, centred in a
 * strip wide enough to grab. Each pane's scroll bar is thin, so it costs
 * little width. A browser with classic scroll bars keeps it in view. Firefox
 * on Windows 11 hides it until the pointer moves, and no CSS changes that. */
[data-split-handle] {
  background: linear-gradient(var(--color-base-300), var(--color-base-300)) center / 1px 100% no-repeat;
  width: calc(var(--spacing) * 3);
}

.dark [data-split-handle] {
  background-image: linear-gradient(var(--color-base-600), var(--color-base-600));
}

[data-split-handle]:hover {
  background-image: linear-gradient(var(--color-primary-600), var(--color-primary-600));
  background-size: 3px 100%;
}

#panes > :is(#CONFIG_EDITOR-group, #FILE_PREVIEWS-group) {
  scrollbar-color: var(--color-base-400) transparent;
  scrollbar-width: thin;
}

/* The Config Tweaker's page is tinted, and its editable boxes keep unfold's
 * white or base-900, so they stand up from it. A surface that cannot be
 * edited takes the tint: the open tab, which joins the page under it, and the
 * sticky footer. A dashed "New" box and a disabled box are see-through, so
 * neither reads as a value. The box rules are in the utilities layer for the
 * reason given above. */
#main:has(#config-tweaker),
#config-tweaker [role="tablist"] > .active {
  background-color: var(--color-base-100);
}

.dark #main:has(#config-tweaker),
.dark #config-tweaker [role="tablist"] > .active {
  background-color: var(--color-base-950);
}

/* The tab bars. They sit where nobody expects tabs, so each item is drawn as a
 * folder tab: rounded top corners, a border on three sides, and a darker tint
 * while it is shut. The bar's own lower border is the line the tabs stand on.
 * The open tab has the page's colour, also on its bottom border, so it covers
 * the line and joins the page under it. Unfold compiled no `gap-1.5`, so the
 * gap between a pip and its label is here. */
#config-tweaker [role="tablist"] {
  column-gap: calc(var(--spacing) * 1);
  padding-left: calc(var(--spacing) * 2);
}

#config-tweaker [role="tablist"] > * {
  align-items: center;
  background-color: var(--color-base-200);
  border: 1px solid var(--color-base-300);
  border-radius: var(--border-radius, 6px) var(--border-radius, 6px) 0 0;
  color: var(--color-base-500);
  display: flex;
  font-weight: 500;
  gap: calc(var(--spacing) * 1.5);
  margin-bottom: -1px;
  padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 3.5);
  white-space: nowrap;
}

#config-tweaker [role="tablist"] > :not(:disabled):hover {
  color: var(--color-primary-600);
}

#config-tweaker [role="tablist"] > .active {
  border-bottom-color: var(--color-base-100);
  color: var(--color-primary-600);
}

.dark #config-tweaker [role="tablist"] > * {
  background-color: var(--color-base-900);
  border-color: var(--color-base-700);
  color: var(--color-base-400);
}

.dark #config-tweaker [role="tablist"] > :not(:disabled):hover {
  color: var(--color-primary-500);
}

.dark #config-tweaker [role="tablist"] > .active {
  background-color: var(--color-base-950);
  border-bottom-color: var(--color-base-950);
  color: var(--color-primary-500);
}

@layer utilities {
  html input.border-dashed:where(#config-tweaker *),
  html input.bg-white:disabled:where(#config-tweaker *) {
    background-color: transparent;
  }
}

/* Django puts a time zone warning beside each date and time box. Unfold lays it over
 * the box, against the nearest positioned ancestor. On constance's settings page, a
 * date or time box sits straight in a table cell, so that ancestor is the table. The
 * icon then lands at the end of the row, and its hover text covers the row. There,
 * the warning is a line of text above the box. A split date and time keeps unfold's
 * own placement, because its wrapper is positioned. */
#changelist-form td > .timezonewarning {
  position: static;
  font-size: var(--text-xs);
  line-height: var(--text-xs--line-height);
  margin-bottom: calc(var(--spacing) * 1);
}

#changelist-form td > .timezonewarning::before {
  display: none;
}
