/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

html, body {
  margin: 0;
  padding: 0;
  overflow-x: hidden; /* optional, prevents horizontal scroll */
}

/* ── The dock ───────────────────────────────────────────────────────────
 * Three names, one mechanism: `shared/_dock` is the fixed wrapper, #bottom-bar
 * is the stack of full-width rows inside it (today just the music player), and
 * --dock-h is how tall that stack currently is.
 *
 * Declare each row's height *here, once*; anything else anchored to the bottom
 * derives from --dock-h instead of restating the number. See docs/patterns/ui.md.
 *
 * Defaults, overrides and the total all live on `body` deliberately. var()
 * substitutes per element, so a default on :root with the override here would
 * resolve --dock-h against :root's zero and inherit down as a constant 0 —
 * silently reserving nothing. Same element, no trap.
 *
 * The padding clears the stack for in-flow content, so footer actions (Delete /
 * Update / Back) aren't covered. On `body` rather than `main` for a second
 * reason: the admin layout's main carries a `py-6` utility, and a class
 * selector outranks a bare element one — the padding would silently lose. */
body {
  --player-h: 0rem;
  --dock-h: var(--player-h);
  padding-bottom: var(--dock-h);
}

body:has(#music-player-bar:not(.hidden)) { --player-h: 6rem; }

/* Safe-area (notch / gesture bar) support — viewport-fit=cover is set in the
 * head, so whatever touches the bottom edge must clear the home indicator
 * itself. env() resolves to 0 on devices without an inset, leaving desktop
 * untouched. `body` prefix outranks the bottom-* utility so cascade order
 * between stylesheets can't flip the result.
 *
 * The inset rides the bottom-most *visible* row, not the #bottom-bar wrapper:
 * the wrapper is 0-tall while the player is hidden, and padding on it would
 * leave an invisible full-width strip over the gesture area swallowing taps on
 * every page. A row added below the player takes the inset over and this drops
 * back to a bare 0.5rem — only one row may carry it, or the gesture bar gets
 * counted twice. (Overlays that escape the bar — the queue drawer and its
 * backdrop — are `fixed` in their own right and handle their own inset below.)
 *
 * The ID selector outranks the bar's own `py-2`, so the 0.5rem base must be
 * restated here — a bare env() would zero the bottom padding wherever the
 * inset is 0 (i.e. every desktop). */
#music-player-bar {
  padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));
}

[data-music-player-target="queue"] {
  padding-bottom: env(safe-area-inset-bottom);
}

/* Fixed elements ignore the body padding above, so they clear the stack
 * themselves. --dock-h collapses to 0 when the dock is empty, which leaves all
 * three at exactly their pre-dock resting position. */
body [data-bulk-select-target="actionBar"] {
  bottom: calc(1rem + var(--dock-h) + env(safe-area-inset-bottom));
}

/* The mobile "+" FAB: above the gesture inset, and above the dock. */
#new-fab {
  bottom: calc(1.875rem + var(--dock-h) + env(safe-area-inset-bottom));
}

/* Flash toasts, same deal — they sat at a bare bottom-4 and rendered over the
 * player whenever a track was loaded. */
body #flash-dispenser {
  bottom: calc(1rem + var(--dock-h) + env(safe-area-inset-bottom));
}

/* The development overlay's panel (superuser + workbench only) sits in the
 * same corner as the toasts and is fixed for the same reason, so it clears
 * the dock and the gesture bar the same way. */
body #development-overlay-panel {
  bottom: calc(1rem + var(--dock-h) + env(safe-area-inset-bottom));
}

/* ── Lexxy, the rich-text editor ─────────────────────────────────────────
 * Lexxy paints from its own custom properties, which lexxy-variables.css
 * declares on :root with a fixed light palette. Re-point them at the DaisyUI
 * theme tokens — this sheet comes after that one (the head links "lexxy"
 * before :app), so a tie on :root goes to these — and both solarized themes
 * drive the editor and the rendered `.lexxy-content` alike. `data-theme` sits
 * on <html>, which is :root, so every var() here resolves per theme. */
