/* ===========================================================================
   @lorehub/web — THE APP'S OWN STYLESHEET.

   Served at `/assets/web.css` (see `mountAssets`), linked by both document
   roots AFTER `/assets/lorehub.css`, so it can lean on the design system's
   tokens and, where it must, out-order a design-system rule.

   WHAT BELONGS HERE: styling for markup that lives in apps/web and has no
   design-system component behind it — a page layout, a one-off composition.
   WHAT DOES NOT: anything styling a `@lorehub/design-system` component. That
   is the design system's job and belongs in its stylesheet next to the
   component, or the component and its CSS drift apart.

   This file exists because the alternative was worse. apps/web shipped no CSS
   of its own, so page-specific rules had nowhere to go but the design system's
   `lorehub.css` — a file whose own header calls it generated from the Claude
   Design project, i.e. a file a refresh could legitimately overwrite. A
   comment warning people not to do that is weaker than simply not putting the
   app's CSS in a regenerable file.

   Everything here is class-first rather than inline-styled: apps/web runs an
   inline-style ratchet (`bun run check:inline-styles`) whose per-file counts
   are a no-growth ceiling, and pages this dense would blow through it.
   ========================================================================= */

/* ===========================================================================
   AUTH PAGES — the signed-out front door (`/login` + `/signup`), built to the
   "Upstream" concept: a full-bleed split, brand panel left, form column right.
   The two pages share every part defined here; what differs between them is
   which parts each renders, never how a part looks.
   ========================================================================= */

.lh-auth {
  display: flex;
  min-height: 100dvh;
  background: var(--bg-app);
}

/* Brand panel ------------------------------------------------------------- */
.lh-auth__brand {
  position: relative;
  flex: none;
  width: 452px;
  overflow: hidden;
  background: linear-gradient(155deg, var(--ink-900) 0%, var(--ink-1000) 68%);
  border-right: 1px solid var(--border-subtle);
}
/* The stream-motif backdrop. Absolutely positioned and aria-hidden: it is
   decoration, and nothing in the panel depends on it for meaning. */
.lh-auth__backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.9;
  pointer-events: none;
}
.lh-auth__brand-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-10);
  height: 100%;
  padding: var(--space-10);
}
.lh-auth__logo {
  display: inline-flex;
  align-self: flex-start;
}
.lh-auth__headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tighter);
  color: var(--text-primary);
  text-wrap: balance;
}
.lh-auth__lede {
  max-width: 34ch;
  margin: var(--space-3) 0 0;
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
}

/* The one piece of product truth on the panel. Illustrative, not live data,
   and worded without a person's name so it reads as an example of what the
   product does rather than as a feed of somebody's real activity. */
.lh-auth__proof {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 13px 15px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface-overlay);
  backdrop-filter: var(--blur-md);
  box-shadow: var(--shadow-md), var(--edge-light);
}
.lh-auth__proof-icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-pill);
  background: var(--teal-tint);
  color: var(--teal-400);
}
.lh-auth__proof-body {
  flex: 1;
  min-width: 0;
}
.lh-auth__proof-line {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.lh-auth__proof-line strong {
  color: var(--text-primary);
}
.lh-auth__proof-meta {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
}

/* Form column ------------------------------------------------------------- */
.lh-auth__col {
  position: relative;
  display: flex;
  flex: 1;
  min-width: 0;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-16);
}
.lh-auth__panel {
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
}
/* The sign-in to create-account cross-link. Absolute at desktop width so it
   sits in the top-right corner the concept puts it in; static (and above the
   heading) once the brand panel is gone, where that corner no longer exists. */
.lh-auth__switch {
  position: absolute;
  top: var(--space-8);
  right: var(--space-8);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.lh-auth__switch a {
  font-weight: var(--weight-semibold);
}
.lh-auth__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tighter);
  color: var(--text-primary);
}
.lh-auth__sub {
  margin: var(--space-2) 0 var(--space-6);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--text-muted);
}
.lh-auth__fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Third-party providers --------------------------------------------------- */
/* One registry drives these, so a new method is a data entry rather than a
   layout change: the first provider gets a full-width row and any others
   fold into a compact grid beneath it. */
