/**
 * Landing page styles: single-file Tailwind CSS 4 entry point, sharing the Web App's
 * visual language — GitHub-style simplicity, solid backgrounds + 1px borders + one
 * brand blue accent, no glassmorphism. Dark theme is toggled via the html.dark class.
 */
@import "tailwindcss";

/* Dark theme toggled via the html.dark class (Tailwind 4 custom variant). */
@custom-variant dark (&:where(.dark, .dark *));

/* Brand color scale (Google blue family), identical to the Web App. */
@theme {
  --color-brand-25: #f8fbff;
  --color-brand-50: #e8f0fe;
  --color-brand-100: #d2e3fc;
  --color-brand-200: #aecbfa;
  --color-brand-300: #8ab4f8;
  --color-brand-400: #669df6;
  --color-brand-500: #4285f4;
  --color-brand-600: #1a73e8;
  --color-brand-700: #0b57d0;
  --color-brand-800: #0842a0;
  --color-brand-900: #062e6f;
  --color-brand-950: #041e49;
}

@layer base {
  :root {
    color-scheme: light;
  }
  .dark {
    color-scheme: dark;
    /* Pure-black dark base, matching the Web App (true-neutral gray overrides). */
    --color-gray-950: #000000;
    --color-gray-900: #0d0d0d;
    --color-gray-800: #1f1f1f;
    --color-gray-700: #303030;
  }
  html {
    scroll-behavior: smooth;
  }
  body {
    @apply bg-white text-gray-900 antialiased dark:bg-gray-950 dark:text-gray-100;
    font-family:
      ui-sans-serif,
      system-ui,
      -apple-system,
      "Segoe UI",
      Roboto,
      "PingFang SC",
      "Microsoft YaHei",
      sans-serif;
  }
  code,
  pre,
  kbd {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  }
  button:focus-visible,
  a:focus-visible,
  summary:focus-visible {
    outline: 3px solid rgb(107 114 128 / 0.4);
    outline-offset: 2px;
  }
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
  ::selection {
    background: rgb(0 0 0 / 0.1);
  }
  .dark ::selection {
    background: rgb(255 255 255 / 0.18);
  }
  * {
    scrollbar-width: thin;
    scrollbar-color: rgb(60 64 67 / 0.28) transparent;
  }
  .dark * {
    scrollbar-color: rgb(232 234 237 / 0.2) transparent;
  }
  /* Anchor targets start at the section content, below the sticky header and its breathing room. */
  .section-anchor {
    scroll-margin-top: 5.5rem;
  }
}