:root {
  --lexxy-color-ink: var(--color-base-content);
  --lexxy-color-ink-medium: color-mix(in oklab, var(--color-base-content) 75%, var(--color-base-100));
  --lexxy-color-ink-light: color-mix(in oklab, var(--color-base-content) 50%, var(--color-base-100));
  --lexxy-color-ink-lighter: color-mix(in oklab, var(--color-base-content) 20%, transparent);
  --lexxy-color-ink-lightest: var(--color-base-300);
  --lexxy-color-ink-inverted: var(--color-base-100);
  --lexxy-color-canvas: var(--color-base-200);
  --lexxy-color-accent-dark: var(--color-primary);
  --lexxy-color-accent-medium: color-mix(in oklab, var(--color-primary) 70%, var(--color-base-100));
  --lexxy-color-accent-light: color-mix(in oklab, var(--color-primary) 25%, var(--color-base-100));
  --lexxy-color-accent-lightest: color-mix(in oklab, var(--color-primary) 12%, var(--color-base-100));
  --lexxy-color-red: var(--color-error);
  --lexxy-color-green: var(--color-success);
  --lexxy-color-blue: var(--color-info);
  --lexxy-color-purple: var(--color-secondary);
  --lexxy-color-code-token-att: var(--color-error);
  --lexxy-color-code-token-comment: var(--lexxy-color-ink-light);
  --lexxy-color-code-token-function: var(--color-accent);
  --lexxy-color-code-token-operator: var(--color-error);
  --lexxy-color-code-token-property: var(--color-info);
  --lexxy-color-code-token-punctuation: var(--lexxy-color-ink-medium);
  --lexxy-color-code-token-selector: var(--color-success);
  --lexxy-color-code-token-variable: var(--color-warning);
  --lexxy-radius: var(--radius-field);
  --lexxy-focus-ring-color: var(--color-primary);
  --lexxy-shadow: 0 2px 8px oklch(0% 0 0 / 0.15);
}

/* Focus reads like every other field — DaisyUI darkens a field's border on
 * :focus-within and the app keeps the ring off — so no ring here either. The
 * ring the global :focus-visible rule would put on the contenteditable is
 * suppressed: browsers count it as text entry and match on a mouse click, and
 * an offset outline inside the editor box draws as a stray rule along the
 * toolbar seam, not as a ring. Lexxy zeroes it itself, but at :where()
 * specificity, which the global rule outranks. */
lexxy-editor:focus-within {
  border-color: var(--color-base-content);
}

.lexxy-editor__content:focus-visible {
  outline: none;
}

/* An @-mention (mentions/_mention) is an inline Action Text attachment, and
 * Lexxy's sheets already lay the chip out — inline-flex, a round image slot,
 * the selected state in the editor — off the `vnd.actiontext` prefix. This is
 * the tint on top, and the link inside taken out of `.prose`'s underline.
 * `:not(.node--selected)` leaves Lexxy's selection colour in charge; without
 * it this rule, later and equally specific, would paint over it. */
.lexxy-content action-text-attachment[content-type="application/vnd.actiontext.mention"]:not(.node--selected),
.lexxy-content action-text-attachment[content-type="application/vnd.actiontext.tag"]:not(.node--selected) {
  --lexxy-attachment-bg-color: color-mix(in oklab, var(--color-primary) 12%, transparent);
  padding-inline: 0.35em;
}

.lexxy-content .mention,
.lexxy-content .hashtag {
  color: var(--color-primary);
  font-weight: 500;
  text-decoration: none;
}

/* The @ prompt's popover lists a display name and a handle per row
 * (mentions/_prompt_item); Lexxy caps the menu at 20ch, which truncates
 * "Ana Maria @ana_maria" mid-handle. Its rules are `:where()`-wrapped, so a
 * plain class selector is enough. Still bounded by the editor's width — the
 * clipped-at-right branch keeps the menu inside the box. */
.lexxy-prompt-menu {
  max-inline-size: min(34ch, calc(100% - var(--lexxy-prompt-offset-x)));
}

/* No text colouring (application.js empties the palette): the swatch dropdown
 * goes too. Its output is inline `style=`, which `style-src-attr 'none'`
 * (content_security_policy.rb) would strip on render. */
lexxy-toolbar .lexxy-editor__toolbar-dropdown--highlight {
  display: none;
}

/* Touch adjustments, in one block so the condition exists once.
 *
 * 16px floor on every text-entry control — anything smaller makes iOS
 * Safari auto-zoom the page on focus. Coarse pointers only: the zoom is a
 * touch-browser behaviour, and on desktop the floor was inflating xs/sm
 * controls (a 16px font inside DaisyUI's 24px input-xs) for no benefit.
 * The DaisyUI field classes set font-size: .875rem at class specificity,
 * which outranks the bare element selectors — the floor must restate them
 * as compound selectors (input.input beats .input) or it silently loses on
 * every DaisyUI-classed field. lexxy-editor (a custom element around a
 * contenteditable, not a textarea) and the tagger's dynamically created
 * type-less input need naming explicitly. Lexxy's toolbar buttons are 2lh
 * squares already — finger-sized without a rule here. */
