/* DeepEnough — DeepL-style web translator, Apple design language adapted for
   tool density (see /DESIGN.md). All color/type/space/radius tokens live in
   tokens.css; reusable component visuals live in components.css. This file
   is page layout only: the frosted nav, the tab row, the centered content
   column, the translate two-pane card, and the responsive breakpoint. Load
   order: tokens.css -> components.css -> app.css (this file). */

/* ---- Reset & base ----------------------------------------------------- */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
}
/* The base color lives on <html> so the ambient orb layer can sit BEHIND a
   transparent <body> (z-index:-1) and still be visible — and so the frosted
   nav has something (the orbs) to refract. */
html { background: var(--bg); }

body {
  background: transparent;
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* ---- Ambient liquid-glass backdrop -------------------------------------
   A fixed, non-interactive layer of soft, saturated-but-diffuse radial orbs
   (token-driven, both themes), warped into organic liquid blobs by the inline
   SVG #liquid filter (feTurbulence + feDisplacementMap — a normal filter on a
   decorative element, cross-browser and CSP-safe: no script, no network).
   It gives the floating glass surfaces something PERCEPTIBLE to refract: the
   orbs carry enough count and alpha (tokens.css) that backdrop-filter blur on
   the nav/tab capsules visibly bends real color, not a flat white hint of it.
   Static by design (no motion), sitting below all content at z-index:-1. If a
   browser
   cannot resolve the filter it simply shows the soft gradients unwarped. */