.lh-au-provs {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.lh-au-provs__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-3);
}
.lh-au-prov {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  height: 46px;
  padding: 0 var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}
.lh-au-prov:hover:not(:disabled) {
  background: var(--surface-hover);
  border-color: var(--border-strong);
}
.lh-au-prov:active:not(:disabled) {
  transform: scale(0.985);
}
.lh-au-provs__grid .lh-au-prov {
  height: 42px;
  font-size: var(--text-sm);
}
/* A provider we have designed for but not yet wired. Rendered inert on
   purpose, so the finished page stays honest about what works today; the
   "soon" tag is what keeps it from reading as a broken button. */
.lh-au-prov:disabled {
  cursor: default;
  background: transparent;
  border-style: dashed;
  color: var(--text-disabled);
}
.lh-au-prov__mark {
  display: inline-flex;
  flex: none;
}
.lh-au-prov:disabled .lh-au-prov__mark {
  opacity: 0.45;
  filter: grayscale(1);
}
.lh-au-prov__soon {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-disabled);
}

.lh-au-rule {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-4);
}
.lh-au-rule::before,
.lh-au-rule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}
.lh-au-rule__label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-disabled);
}

/* Password field ---------------------------------------------------------- */
/* The reveal toggle is server-rendered `hidden` and unhidden by the login
   bundle, so a JS-off visitor never meets a control that cannot work. */
/* The 24px minimum is the target size, not the glyph: `.lh-input` centers its
   children rather than stretching them, so with `padding: 0` this button would
   otherwise collapse to the 16px icon — under WCAG 2.2 §2.5.8. */
.lh-au-pw__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.lh-au-pw__toggle:hover {
  color: var(--text-secondary);
}
/* Both glyphs are server-rendered and CSS picks one, so revealing a password
   never needs the icon layer re-hydrated mid-interaction. */
.lh-au-pw__toggle[data-state="hidden"] [data-when="hide"],
.lh-au-pw__toggle[data-state="shown"] [data-when="show"] {
  display: none;
}

/* Strength meter ---------------------------------------------------------- */
/* Sign-up only. `data-score` is written by the client; absent is the
   untouched state, in which every segment stays neutral. */
.lh-au-meter {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}
.lh-au-meter__bar {
  display: flex;
  flex: 1;
  gap: var(--space-1);
}
.lh-au-meter__seg {
  flex: 1;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--ink-700);
  transition: background var(--dur-fast) var(--ease-out);
}
.lh-au-meter__word {
  width: 66px;
  font-family: var(--font-mono);
  font-size: 11px;
  text-align: right;
  color: var(--text-disabled);
}
.lh-au-meter[data-score="0"] {
  --_meter-tone: var(--red-500);
}
.lh-au-meter[data-score="1"] {
  --_meter-tone: var(--amber-500);
}
.lh-au-meter[data-score="2"] {
  --_meter-tone: var(--teal-500);
}
.lh-au-meter[data-score="3"] {
  --_meter-tone: var(--green-500);
}
.lh-au-meter[data-score="0"] .lh-au-meter__seg:nth-child(-n + 1),
.lh-au-meter[data-score="1"] .lh-au-meter__seg:nth-child(-n + 2),
.lh-au-meter[data-score="2"] .lh-au-meter__seg:nth-child(-n + 3),
.lh-au-meter[data-score="3"] .lh-au-meter__seg {
  background: var(--_meter-tone);
}
.lh-au-meter[data-score="0"] .lh-au-meter__word,
.lh-au-meter[data-score="1"] .lh-au-meter__word {
  color: var(--amber-500);
}
.lh-au-meter[data-score="2"] .lh-au-meter__word,
.lh-au-meter[data-score="3"] .lh-au-meter__word {
  color: var(--text-secondary);
}

/* A quiet link beside a field label ("Forgot password?"), sitting in the design
   system's `labelAside` slot. */
.lh-au-aside {
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  white-space: nowrap;
}