@media (pointer: coarse) {
  input[type="text"],
  input[type="search"],
  input[type="email"],
  input[type="password"],
  input[type="url"],
  input[type="number"],
  textarea,
  input.input,
  textarea.textarea,
  select.select,
  lexxy-editor,
  .tagger input {
    font-size: 16px;
  }
}

/* Turbo drives View Transitions (meta[name="view-transition"] in the
 * head). Keep the cross-fade fast so navigation feels snappy rather than
 * animated, and drop it entirely for reduced-motion users. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 150ms;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

/* Visible keyboard focus everywhere, tokened to the theme so it holds
 * in both solarized variants. :focus-visible only — mouse clicks don't
 * get the ring. */
:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Reduced motion: collapse every animation/transition to a single
 * frame. The View Transition rule above handles navigation; this
 * catches the rest (spinner, skeleton pulse, flash slide, queue-drawer
 * slide) without needing per-component opt-outs. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Global-search toggle: ring the search input while it's searching the whole
 * library. `outline` (not box-shadow) so it doesn't fight the input's shadow
 * utility. Paired with the highlighted pill — see filter_controller#toggleScope. */
.is-global-search {
  outline: 2px solid var(--color-warning, #b58900);
  outline-offset: 2px;
  border-radius: 9999px;
}

.github-signin-container {
  background-color: #000;
  transition: background-color 0.3s ease;
  border-radius: 6px;
  border: none;
}

.github-signin-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #000;
  color: #fff;
  font-size: 16px;
  font-weight: 600;
  padding: 10px 20px;
  border: 2px solid #1b1f23;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.3s ease;
  width: 100%;
}

.github-signin-btn .github-icon {
  width: 64px;
  margin-right: 8px;
}

.github-signin-btn span {
  font-family: Arial, sans-serif;
  font-size: 16px;
}

.animated-border {
    stroke: var(--color-base-content);
    stroke-dasharray: 711.5;
    stroke-dashoffset: -711.5;
    stroke-width: 3;
    transition: stroke-dashoffset 1s ease-in-out;

    transform: rotate(-270deg);
    transform-origin: 50% 50%;
    stroke-linecap: round;
}

.yin-yang-container {
  width: 100%;
  height: auto
}

.yin-yang-container:hover .animated-border {
    stroke-dashoffset: 0;
}

/* #theme-toggler */
#theme-toggler::before {
  background-color: var(--color-secondary);
}
/* #theme-toggler */

/* Comment composer
 *
 * Lexxy ships a full formatting toolbar. On a comment box that is most of the
 * chrome on screen before anyone has typed a word, so it stays collapsed until
 * the editor is reached for. The editor becomes a two-row grid — the toolbar
 * Lexxy prepends, then the contenteditable — and the toolbar's row goes from
 * 0fr to 1fr (not height/max-height), so it opens to its real height.
 *
 * The clip that makes the collapse work sits on the toolbar and only while it
 * is collapsed: its dropdowns (link, formatting) are absolutely positioned
 * children hanging below the buttons, and an always-on overflow: hidden would
 * swallow them — the link button would read as dead because pressing it shows
 * nothing. Padding and border collapse with the row, or a 5px seam survives.
 *
 * minmax(0, 1fr) for the one column, not the default auto: an auto column is
 * never narrower than its content's min-content, and the toolbar's is its
 * whole button row, so it would run past the box's edge and never fold — the
 * fold only happens once `max-inline-size: 100%` can actually cap it.
 */
.comment-composer lexxy-editor {
  --lexxy-editor-rows: 2lh;
  border: 0;
  border-radius: inherit;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 0fr auto;
  transition: grid-template-rows 0.2s ease;
}

.comment-composer:focus-within lexxy-editor {
  grid-template-rows: 1fr auto;
}

.comment-composer lexxy-toolbar {
  min-height: 0;
  opacity: 0;
  transition: opacity 0.2s ease, padding 0.2s ease, border-width 0.2s ease;
}

.comment-composer:not(:focus-within) lexxy-toolbar {
  border-block-end-width: 0;
  overflow: hidden;
  padding-block: 0;
}

.comment-composer:focus-within lexxy-toolbar {
  opacity: 1;
}

/* Ring the composer, not the editor: the lexxy-editor:focus-within treatment
 * above darkens a border this one has switched off, and the composer's own
 * outline is the signal that the toolbar is open. */