.ambient {
  position: fixed;
  inset: -10%;
  z-index: -1;
  pointer-events: none;
  /* Four overlapping fields instead of three thin ones: blue anchors top-left
     behind the floating nav capsule, teal top-right, indigo lower-left under
     the translate card, a second soft blue lower-right for balance — so every
     floating glass surface always sits over rich, varied color to bend. */
  background:
    radial-gradient(54rem 54rem at 14% 5%, var(--orb-1), transparent 62%),
    radial-gradient(48rem 48rem at 100% 6%, var(--orb-2), transparent 64%),
    radial-gradient(56rem 56rem at 22% 98%, var(--orb-3), transparent 62%),
    radial-gradient(46rem 46rem at 88% 88%, var(--orb-4), transparent 60%);
  filter: url(#liquid);
}
/* Filter reference unsupported / turbulence off → still soft, just unwarped. */
@supports not (filter: url(#liquid)) {
  .ambient { filter: blur(8px); }
}

svg.icon {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}

button { font-family: inherit; }

/* Hidden inline icon sprite: kept out of layout flow. */
.icon-sprite { position: absolute; }

/* ---- Floating capsule nav ------------------------------------------------
   DD-006 r5: Apple's Liquid Glass model treats the nav as a CONTROL layer
   that floats above content, detached from the screen edges — not an
   edge-to-edge bar. Sticky + margin/max-width (rather than `position: fixed`)
   keeps it in normal flow so the page never needs a compensating spacer, and
   caps at the same 1100px column as .shell so the capsule's edges align with
   the content below it. (.nav-frost recipe — fill/blur/specular — lives in
   components.css; the capsule geometry + the one sanctioned floating shadow
   live here, since this is the highest-floating surface in the app.) */
.app-nav {
  position: sticky;
  top: var(--sp-4);
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  max-width: 1100px;
  width: calc(100% - var(--sp-6));
  margin: var(--sp-4) auto 0;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-capsule);
  border: none;
  box-shadow: var(--glass-shadow-lift),
    inset 0 1px 0 var(--glass-edge),
    inset 0 -1px 0 var(--glass-rim);
}
.brand {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  min-width: 0;
  /* Clip the tagline within the brand box so its nowrap text can never spill
     over and push the actions cluster off-canvas on narrow widths. */
  overflow: hidden;
}
/* Logo mark: inline SVG (web/static/logo.svg content mirrored in index.html
   next to the wordmark). Tinted via currentColor so it always matches --text
   without a second color role. .brand keeps align-items:baseline so the
   wordmark + tagline text sit on a shared text baseline, but a replaced
   element like this SVG has no baseline of its own — the flex baseline
   algorithm falls back to its bottom margin edge, which rides the whole box
   up above the wordmark's optical center. align-self:center opts the mark
   out of that baseline alignment so it centers on the lockup's cross axis
   instead (same fix already applied to the styleguide demo lockup,
   .sg-brand-lockup-mark in styleguide.css — this brings the real nav in
   line with it). */
.brand-mark {
  flex: 0 0 auto;
  align-self: center;
  width: 26px;
  height: 26px;
  color: var(--accent);
}
/* The wordmark: the one deliberate --font-display exception (tokens.css) —
   a self-hosted subset face with real character instead of plain system
   600-weight text. Falls back to --font-sans (still 600) if the woff2 fails
   to load, so the brand never goes invisible. */
.brand-name {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  letter-spacing: var(--ls-wordmark);
  font-size: var(--fs-wordmark);
  color: var(--text);
  flex: 0 0 auto;
}
.brand-tagline {
  /* --text-muted (5.07:1 on bg, 4.74:1 on the light frost) clears AA 4.5:1;
     --text-subtle measured 4.21-4.29:1 on frost and failed (WCAG 1.4.3). */
  color: var(--text-muted);
  font-size: var(--fs-chrome-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
/* The three action controls are a hard floor: they never shrink or get pushed
   off-canvas — the brand yields (and its tagline clips) first. (DD-006 UIUX R1) */
.nav-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
}

/* ---- Centered content column ------------------------------------------- */
.shell {
  max-width: 1100px;
  margin: 0 auto;
  width: 100%;
  /* A visible gap above the tab capsule so it reads as detached from the
     floating nav capsule above it, not butted flush against it (DD-006 r5). */
  padding: var(--sp-5) var(--sp-5) var(--sp-5);
  /* Bottom padding intentionally lighter than the 48px block ceiling: on the
     sparse idle view (empty card, no output) --sp-7 stacked with the
     footer's own top padding read as a floaty void between the card and the
     footer rail (DD-006 lens1 P2). --sp-5 keeps the footer feeling anchored
     to the app instead of detached from it. */
}

/* ---- Tab row: floating segmented glass capsule --------------------------
   DD-006 r5: the signature iOS 26 pattern — a compact glass capsule that
   hugs its own content width (not an edge-to-edge underline bar), with the
   active tab riding a brighter "chip" (.tab-chip, components.css) inside a
   recessed track. inline-flex + max-width so it stays compact on wide
   screens and still clips/scrolls on narrow ones. .tabs (fill/glass recipe,
   .tab/.tab-chip visuals) lives in components.css; this is page position +
   the narrow-screen scroll only. */
.app-tabs {
  display: inline-flex;
  max-width: 100%;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
.app-tabs::-webkit-scrollbar { display: none; }
.app-tabs .tab { flex: 0 0 auto; white-space: nowrap; }

/* ---- Views -------------------------------------------------------------- */
.app-main { padding-top: var(--sp-4); }

#view-translate {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* ---- Language bar ------------------------------------------------------
   Composition wrapper; .lang-select/.swap-btn visuals live in
   components.css. */
.lang-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.lang-select-wrap { flex: 1 1 0; min-width: 0; }

/* ---- Translate card (two-pane layout) -------------------------------------
   Hairline border, no shadow (DD §2 — the card is the one element allowed a
   framing border). The .stdout/.streaming caret mechanism is untouched
   (frozen contract). */
.translate-card {
  display: flex;
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  /* A 1px specular top edge ties the opaque content card into the glass
     material language (it reads in dark, invisibly flush in light) without
     any translucency — the panes stay fully legible. Sits at the top edge,
     never behind text. */
  box-shadow: inset 0 1px 0 var(--glass-edge);
  overflow: hidden;
  /* Viewport cap (ENH-007), scoped to this card only — Glossary/Guidelines/
     Docs keep today's full-page scroll. --chrome-budget is the measured sum
     of every OTHER chrome element on the Translate view at desktop width
     (selector names only below, not line numbers — those go stale the
     moment anything nearby is edited, including in the very commit that
     first wrote this comment; grep the selector instead):
     .app-nav margin-top+padding+icon-btn content 77, .shell padding-top 24,
     .tabs track padding+.tab padding+text 45, .app-main padding-top 17,
     #view-translate's flex gap 17x2 (one above the card, one below, to
     .status-line), .lang-bar/.swap-btn height 36, .status-line <p>'s UA
     margins+line 44, .shell padding-bottom 24, .app-footer padding+border+
     line 55 = 356, rounded up to 380 for buffer. Re-check this value (and
     the (max-width:430px) override below) if you touch any of those
     declarations — nothing else catches this drift.
     max-height (not height): the card stays at its natural idle size (measured
     302px = .pane's 300px min-height floor + this card's 2px border; pane-foot
     lives INSIDE that 300px, not stacked additively on top of it) and only
     grows until the cap binds, matching .modal-card's own max-height
     precedent (components.css) — height would pin it to the cap even
     when idle, reopening the "floaty void" DD-006 already fixed just above
     (§ shell padding comment). The 348px floor is a deliberate round number
     comfortably above both real minimums it has to cover — 302px here and
     339px at <=900px (2x168 pane + 1px divider + 2px card border, see the
     coupling comment at that media query below) — and is the
     graceful-degradation guard on very short viewports or high browser zoom:
     the card (and then the page) may show a few px of ordinary scroll rather
     than clipping pane content. svh (not dvh): dvh recomputes live as
     mobile Safari/Chrome's address bar collapses/expands on scroll, forcing
     a reflow of this max-height (and its two .pane children) on every such
     toolbar change; svh is the stable, small-viewport value (same browser
     support as dvh — both shipped together) and never triggers that,
     at the cost of never growing to fill the larger viewport once the
     toolbar auto-hides — an acceptable trade since the 380px budget already
     has slack (simplify pass, efficiency finding). */
  --chrome-budget: 380px;
  max-height: max(348px, calc(100svh - var(--chrome-budget)));
}
.pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 50%;
  min-width: 0;
  min-height: 300px;
}
.pane-source { border-right: 1px solid var(--hairline); }
/* Dark theme: the sole two-pane divider drops to ~1.2:1 at --hairline and
   nearly dissolves; step it up to --hairline-strong. border-color covers the
   right divider (desktop) and the bottom divider (stacked <=900px) alike. */
[data-theme="dark"] .pane-source { border-color: var(--hairline-strong); }
.pane-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--sp-5);
  margin: 0;
  overflow: auto;
  font-size: var(--fs-content);
  line-height: var(--lh-content);
  letter-spacing: var(--ls-content);
  color: var(--text);
  background: transparent;
  /* Themed scrollbar recipe: components.css (grouped with .modal-card,
     simplify pass — a page-layout/component-visual cross-file split doesn't
     hold for one shared recipe with no other page-specific logic; see the
     pre-existing focus-ring precedent there for the same grouping pattern).
     Track backdrop here resolves to this element's own transparent
     background over .translate-card's --card fill. */
}
textarea.input {
  border: none;
  outline: none;
  resize: none;
  width: 100%;
  font-family: inherit;
}
/* Placeholder copy gets more grace than the typed content it stands in for:
   lighter weight, a touch larger than --fs-content, roomier line-height — an
   invitation, not a form-field hint (DD-006 r5 "type scale richness"). The
   moment a user types, real content renders at the pane's own --fs-content/
   --fw-regular; only the empty-state copy gets this treatment. */