/* The neutral outcome panel on the reset pages. `hidden` until the client
   reveals it, and an author `display` would beat the UA's `[hidden]` rule, so
   this deliberately sets none. */
.lh-au-notice {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in oklab, var(--teal-500) 32%, transparent);
  border-radius: var(--radius-md);
  background: var(--teal-tint);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

.lh-au-legal {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-relaxed);
  color: var(--text-disabled);
}

/* Shared portal controls -------------------------------------------------- */
.lh-confirm {
  display: inline-block;
  max-width: 22rem;
  text-align: left;
}

.lh-confirm__summary {
  color: var(--danger);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}

.lh-confirm__body {
  min-width: 0;
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  border: 1px solid var(--danger);
  border-radius: var(--radius-md);
  background: var(--danger-soft);
}

.lh-confirm__description {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
}

.lh-copy-value-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.lh-copy-value {
  flex: 1;
  min-width: 0;
}

/* Motion ------------------------------------------------------------------ */
.lh-au-flow {
  animation: lh-au-flow 5s linear infinite;
}
/* The backdrop's lanes drift at different speeds so they never lock into one
   marching rhythm. Modifier classes rather than a per-path inline duration:
   the views are held to a class-first budget (see the section header). */
.lh-au-flow--2 {
  animation-duration: 6.6s;
}
.lh-au-flow--3 {
  animation-duration: 8.2s;
}
.lh-au-flow--4 {
  animation-duration: 9.8s;
}
@keyframes lh-au-flow {
  to {
    stroke-dashoffset: -240;
  }
}
.lh-au-pulse {
  transform-origin: center;
  animation: lh-au-pulse 3s var(--ease-in-out) infinite;
}
@keyframes lh-au-pulse {
  0%,
  100% {
    opacity: 0.25;
    transform: scale(1);
  }
  50% {
    opacity: 0.8;
    transform: scale(1.25);
  }
}
@media (prefers-reduced-motion: reduce) {
  .lh-au-flow,
  .lh-au-pulse {
    animation: none;
  }
}

/* Narrow viewports -------------------------------------------------------- */
/* Below the split's comfortable width the brand panel is dropped rather than
   squeezed, and the page becomes the plain centered form the concept's own
   notes call for. The panel carries nothing the form column needs. */
@media (max-width: 900px) {
  .lh-auth__brand {
    display: none;
  }
  .lh-auth__col {
    padding: var(--space-8) var(--space-6);
  }
  .lh-auth__switch {
    position: static;
    justify-content: center;
    margin: 0 auto var(--space-6);
  }
}

/* ===========================================================================
   RICH TEXT — rendered authored prose (`rich-text-views.tsx`).

   These style the closed node union from `@lorehub/rich-text-contract`, which
   both this portal and the desktop client render. The MARKUP is allowed to
   differ between the two clients; what must not differ is which constructs
   exist and how they read, so keep this list in step with the desktop's own
   rich-text block in `lorehub-desktop/src/styles.css`.

   Everything is scoped under `.rt` so authored content cannot restyle the page
   around it — an author controls the tree's SHAPE, and a bare `p`/`table` rule
   here would let that shape reach outside the region it was rendered into.
   ========================================================================= */

.rt {
  color: var(--text-primary);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  /* Authored content is one of the few places on this site where selecting and
     copying text is the point, so it opts out of any ambient suppression. */
  user-select: text;
}

.rt > :first-child {
  margin-top: 0;
}

.rt > :last-child {
  margin-bottom: 0;
}

.rt .rt-p {
  margin: 0 0 var(--space-3);
  /* Authored prose can contain an unbroken identifier or URL far wider than the
     column. Breaking it is better than a horizontal scrollbar on the PAGE — the
     one element allowed its own horizontal scroll is the table below. */
  overflow-wrap: anywhere;
}

/* Headings clamp to the host surface's outline in the renderer, so the level
   here is already correct; these rules only keep authored headings visually
   subordinate to the page's own title rather than competing with it. */
