/* Small app-scoped styles. Everything else comes from tailwind.css.
 *
 * We keep this file alive (rather than collapsing into input.css) so future
 * one-off tweaks (e.g. dt-prefers-mono-display, dt-paywall-overlay) have a
 * landing spot that doesn't force a CSS rebuild.
 */

/* Alpine x-cloak - hide pre-hydration content. We include this explicitly so
 * the first paint doesn't flash unhydrated UI. */
[x-cloak] {
  display: none !important;
}

/* Rail fallbacks: elements that exist only to stand in for the right rail on
 * the viewports too narrow to have one (today, the chat promo banners in
 * main). They must disappear at exactly the width the rail appears at, or the
 * same promo runs twice on one screen.
 *
 * Written here rather than left to the `xl:hidden` utility the markup also
 * carries. tailwind.css is generated and git-ignored, and `xl:hidden` is used
 * by nothing else in the app: a tailwind.css built before the banners existed
 * has the rail's `xl:flex` but no `.xl\:hidden` at all, so the banner stays up
 * beside the rail until someone re-runs `bun run css`. index.css ships exactly
 * as written, so this rule cannot go missing. The breakpoint is Tailwind's
 * `xl` (80rem), the same one the rail's own `hidden xl:flex` uses - keep the
 * two in step. */
@media (width >= 80rem) {
  .dt-rail-fallback {
    display: none;
  }
}

/* Overlap both asks so auth hydration cannot change the banner height. */
.dt-chat-ad-asks {
  display: grid;
}
.dt-chat-ad-asks > div {
  grid-area: 1 / 1;
}

/* Horizontal-overflow guard.
 *
 * A single element wider than the viewport doesn't just clip itself - it grows
 * the page's scroll box, and every `position: fixed` overlay sizes off that
 * instead of the viewport. That is how an overflowing topbar ended up
 * pushing the first-run onboarding modal half off-screen on a phone: the
 * modal was 537px wide inside a 360px window.
 *
 * `clip` rather than `hidden` on purpose - `overflow: hidden` would turn these
 * elements into scroll containers and break the `position: sticky` topbar.
 * This is a backstop, not a licence to ship overflowing layouts: it hides the
 * symptom, so fix the offending element as well.
 * tests/integration/mobileLayout.test.js pins the layout contract. */
html,
body {
  overflow-x: clip;
}

/* Touch targets on phones (docs/frontend-testing.md asks for >= 44px).
 *
 * daisyUI's small button sizes are tuned for a mouse: `btn-sm` is 32px tall and
 * `btn-xs` 24px. That is fine on the desktop topbar, but below `lg` these are
 * the only chrome controls left on screen, so they get a floor without their
 * visual size changing much. Scoped tightly - the pomodoro chip's inline
 * play/pause is nested inside a pill that must stay within the 48px bar, so it
 * gets a smaller floor than the standalone controls. */
@media (width < 64rem) {
  .dt-topbar .btn-circle:not(.btn-xs),
  .dt-topbar .dropdown > .btn {
    min-height: 2.75rem;
    min-width: 2.75rem;
  }
  .dt-topbar .btn-xs.btn-circle {
    min-height: 2rem;
    min-width: 2rem;
  }
  /* Rows and segmented controls inside the overflow menu. The floor goes on
     the row itself, not its children - stretching an inline icon to 44px
     drags it off the text baseline. */
  .dt-touch-menu li > * {
    min-height: 2.75rem;
    align-items: center;
  }
  .dt-touch-segment .btn {
    min-height: 2.5rem;
  }
}

/* Scoreboard rows (home Today/Overall/Streak, profile Streak/Challenges/
 * Perfect rounds). Three stats across is the right shape for the data - it
 * reads as one instrument panel - but daisyUI's stat padding (1.5rem an edge)
 * plus its nowrap captions needs ~150px a column, so the third stat used to
 * run off the side of a phone. Below `sm` tighten the padding and let the
 * captions wrap rather than dropping to a stacked layout, which would push the
 * quick-challenge grid below the fold. */