textarea.input::placeholder {
  color: var(--text-subtle);
  font-weight: var(--fw-light);
  font-size: 19px;
  line-height: 1.6;
}
.stdout {
  white-space: pre-wrap;
  word-break: break-word;
  font-family: inherit;
}
/* Output-pane empty state: a muted hint drawn via ::before from the pane's
   data-placeholder (set by app.js to reflect no-key vs. ready). Scoped to
   :not(.streaming) so it vanishes the instant a run starts (translate() adds
   .streaming and empties #output together), never colliding with the
   .stdout.streaming::after caret (frozen contract). Same placeholder-grace
   treatment as the source textarea's ::placeholder, for a matched idle pair. */
.stdout:not(.streaming):empty::before {
  content: attr(data-placeholder);
  display: inline-block;
  color: var(--text-subtle);
  font-weight: var(--fw-light);
  font-size: 19px;
  line-height: 1.6;
}
/* Subtle "translating" affordance — no terminal block cursor. Frozen
   contract (DD-006 §3): selector and mechanism unchanged, colors only. */
.stdout.streaming::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1.1em;
  vertical-align: text-bottom;
  margin-left: 1px;
  background: var(--accent);
  animation: caret 1s ease-in-out infinite;
}
@keyframes caret { 50% { opacity: 0.2; } }
/* Reduced motion: hold the caret steady instead of blinking (it still marks
   the in-flight position). Frozen selector, animation only. WCAG 2.2.2. */