/* ---------- Animations (same tone as the Web App: 160-280ms ease, slight offset) ---------- */

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.99);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.anim-rise {
  animation: rise-in 280ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
.anim-fade {
  animation: fade-in 120ms ease-out both;
}

/* Scroll reveal: sections start slightly offset and settle in when they enter the viewport. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 500ms cubic-bezier(0.2, 0.7, 0.3, 1),
    transform 500ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.reveal-visible {
  opacity: 1;
  transform: none;
}

/* Hero backdrop: a faint dot grid fading out downwards — subtle, no gradients on content. */
.hero-dots {
  background-image: radial-gradient(rgb(26 115 232 / 0.14) 1px, transparent 1px);
  background-size: 22px 22px;
  mask-image: linear-gradient(to bottom, black 0%, transparent 78%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 78%);
}
.dark .hero-dots {
  background-image: radial-gradient(rgb(138 180 248 / 0.16) 1px, transparent 1px);
}

/* ---------- Ambient neon backdrop: blurred glows wandering across the screen ----------
   Viewport-FIXED (the page background stays put while content scrolls over it) with a
   uniform page background behind every section, so nothing jumps at section boundaries.
   Each glow wanders freely across the whole screen on its own slow route (vw/vh
   amplitudes), soft and blue-family only (enterprise tone; hue drift stays within blues). Reduced-motion freezes them in place. */
.neon-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.neon-blob {
  position: absolute;
  width: 48rem;
  height: 48rem;
  border-radius: 9999px;
  opacity: 0.12;
  will-change: transform, filter;
  filter: blur(88px);
  animation: neon-hue 44s linear infinite alternate;
}
.dark .neon-blob {
  opacity: 0.16;
}
.neon-blob-a {
  top: -12rem;
  left: -10rem;
  background: radial-gradient(circle, #4285f4 0%, transparent 66%);
  animation:
    neon-wander-a 44s ease-in-out infinite,
    neon-hue 46s linear infinite alternate;
}
.neon-blob-b {
  top: 8%;
  right: -12rem;
  background: radial-gradient(circle, #0ea5e9 0%, transparent 66%);
  animation:
    neon-wander-b 56s ease-in-out infinite,
    neon-hue 52s linear infinite alternate -16s;
}
.neon-blob-c {
  bottom: -14rem;
  left: 18%;
  background: radial-gradient(circle, #06b6d4 0%, transparent 66%);
  animation:
    neon-wander-c 50s ease-in-out infinite,
    neon-hue 58s linear infinite alternate -30s;
}
.neon-blob-d {
  top: 42%;
  left: -14rem;
  background: radial-gradient(circle, #2563eb 0%, transparent 66%);
  animation:
    neon-wander-d 62s ease-in-out infinite,
    neon-hue 48s linear infinite alternate -22s;
}
@keyframes neon-wander-a {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  30% {
    transform: translate3d(48vw, 22vh, 0) scale(1.12);
  }
  60% {
    transform: translate3d(16vw, 58vh, 0) scale(0.94);
  }
  80% {
    transform: translate3d(-6vw, 28vh, 0) scale(1.06);
  }
}
@keyframes neon-wander-b {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  25% {
    transform: translate3d(-38vw, 32vh, 0) scale(1.08);
  }
  55% {
    transform: translate3d(-58vw, 6vh, 0) scale(1.16);
  }
  80% {
    transform: translate3d(-14vw, -6vh, 0) scale(0.96);
  }
}
@keyframes neon-wander-c {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  30% {
    transform: translate3d(30vw, -34vh, 0) scale(1.1);
  }
  55% {
    transform: translate3d(-12vw, -56vh, 0) scale(0.95);
  }
  80% {
    transform: translate3d(8vw, -16vh, 0) scale(1.05);
  }
}
@keyframes neon-wander-d {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  25% {
    transform: translate3d(34vw, -18vh, 0) scale(1.1);
  }
  50% {
    transform: translate3d(64vw, 8vh, 0) scale(0.95);
  }
  75% {
    transform: translate3d(28vw, 24vh, 0) scale(1.08);
  }
}
@keyframes neon-hue {
  from {
    filter: blur(88px) hue-rotate(-8deg);
  }
  to {
    filter: blur(88px) hue-rotate(12deg);
  }
}

/* ---------- Trend-line growth: draw from zero rightwards, hold, fade, loop ----------
   Paths must set pathLength=1; the dot pops in once the line has fully grown. */
.anim-line-draw {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: line-draw var(--draw-dur, 4.6s) cubic-bezier(0.3, 0, 0.35, 1) infinite;
}
.anim-line-dot {
  opacity: 0;
  animation: line-dot var(--draw-dur, 4.6s) linear infinite;
}
@keyframes line-draw {
  0% {
    stroke-dashoffset: 1;
    opacity: 1;
  }
  55% {
    stroke-dashoffset: 0;
    opacity: 1;
  }
  84% {
    stroke-dashoffset: 0;
    opacity: 1;
  }
  94%,
  100% {
    stroke-dashoffset: 0;
    opacity: 0;
  }
}
@keyframes line-dot {
  0%,
  52% {
    opacity: 0;
  }
  58%,
  84% {
    opacity: 1;
  }
  94%,
  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .reveal {
    opacity: 1;
    transform: none;
  }
  /* With animations disabled the trend lines' base state is hidden; restore them fully drawn. */
  .anim-line-draw {
    stroke-dashoffset: 0;
    opacity: 1;
  }
  .anim-line-dot {
    opacity: 1;
  }
}

/* ---------- Building-blocks loop (LangChain comparison): each block pops in at its
   negative delay within the shared cycle, holds while the rest of the tower lands,
   then the whole skyline fades and rebuilds. Reduced-motion shows towers complete. */
.anim-block {
  animation: block-build var(--cycle, 7.2s) linear infinite;
}
@keyframes block-build {
  0% {
    opacity: 0;
    transform: translateY(5px) scale(0.92);
  }
  3% {
    opacity: 1;
    transform: none;
  }
  93% {
    opacity: 1;
    transform: none;
  }
  100% {
    opacity: 0;
    transform: none;
  }
}

/* Printing never scrolls, so scroll-reveal must not hide content on paper/PDF. */
@media print {
  .reveal {
    opacity: 1;
    transform: none;
  }
}

/* ---------- Minimal typography for Markdown body content (blog + CONTRACT.md) ---------- */

.md-body {
  overflow-wrap: break-word;
}
.md-body :is(p, ul, ol, pre, blockquote, table, details) {
  margin: 0.625rem 0;
}
.md-body li {
  margin: 0.3rem 0;
}
.md-body p,
.md-body li {
  line-height: 1.75;
}
.md-body :is(h1, h2, h3, h4) {
  font-weight: 600;
  margin: 1.25rem 0 0.5rem;
}
.md-body h1 {
  font-size: 1.375rem;
}
.md-body h2 {
  font-size: 1.125rem;
}
.md-body h3 {
  font-size: 1rem;
}
.md-body ul {
  list-style: disc;
  padding-left: 1.25rem;
}
.md-body ol {
  list-style: decimal;
  padding-left: 1.25rem;
}
.md-body a {
  @apply text-brand-700 underline decoration-brand-300 underline-offset-2 transition-colors hover:text-brand-600 dark:text-brand-300 dark:decoration-brand-700;
  /* `anywhere` rather than `word-break: break-all`: it only breaks a token that would otherwise
     overflow, so long URLs wrap cleanly (filling each line) while short Latin words in mixed
     CJK/Latin link text never split mid-word; unlike `break-word` it also counts the break
     opportunities toward min-content sizing, so a long link can't blow out flex/table layouts. */
  overflow-wrap: anywhere;
}
/* Font size only, so fenced blocks keep the size they have always had; the visible chrome below
   is scoped to inline code. */
.md-body code {
  @apply text-[0.85em];
}
/* Inline code: a hairline outline over a barely-there brand wash, replacing the solid gray fill
   this used to carry — in prose dense with `code` that fill read as a row of heavy blocks. The
   chrome is scoped to inline code because the `.md-body pre code` reset below strips background,
   color and padding inside fenced blocks but would not strip a border. Prose often holds long
   unbroken paths and identifiers, so inline code gets the same `anywhere` treatment as links. */
.md-body :not(pre) > code {
  @apply rounded-md border border-brand-200/60 bg-brand-50/40 px-[0.3em] py-[0.1em] text-[0.875em] text-gray-900 dark:border-brand-900/70 dark:bg-brand-950/50 dark:text-gray-100;
  overflow-wrap: anywhere;
}
.md-body pre {
  @apply overflow-x-auto rounded-lg border border-gray-200 bg-gray-50 p-3 text-sm dark:border-gray-800 dark:bg-gray-900;
}
.md-body pre code {
  background: transparent;
  color: inherit;
  padding: 0;
}
.md-body blockquote {
  @apply border-l-2 border-gray-300 pl-3 text-gray-600 dark:border-gray-700 dark:text-gray-400;
}
/* <details>/<summary> — raw HTML that posts already use for long prompts and transcripts, and
   which the browser leaves entirely unstyled (a bare disclosure triangle against flat prose).
   Styled as a card in the same language as `pre`: 1px border, faint fill, rounded. The summary is
   the card's header, so it keeps the horizontal padding of the box and cancels it with a negative
   margin to stay full-bleed, which lets its hover tint reach the border. Open state adds a
   hairline under it, so an expanded block reads as header + body rather than one run of text.

   The disclosure marker is replaced rather than kept: the native one differs per engine and per
   platform and cannot be recolored. `list-style: none` removes it in Chrome and Firefox (where a
   summary is a list item) and the `::-webkit-details-marker` rule removes it in Safari; the
   substitute is a CSS chevron in `currentColor`, so it follows the theme for free and rotates a
   quarter turn on open. It is positioned against the first line (padding + half a line box), not
   the box's center, so a summary long enough to wrap keeps its chevron on line one. The global
   prefers-reduced-motion block below already neutralizes the rotation transition. */
.md-body details {
  @apply rounded-lg border border-gray-200 bg-gray-50/60 px-3.5 dark:border-gray-800 dark:bg-gray-900/40;
}
.md-body details[open] {
  @apply pb-2.5;
}
.md-body summary {
  @apply relative -mx-3.5 cursor-pointer rounded-lg py-2 pr-3.5 pl-8 font-medium transition-colors select-none hover:bg-gray-200/50 dark:hover:bg-gray-800/50;
  list-style: none;
}
.md-body summary::-webkit-details-marker {
  display: none;
}
.md-body summary::before {
  content: "";
  position: absolute;
  left: 1.125rem;
  top: calc(0.5rem + 0.75em);
  width: 0.4em;
  height: 0.4em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  opacity: 0.6;
  transform: translate(-50%, -50%) rotate(-45deg);
  transition: transform 150ms ease;
}
.md-body details[open] > summary {
  @apply mb-1 rounded-b-none border-b border-gray-200 dark:border-gray-800;
}
.md-body details[open] > summary::before {
  transform: translate(-50%, -50%) rotate(45deg);
}
/* The last block inside an open card keeps the card's own bottom padding as its only gutter. */
.md-body details > :last-child {
  margin-bottom: 0;
}
/* Tables: `display: block` + `overflow-x: auto` makes the table its own scroll container, so a
   wide one scrolls inside the prose column instead of widening the page. `border-collapse:
   separate` with zero spacing rather than `collapse`: collapsed borders come from a shared-edge
   model that paints inconsistently under the translucent row tints below, while separate borders
   keep every cell's own hairline crisp behind a semi-transparent fill — the per-side rules below
   rebuild the single-hairline grid.

   `width: max-content` keeps a table narrower than the prose column at its own width. A
   `display: block` table is a block box, so its width would otherwise default to the container's
   while the rows inside stay at their own — leaving an empty strip past the last cell that any
   edge decoration paints into. `max-width: 100%` still clamps a wide one, so it scrolls.

   Deliberately undecorated edges: an earlier revision drew CSS scroll shadows here as an overflow
   cue, and they were dropped as visual noise. The trade-off is real — on overlay-scrollbar
   platforms the scrollbar reserves no layout space and stays invisible until you scroll — so a
   wide table now announces itself only by being clipped at the column edge. */
.md-body table {
  border-collapse: separate;
  border-spacing: 0;
  display: block;
  overflow-x: auto;
  width: max-content;
  max-width: 100%;
  scrollbar-width: thin;
}
/* Cells wrap, with a floor. `white-space: nowrap` used to hold every cell on one line, which sent
   even a mildly wide table into horizontal scroll; the ask now is that a cell may take a second
   line, and only past that should the table widen and scroll instead of growing into a paragraph.

   CSS cannot count lines, so the knob is `min-width`: a column is never squeezed below 9rem, and
   once the columns' floors add up past the prose column the table overflows and horizontal
   scrolling takes over. 9rem is ~16 Latin / 8 CJK characters of text at this size, and it
   is the largest floor that still leaves every table in the blog at one line inside the 720px
   prose column — the widest, the 4-column benchmark table, lands at 697px. At 10rem that same
   table fills the column exactly and its header starts wrapping; at 12rem it scrolls on the
   desktop it fits on today.

   What this does not do: a two-column table whose second column is a sentence still wraps past
   two lines on a phone, because two 9rem floors fit inside a 390px viewport so nothing overflows
   and nothing scrolls. Raising the floor until it does makes that case *worse* (the prose column
   gets narrower, so the line count goes up). A cell holding 150+ characters of prose is a content
   problem — it wants a definition list, not a table row. */
.md-body :is(th, td) {
  @apply border-r border-b border-gray-200 px-2.5 py-1.5 text-sm dark:border-gray-800;
  min-width: 9rem;
}
.md-body :is(th, td):first-child {
  @apply border-l;
}
/* Header and zebra stripes are translucent, like the blog cards: the page (and its ambient
   backdrop) reads through them, so the grid layers over the page instead of sitting on a slab. */
.md-body thead :is(th, td) {
  @apply border-t bg-gray-200/50 text-left font-semibold dark:bg-gray-800/60;
}
.md-body tbody tr:nth-child(even) {
  @apply bg-gray-100/70 dark:bg-gray-800/35;
}
.md-body hr {
  @apply my-4 border-gray-200 dark:border-gray-800;
}