@media (width < 40rem) {
  .dt-stats-compact .stat {
    padding-inline: 0.7rem;
    padding-block: 0.75rem;
  }
  .dt-stats-compact .stat-title,
  .dt-stats-compact .stat-desc {
    white-space: normal;
    font-size: 0.6875rem;
    line-height: 1.2;
  }
  .dt-stats-compact .stat-value {
    font-size: 1.5rem;
  }
}

/* Slightly tighter spacing in the daisyUI bottom-nav labels. */
.btm-nav .btm-nav-label {
  font-size: 0.65rem;
  line-height: 1;
  margin-top: 0.15rem;
}

/* Highlighted separable-prefix pipe in conjugation prompts ("weiter|gehen").
 * Uses the same daisyUI warning token as the Streak counter (text-warning), so
 * it tracks the active theme automatically. */
.verb-split {
  color: var(--color-warning);
  font-weight: 700;
}

/* Tappable words inside a challenge prompt: the noun, and the tested adjective.
 * Both open their word-detail modal, so
 * they only hint at being tappable - anything louder competes with the
 * underlined answer blank they sit next to. */
.word-link,
.cue-link {
  cursor: pointer;
}
.word-link:hover,
.cue-link:hover {
  color: var(--color-primary);
}

/* The tested word or article stem is boxed, so it reads as "this is the one to form".
 * Square box hugging the text, with the breathing room on the outside so
 * the box does not crowd the words next to it. inline-block keeps the
 * ancestor <u> from underlining it. */
.cue-link,
.article-cue {
  display: inline-block;
  font-style: normal;
  padding: 0 0.2em;
  margin: 0 0.4em;
  border: 1px solid color-mix(in oklab, var(--color-base-content) 35%, transparent);
}

.article-result-success .article-cue {
  color: var(--color-success);
}

.article-result-error .article-cue {
  color: var(--color-error);
}

/* A vulgar Meaning, shown only to learners who opted into 18+ content. */
.meaning-vulgar {
  background: color-mix(in oklab, var(--color-error) 12%, transparent);
  border-radius: 0.25rem;
  padding: 0 0.25em;
}

/* The tint alone vanishes on a solid success/error choice button, so the
 * dashed underline (current text colour) carries the marker there. */
[data-testid="challenge-option"] .meaning-vulgar {
  text-decoration: underline dashed;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* ─────────────────────────────────────────────────────────────────────────
 * "Rotlicht" identity layer (design plan res/design/v2).
 * Barlow (UI) + Barlow Condensed (display) come from the head. Condensed
 * carries the display voice - headings, big numbers - like German signage;
 * Barlow handles body. Brand red #B01E28 + amber live in the theme tokens.
 * ───────────────────────────────────────────────────────────────────────── */
:root {
  --font-display: "Barlow Condensed", "Barlow", ui-sans-serif, system-ui, sans-serif;
}

/* The design has four surface levels (topbar < bg < panel < card in dark);
 * daisyUI only has three base tokens, so topbar + panel get their own vars. */
[data-theme="dark"] {
  --dt-topbar: #141518;
  --dt-panel: #1F2024;
}
[data-theme="light"] {
  --dt-topbar: #FFFFFF;
  --dt-panel: #F1F2F3;
}
.dt-topbar {
  background: var(--dt-topbar);
}
.dt-panel {
  background: var(--dt-panel);
}

/* Headings render in condensed display type, drawn tight like signage. */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.005em;
}

/* Section eyebrows / labels: condensed, widely tracked - instrument-panel
 * captions. All the app's small caps labels share `uppercase tracking-wider`. */
.uppercase.tracking-wider {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.12em;
}

/* Big data figures (streak, XP, points) share the condensed display voice so
 * they read as panel numerals rather than body text. */