@media (prefers-reduced-motion: reduce) {
  .stdout.streaming::after { animation: none; opacity: 1; }
}

.pane-foot {
  display: flex;
  align-items: center;
  /* Wrap so the Accept pill drops to a second row instead of clipping under
     .translate-card{overflow:hidden} on narrow panes. `gap` already supplies
     the row gap once wrapped. (DD-006 UIUX R1) */
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--hairline);
  min-height: 48px;
}
.pane-foot .meta {
  font-size: var(--fs-chrome-xs);
  color: var(--text-subtle);
}
.pane-foot .spacer { flex: 1 1 auto; }

/* ---- Status meta line (under the card) --------------------------------- */
.status-line #seg-pair { color: var(--accent); }

/* ---- Responsive --------------------------------------------------------
   <=900px: single column, panes stack vertically, tabs stay on one line and
   scroll horizontally (.app-tabs overflow-x is always on). Compact spacing
   keeps the output pane above the fold on phones. */
@media (max-width: 900px) {
  .translate-card { flex-direction: column; }
  .pane {
    flex-basis: auto;
    /* Coupled to .translate-card's 348px max-height floor above: two stacked
       panes at this min-height + the 1px divider + the card's own 2px border
       = 339px total, ~9px under the floor. Raising this value shrinks that
       margin further — recheck the floor if you change it (ENH-007). */
    min-height: 168px;
  }
  .pane-source {
    border-right: none;
    border-bottom: 1px solid var(--hairline);
  }
  .pane-body { padding: var(--sp-4); }
}

/* Phones: drop the decorative tagline so the nav has room for the brand name
   plus all three action controls at 320px. (DD-006 UIUX R1) */
@media (max-width: 480px) {
  .brand-tagline { display: none; }
}

/* Very narrow phones (<=430px, e.g. 390pt iPhone): the four tab labels no
   longer fit the capsule at full padding — "Docs" clipped at the scroll edge.
   Trim the tab horizontal padding to --sp-3 so all four read without scrolling
   (the .app-tabs overflow-x scroll stays as a safety net; the .tab-chip is
   absolutely positioned inside .tabs, so its offsetLeft math is unaffected by
   any track scrollLeft). And stack the language bar: two full-width selects
   with the swap control centered between them, so "Detect language" renders
   untruncated instead of being squeezed into a half-width control. */
@media (max-width: 430px) {
  .app-tabs .tab {
    padding-left: var(--sp-3);
    padding-right: var(--sp-3);
  }
  .lang-bar {
    flex-direction: column;
    align-items: stretch;
  }
  .lang-select-wrap { width: 100%; }
  .swap-btn { align-self: center; }
  /* .lang-bar stacks to 3 rows here (2 selects + swap-btn, each 36px, plus
     2x --sp-2 gaps) = 124px vs. the desktop single-row 36px = +88px more
     chrome above the card — added to .translate-card's --chrome-budget
     (ENH-007) so the viewport cap stays accurate at this breakpoint. */
  .translate-card { --chrome-budget: 468px; }
}

/* ---- Footer (DD-006 B6) -------------------------------------------------
   Single quiet line, same width as .shell. Reuses .status-line for the
   muted 12px type recipe (components.css); adds only the layout (width cap,
   hairline top border). No in-page links here — GitHub already lives in the
   nav, and /styleguide stays a public route without a UI entry point. */
.app-footer {
  display: flex;
  align-items: center;
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--hairline);
}