.rt .rt-h {
  margin: var(--space-4) 0 var(--space-2);
  font-weight: 650;
  line-height: var(--leading-snug);
}

.rt h1.rt-h,
.rt h2.rt-h {
  font-size: var(--text-md);
}

.rt h3.rt-h,
.rt h4.rt-h {
  font-size: var(--text-sm);
}

.rt h5.rt-h,
.rt h6.rt-h {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.rt .rt-code,
.rt-subject .rt-code {
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-family: var(--font-mono);
  font-size: 0.92em;
  overflow-wrap: anywhere;
}

.rt .rt-pre {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
  /* A code block keeps its own line breaks and scrolls itself rather than
     wrapping, because wrapped code misreads. */
  overflow-x: auto;
}

.rt .rt-pre code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: pre;
}

.rt .rt-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}

/* A mention is display-only: it must READ as a name, not as something clickable,
   or the affordance lies about what it does. No pointer cursor, no underline. */
.rt .rt-mention,
.rt-subject .rt-mention {
  padding: 0.05em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
  color: var(--text-primary);
  font-weight: 600;
  white-space: nowrap;
}

.rt .rt-image-omitted,
.rt-subject .rt-image-omitted {
  color: var(--text-muted);
  font-style: italic;
}

.rt .rt-list {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
}

/* mdast gives every list item a paragraph child even in a tight list, so without
   this a one-line bullet renders with a paragraph's bottom margin and the list
   reads as loose. */
.rt .rt-list li > .rt-p:only-child {
  margin: 0;
}

.rt .rt-list li {
  margin: 0 0 var(--space-1);
}

/* Read-only task items. The marker is a glyph rather than a disabled checkbox:
   a disabled control would sit in the reading order implying it can be toggled,
   which it cannot — the state is not editable from a reader. */
.rt .rt-task {
  list-style: none;
  margin-left: calc(var(--space-5) * -1);
}

.rt .rt-task > .rt-p:first-of-type {
  display: inline;
}

.rt .rt-task-mark {
  color: var(--text-secondary);
}

.rt .rt-quote {
  margin: 0 0 var(--space-3);
  padding: 0 0 0 var(--space-3);
  border-left: 3px solid var(--border-default);
  color: var(--text-secondary);
}

.rt .rt-rule {
  margin: var(--space-4) 0;
  border: 0;
  border-top: 1px solid var(--border-subtle);
}

/* The table's scroll region is focusable (`tabindex=0` in the renderer) so a
   keyboard-only reader can reach columns past the fold. That is the whole reason
   the wrapper exists; do not collapse it into the table element. */
.rt .rt-table-scroll {
  margin: 0 0 var(--space-3);
  overflow-x: auto;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.rt .rt-table-scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.rt .rt-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-xs);
}

.rt .rt-table th,
.rt .rt-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  text-align: left;
}

.rt .rt-table th {
  background: var(--surface-sunken);
  font-weight: 650;
}

.rt .rt-table tr:last-child td {
  border-bottom: 0;
}

/* The bounded fallback, shown when a value exceeded the input/node/depth caps or
   failed to parse. It renders a PREFIX the contract produced, never the original
   value. */
.rt .rt-fallback-note {
  margin: 0 0 var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-style: italic;
}

.rt .rt-plain {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A commit subject rendered with non-interactive inline formatting. It appears
   inside a heading or a clickable row, so it never mounts a link, a heading, or
   a list — see `SubjectInline` for why. */
.rt-subject {
  overflow-wrap: anywhere;
}

/* Table column alignment, driven by the `data-align` attribute the renderer sets from
   the source table's `:---:` markers. An attribute rather than an inline style so the
   per-file inline-style ratchet (`bun run check:inline-styles`) stays flat. */
.rt .rt-table [data-align="left"] {
  text-align: left;
}

.rt .rt-table [data-align="center"] {
  text-align: center;
}

.rt .rt-table [data-align="right"] {
  text-align: right;
}

/* The commit BODY block on the revision detail page. A class rather than an inline
   margin, same ratchet reason. */
.rt-commit-body {
  margin-bottom: var(--space-4);
}