.stat-value,
.dt-num {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.01em;
}

/* ─────────────────────────────────────────────────────────────────────────
 * Profile streak activity calendar. A contribution-style grid: one column per
 * week (oldest on the left), seven weekday rows, filled cells marking the days
 * the user hit their daily goal. Colours come from daisyUI theme tokens so the
 * grid tracks light/dark like the rest of the app; the warning token matches
 * the Streak counter's flame.
 * ───────────────────────────────────────────────────────────────────────── */
.dt-activity-grid {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(7, 0.75rem);
  grid-auto-columns: 0.75rem;
  gap: 3px;
  width: max-content;
}
.dt-activity-cell {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 2px;
  background: var(--color-base-300);
}
.dt-activity-on {
  background: var(--color-warning);
}
.dt-activity-today {
  outline: 2px solid var(--color-warning);
  outline-offset: 1px;
}
.dt-activity-empty {
  background: transparent;
}

/* Home hero 7-day strip: the same goal-met days as the calendar above, as
 * dots. Today is ringed, and half-filled while it has points but no goal. */
.dt-week-dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 9999px;
  border: 2px solid var(--color-base-300);
}
.dt-week-on {
  background: var(--color-warning);
  border-color: var(--color-warning);
}
.dt-week-progress {
  border-color: var(--color-warning);
  background: linear-gradient(to top, var(--color-warning) 50%, transparent 50%);
}
.dt-week-today {
  outline: 2px solid var(--color-warning);
  outline-offset: 1px;
}

/* Flat, bordered surfaces: every daisyUI card gets a hairline border since
 * depth:0 removes the shadow. This file is unlayered and loads after
 * tailwind.css, so a bare `.card` here beats layered utilities like
 * `border-primary/25` regardless of source order; scope it to cards that
 * don't declare their own `border-*` utility so those overrides win. */
.card:not([class*="border-"]) {
  border: 1px solid var(--color-base-300);
}

/* Closed dropdown panels must not swallow taps.
 *
 * daisyUI hides a closed `.dropdown-content` with `display: none`, but that
 * declaration sits in daisyUI's nested cascade layer, so any plain Tailwind
 * display utility on the same element (`flex`, `grid`, `block`, ...) outranks
 * it. The panel then stays laid out at `opacity: 0`: invisible, yet still the
 * topmost hit-test target over whatever is beneath it. That is how the mobile
 * overflow menu came to cover the top-right of every screen - tapping a
 * challenge card there focused the dropdown and opened the account menu
 * instead of starting the challenge.
 *
 * The markup rule is to keep display utilities off `.dropdown-content` (use
 * `space-y-*`, or wrap the children in a flex div). This is the backstop:
 * unlayered, so it beats both daisyUI and the utilities, and it matches only
 * the closed state, leaving the open panel and its fade-out untouched. */
.dropdown:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within)
  .dropdown-content {
  pointer-events: none;
}

/* Same trap, worse blast radius: closed checkbox modals.
 *
 * The notifications sheet and the notification-preferences sheet live in the
 * DOM at all times, driven by a `.modal-toggle` checkbox. A closed `.modal`
 * is a `position: fixed; inset: 0` box holding a full-viewport
 * `label.modal-backdrop`, and daisyUI keeps it out of the way with
 * `visibility: hidden` + `pointer-events: none` from inside its cascade
 * layer. Any plain utility that touches those two properties therefore wins,
 * and the failure is not a dead zone in one corner: the whole screen becomes
 * a label that toggles the sheet, so every tap anywhere opens (or closes)
 * Notifications instead of doing what the user aimed at.
 *
 * Unlayered, and matching only the unchecked state, so it restates daisyUI's
 * own closed values without touching the open sheet or its fade-out (the
 * transition still comes from daisyUI's rule). */
.modal-toggle:not(:checked) + .modal {
  visibility: hidden;
  pointer-events: none;
}