.comment-composer:focus-within {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* The toolbar stays in the tab order on purpose: :focus-within opens the
 * composer, so tabbing into a collapsed button reveals it rather than moving
 * focus somewhere invisible.
 */
@media (prefers-reduced-motion: reduce) {
  .comment-composer lexxy-editor,
  .comment-composer lexxy-toolbar {
    transition: none;
  }
}

/* ── The page-flow diagram ──────────────────────────────────────────────
 * /admin/development/flow, drawn as SVG by flow_map_controller. Everything
 * here is expressed in DaisyUI's theme variables rather than literal colours,
 * so the map follows the solarized-light/solarized-dark toggle like the rest
 * of the app — an SVG with baked-in hex values is the classic way to end up
 * with a diagram that is unreadable in one theme and nobody notices.
 */
.flow-canvas {
  touch-action: none;
  cursor: grab;
  user-select: none;
}

.flow-canvas:active {
  cursor: grabbing;
}

.flow-node {
  cursor: pointer;
}

.flow-node__box {
  fill: var(--color-base-100);
  stroke: var(--color-base-300);
  stroke-width: 1;
}

.flow-node__accent {
  fill: var(--color-base-300);
}

.flow-node__title {
  font: 600 11px ui-monospace, SFMono-Regular, Menlo, monospace;
  fill: var(--color-base-content);
}

.flow-node__path {
  font: 400 10px ui-monospace, SFMono-Regular, Menlo, monospace;
  fill: var(--color-base-content);
  opacity: 0.55;
}

/* Section accents. The left rail is the only thing carrying section identity,
 * deliberately — colouring the whole box turns a 90-node map into a swatch
 * chart and makes the selection state impossible to see. */
.flow-node[data-section="admin"] .flow-node__accent { fill: var(--color-warning); }
.flow-node[data-section="public"] .flow-node__accent { fill: var(--color-success); }
.flow-node[data-section="auth"] .flow-node__accent { fill: var(--color-info); }
.flow-node[data-section="development"] .flow-node__accent { fill: var(--color-error); }
.flow-node[data-section="chat"] .flow-node__accent,
.flow-node[data-section="map"] .flow-node__accent,
.flow-node[data-section="billing"] .flow-node__accent { fill: var(--color-secondary); }

.flow-node.is-selected .flow-node__box {
  stroke: var(--color-primary);
  stroke-width: 2;
  fill: color-mix(in oklab, var(--color-primary) 14%, var(--color-base-100));
}

.flow-node.is-dimmed { opacity: 0.2; }
.flow-node.is-hidden,
.flow-edge.is-hidden { display: none; }

/* Hidden until a note exists for the node; flow_map_controller flips the class
 * from whatever is currently in the note list. */
.flow-node__badge { display: none; }
.flow-node.has-notes .flow-node__badge { display: block; }
.flow-node__badge circle { fill: var(--color-warning); }

.flow-node__badge-count {
  font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace;
  fill: var(--color-base-100);
  text-anchor: middle;
}

.flow-edge {
  fill: none;
  stroke: var(--color-base-content);
  opacity: 0.16;
}

.flow-edge.is-active {
  stroke: var(--color-primary);
  opacity: 0.75;
}

/* ── Flash nudge ────────────────────────────────────────────────────────
 * `.flash-offset` was a class name with no rule behind it anywhere in the
 * app — the nudge lived entirely in a `style=` attribute on the same
 * element, so the class was decoration and the attribute was the feature.
 * The rule now says what the name always claimed. */
.flash-offset {
  transform: translate(-1.25%, 0);
}

/* ── Item icon tints ────────────────────────────────────────────────────
 * One rule per `Item::ICON_COLORS` entry. The palette is a closed set —
 * `validates :icon_color, inclusion: { in: ICON_COLORS }` — so an item's
 * colour is a *class*, not a value that has to be computed into a `style=`
 * attribute the CSP then has to allow. `ItemsHelper#item_tint_class` builds
 * the name; `test/view_styling_test.rb` pairs this block to the palette in
 * both directions, so neither a colour without a rule nor a rule without a
 * colour survives.
 *
 * Only `color` is set, deliberately: the picker's swatch wants the same
 * colour as a background and gets it with Tailwind's `bg-current`, so the
 * palette is stated once rather than once per property. */
.icon-tint-e11d48 { color: #e11d48; }
.icon-tint-f59e0b { color: #f59e0b; }
.icon-tint-f5c518 { color: #f5c518; }
.icon-tint-10b981 { color: #10b981; }
.icon-tint-0ea5e9 { color: #0ea5e9; }
.icon-tint-6366f1 { color: #6366f1; }
.icon-tint-8b5cf6 { color: #8b5cf6; }
.icon-tint-ec4899 { color: #ec4899; }
.icon-tint-64748b { color: #64748b; }

/* Zoomed out past the point where 10px type means anything. Dropping the text
 * is also what keeps a full-map zoom from painting several hundred glyph runs
 * on every pan frame. */
.flow-canvas.is-far .flow-node__title,
.flow-canvas.is-far .flow-node__path { display: none; }
