/* landing.css — the ONE owner of the app landing's look (2026-09-21 rebuild).
   Loads after style.css on index.html only. Written fresh against the
   ChatGPT reference anatomy + the warm Tomasetti skin. Every value here is
   a deliberate choice; nothing is an override of an override.

   2026-10-01 de-clone pass: this file also carries the app's serif display
   face. Fraunces 600, the exact face and file the landing page chose in
   Task 2, declared here because style.css is contractually held to exactly
   one @font-face block (test_page.py, the anti-CDD-drift pin) and this
   stylesheet loads on the app page alone. The family token itself
   (--display-serif) and the [data-app] --display re-point live in
   style.css, beside the other type tokens; what lives here is the face and
   the fine-tuning the serif register needs. */

/* ── the display face ─────────────────────────────────────────────────
   Same block as landing-page.css declares for the front door: Fraunces 600,
   latin subset, downloaded once from Google's type service and served from
   /static/fonts like Outfit. A page loads one of the two files, never both,
   so the family is declared once per page either way. No preload is added
   to index.html for it: test_page.py holds the app page to exactly one
   preload (the Outfit first paint needs), and display text can take the
   swap. display: swap keeps the headline visible while the file loads. */

@font-face {
  font-family: 'Fraunces';
  src: url('/static/fonts/fraunces-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ── ground ─────────────────────────────────────────────────────────── */
.col { min-height: 100vh; }

/* ── greeting ────────────────────────────────────────────────────────── */
.welcome { display: flex; flex-direction: column; margin-block: auto; padding: 18px 4px 10px; }
.greet { text-align: center; margin-bottom: 40px; }
.greet h2 {
  font-family: var(--display);
  /* Serif register, 2026-10-01: Fraunces 600 via the [data-app] --display
     flip in style.css. A half step larger than the sans it replaced
     (Fraunces 600 sets smaller than Outfit 700 at equal size, the same
     correction Task 2 made on the landing's h1), and Fraunces carries its
     own near-zero tracking, so Outfit's -.01em goes too. */
  font-size: clamp(2rem, 1.2rem + 2.2vw, 2.5rem);
  font-weight: 600; line-height: 1.2; letter-spacing: -.005em;
  color: var(--ink);
}
.greet p {
  color: var(--muted); margin: 14px auto 0; font-size: 1.02rem;
  line-height: 1.65; max-width: 34rem;
}

/* The two teasers: one compact quiet line, links in ink, no underline. */
.greetteaser {
  display: inline; margin: 0 10px 0 0;
  font-size: .9rem; color: var(--muted);
}
.greetteaser a { color: var(--ink); text-decoration: none; font-weight: 500; }
.greetteaser a:hover { color: var(--signal-ink); text-decoration: underline; }

/* ── quota badge ─────────────────────────────────────────────────────── */
.quota {
  text-align: center; padding: 4px 14px; margin: 0 auto 14px; width: fit-content;
  background: var(--accent-soft); border: 1px solid var(--line);
  border-radius: 999px; color: var(--muted); font-size: .85rem;
}
.quota b { color: var(--ink); }

/* ── composer: one capsule ───────────────────────────────────────────── */
.composer {
  position: sticky; bottom: 0; z-index: 4;
  width: 100%;
  background: transparent; border-top: 0;
  padding: 10px max(22px, calc((100% - var(--col)) / 2 + 22px)) 16px;
}
body:has(#welcome:not([hidden])) .composer {
  position: static; background: transparent; border-top: 0;
  padding-bottom: 20px; margin-bottom: auto;
}
.composer::before {
  content: none;
}
body:not(:has(#welcome:not([hidden]))) .composer::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%;
  height: 64px; pointer-events: none;
  background: linear-gradient(to top, var(--canvas) 12%, transparent);
}
.askbox {
  display: flex; flex-direction: row; align-items: center; gap: 10px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 8px 8px 8px 22px;
  margin-bottom: 14px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .06), 0 14px 36px rgba(0, 0, 0, .09);
  transition: border-color .15s, box-shadow .15s;
}
.askbox:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 14px 36px rgba(0, 0, 0, .09);
}
.askbox textarea {
  flex: 1; min-width: 0; width: auto;
  resize: none; min-height: var(--tap); max-height: 9rem;
  background: none; border: 0; outline: none; color: var(--ink);
  border-radius: 0; padding: 6px 2px;
  font: inherit; font-size: max(1.05rem, 16px); line-height: 1.55; overflow-y: auto;
}
.askbox textarea::placeholder { color: var(--muted); opacity: 1; }
.askacts { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.composer > .notice {
  margin: 8px 0 0; padding: 9px 13px;
  border-left: 3px solid var(--danger); border-radius: var(--radius-xs);
  background: var(--danger-wash); color: var(--danger);
  font-size: 1rem; line-height: 1.6;
}

/* ── starters: quiet rows, one left edge ─────────────────────────────── */
.chiplabel {
  font-size: .8rem; color: var(--muted);
  margin: 16px 0 0; padding: 0 4px; text-align: center;
}
.chips {
  list-style: none;
  display: flex; flex-direction: column; align-items: stretch;
  gap: 2px;
  width: min(100%, 30rem);
  margin: 8px auto 10px;
}
.chip {
  width: 100%; justify-content: flex-start; text-align: left;
  display: flex; align-items: center; gap: 10px;
  background: transparent; border: 1px solid transparent;
  border-radius: 12px;
  padding: 10px 14px; min-height: var(--tap);
  color: var(--muted); font: inherit; font-size: 1rem; line-height: 1.4;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.chip::before {
  content: "›"; flex: 0 0 auto;
  color: var(--signal); font-weight: 600;
  transition: transform .12s;
}
.chip:hover {
  background: var(--surface-hover);
  color: var(--ink);
}
.chip:hover::before { transform: translateX(2px); }

/* ── upgrade affordances ─────────────────────────────────────────────── */
.upgradelink {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: var(--tap); padding: 6px 14px;
  color: var(--signal-ink); font-weight: 600; font-size: .95rem;
  text-decoration: none; border-radius: 999px;
  transition: background .15s;
}
.upgradelink:hover { background: var(--accent-soft); }
.ugspark { color: var(--signal); font-size: .95rem; }
header { display: flex; align-items: center; }
header .upgradelink { margin-left: auto; margin-right: 18px; }
.sideupgrade { width: 100%; margin: 0 0 2px; }

/* ── sidebar: flat rows, no boxes ────────────────────────────────────── */
.newchat {
  background: transparent; border-color: transparent; border-radius: 12px;
  font-weight: 500;
}
.newchat:hover { background: var(--surface-hover); border-color: transparent; }
.convsearch {
  background: transparent; border-color: transparent; border-radius: 12px;
}
.convsearch:hover { background: var(--surface); }
.convsearch:focus {
  background: var(--surface); border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.themebtn {
  background: var(--surface); border-color: var(--line); border-radius: 50%;
}
.themebtn:hover { background: var(--surface-hover); border-color: var(--line-strong); }
.sizebtns button[aria-pressed="true"] {
  background: var(--accent-soft); color: var(--ink); border-color: var(--accent);
}
.installbtn { color: var(--muted); }
.installbtn:hover { color: var(--ink); background: var(--surface-hover); }

/* ── polish pass 2026-09-26 (Tj: "the app still looks horrible") ────────
   One coherent layer, not another patch: the welcome gets its hero back
   (greeting stays in view, starters become a compact card grid, the quota
   pill tucks under the composer), and the thread stops reading as a form
   (tinted user bubble, action pills, tighter top spacing). Every rule here
   overrides earlier blocks in this file and style.css at equal weight. */

/* Starters: a compact two-column grid of soft cards, aligned to the
   composer's measure. The nine-row full-bleed list read as a bare table
   and pushed everything below the fold. One column again under 620px. */
.chips {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px; width: min(100%, 44rem); margin: 16px auto 6px;
}
.chip {
  display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px 16px; min-height: var(--tap);
  color: var(--ink); font-weight: 500;
}
.chip:hover {
  border-color: var(--accent); background: var(--accent-soft);
  color: var(--ink);
}
.chip::before {
  content: none;
}
@media (max-width: 620px) {
  .chips { grid-template-columns: 1fr; }
}

/* Quota: a quiet caption tucked under the composer, not a stranded pill
   floating in its own band of empty page. */
.quota {
  margin: 0 auto 12px; padding: 3px 12px; font-size: .8rem;
}

/* The greeting is the hero: the composer sits below it with air, and the
   starter grid follows close so the fold shows headline, box, and keys. */
.greet { margin-bottom: 8px; }
.greet h2 { margin-bottom: 10px; }

/* Thread: the visitor's question gets a tinted card -- it should read as
   "you said this", not as an empty input. The answer keeps the paper. */
.msg.user .bubble {
  background: var(--accent-soft); border-color: transparent;
}
@media (prefers-color-scheme: dark) {
  .msg.user .bubble { background: color-mix(in srgb, var(--accent) 16%, var(--surface)); }
}

/* Answer actions: bordered pills with a real affordance, not naked text
   links that read as labels. */
.acts button {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: 8px 14px; min-height: 0;
}
.acts button:hover {
  border-color: var(--accent); background: var(--accent-soft);
  color: var(--ink);
}

/* Thread top: the old 26px pad plus the sticky fade read as a hole at the
   top of every conversation. */
.chat { padding: 14px 0 10px; }

/* Sidebar entries: keep the margin tick, drop the double-bold (the active
   title went to 700 on top of the row's own 600 and read as shouting). */
.convopen[aria-current="true"] .convtitle { font-weight: 600; }

/* ── Shoelace component layer (2026-09-26, Tj: "use the new components") ──
   The page's interactive furniture is Shoelace now -- vendored under
   /static/vendor/shoelace, self-hosted, themed by the same light/dark
   tokens. These rules dress the hosts and guard the swap; everything inside
   a component's shadow is Shoelace's, reached only through ::part and the
   documented custom properties. */

/* Before a component definition loads, hide the host: an unstyled sl-button
   is a bare word in Times, and one frame of that reads as broken. */
sl-button:not(:defined), sl-icon:not(:defined), sl-badge:not(:defined) {
  visibility: hidden;
}
sl-button:defined { display: inline-flex; }

/* Starter grid: the hosts stretch to their cells and their inner button
   left-aligns, so nine cards read as one calm bank. */
.chips sl-button::part(base) {
  width: 100%; justify-content: flex-start;
  font-weight: 500;
}
.chips sl-button::part(base):hover { background: var(--accent-soft); }

/* Horizontal overflow guard: at 390px one long line or button row must
   never widen the page -- the app clips at the canvas edge instead. */
body { overflow-x: clip; }
@media (max-width: 620px) {
  .greet h2 { font-size: 1.7rem; }
  .greet p { overflow-wrap: anywhere; }
  .askbox { gap: 6px; padding: 6px 6px 6px 14px; }
  .askbox .micbtn, .askbox .photobtn { width: calc(var(--tap) - 4px); height: calc(var(--tap) - 4px); }
}

/* Manual dark: Shoelace follows the html class set by applyTheme, and the
   tinted question bubble needs the same class scope -- prefers-color-scheme
   alone misses a visitor who picked dark by hand. */
.sl-theme-dark .msg.user .bubble {
  background: color-mix(in srgb, var(--accent) 16%, var(--surface));
}

/* Shoelace ships a blue primary; the house signal red leads instead. These
   overrides are the whole branding integration -- components read the
   palette from custom properties, so re-pointing them here re-colors every
   component state without touching the vendored files. */
:root {
  --sl-color-primary-50: rgba(109, 40, 217, .07);
  --sl-color-primary-100: rgba(109, 40, 217, .12);
  --sl-color-primary-200: rgba(109, 40, 217, .20);
  --sl-color-primary-300: var(--signal);
  --sl-color-primary-400: var(--signal);
  --sl-color-primary-500: var(--signal);
  --sl-color-primary-600: var(--signal);
  --sl-color-primary-700: var(--signal-ink);
  --sl-color-primary-800: var(--signal-ink);
  --sl-color-primary-900: var(--signal-ink);
  --sl-color-primary-950: var(--signal-ink);
}
html.sl-theme-dark {
  --sl-color-primary-50: rgba(196, 181, 253, .10);
  --sl-color-primary-100: rgba(196, 181, 253, .16);
  --sl-color-primary-200: rgba(196, 181, 253, .24);
  --sl-color-primary-700: #a78bfa;
  --sl-color-primary-800: #a78bfa;
  --sl-color-primary-900: #a78bfa;
  --sl-color-primary-950: #a78bfa;
}

/* Starter hosts go invisible: the component's own outline pill IS the card.
   The old host-card double box is gone. */
.chips .chip { background: transparent; border-color: transparent; padding: 0; min-height: 0; }
.chips sl-button::part(base) {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  font-weight: 500;
  width: 100%; justify-content: flex-start;
}
.chips sl-button::part(base):hover {
  border-color: var(--accent); background: var(--accent-soft); color: var(--ink);
}
.chips sl-button::part(label) { color: var(--ink); }

/* ── the rack chips: the first-run strip ─────────────────────────────────
   The primary row of the landing, above the starters it outranks. Same
   outline pill as the starters, but sized to its own model name in one
   centered wrap, so the row reads as a single offer the starter grid under
   it only backs up. The pill host stretch the starter grid needs is off
   here for the same reason. */
.rackchips {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 8px; width: min(100%, 44rem); margin: 14px auto 0;
}
.rackchips sl-button::part(base) {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-weight: 500;
  width: auto; justify-content: center;
}
.rackchips sl-button::part(base):hover {
  border-color: var(--accent); background: var(--accent-soft); color: var(--ink);
}
.rackchips sl-button::part(label) { color: var(--ink); }
@media (max-width: 640px) {
  /* The strip stops wrapping on a phone, on the starters' old mobile shape:
     one swipeable line under the composer. It bleeds past the composer's
     14px gutters so a pill rides to the screen edge instead of dying
     mid-air, snap settles on a pill edge, and the scrollbar stays silent --
     the cut pill at the viewport edge is what says scrollable. Seven model
     names stacked would push the starters past two viewports of scrolling. */
  .rackchips {
    flex-wrap: nowrap; justify-content: flex-start;
    width: auto; gap: 8px;
    margin: 12px -14px 6px; padding: 0 14px;
    overflow-x: auto; scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  .rackchips::-webkit-scrollbar { display: none; }
  .rackchips .chip { flex: 0 0 auto; scroll-snap-align: start; white-space: nowrap; }
}

/* Thread action pills: outline components over the house surface, not
   Shoelace's filled-neutral default. Hover answers to the house ink. */
.acts sl-button::part(base) {
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
  font-weight: 500;
}
.acts sl-button::part(base):hover {
  border-color: var(--accent); background: var(--accent-soft); color: var(--ink);
}
.acts sl-button::part(label) { color: var(--ink); }

/* .upgradelink is now an sl-button host: the component paints itself
   (primary = house red), so the legacy link dressing on the host is off. */
.upgradelink {
  display: inline-flex; min-height: 0; padding: 0;
  color: inherit; font-size: inherit; text-decoration: none;
  border-radius: 0; transition: none;
  background: transparent;
}
.upgradelink:hover { background: transparent; }
.upgradelink::part(base) { font-weight: 600; }

/* Mobile composer: three action buttons plus a full placeholder don't fit
   390px — shrink the buttons and shorten the placeholder so nothing clips. */
@media (max-width: 640px) {
  /* Named keys, NOT `.askacts button`. The thinking-depth menu lives in this
     row too, and a bare descendant selector squared off its three 97px-tall
     rows into 36px boxes -- text overflowing and overlapping the row below.
     A width rule meant for the round icon keys has no business reaching a
     menu item. */
  .askacts .micbtn, .askacts .photobtn, .askacts .stop, .askacts sl-button {
    width: 36px; height: 36px;
  }
  .composer textarea { font-size: max(15px, 16px); }
  .composer { gap: 6px; }
}

/* ── thinking depth: the elastic dropdown ────────────────────────────────
   The panel's open/close is a spring writing scaleY every frame (app.js),
   which is why nothing here transitions `transform`: two things animating
   one property is a menu that fights itself. The items keep their own
   staggered fade because that is a different property and the stagger is
   what makes the panel read as filling rather than snapping.

   transform-origin is BOTTOM here, not the top the demo used: this menu
   opens upward out of a sticky composer, so it has to grow from the edge
   it is anchored to or it detaches from its own button. */
.depthwrap { position: relative; flex: 0 0 auto; }
.depthbtn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 38px; padding: 0 11px;
  background: transparent; color: var(--muted);
  border: 1px solid var(--line-strong); border-radius: 999px;
  font: inherit; font-size: .84rem; font-weight: 600;
  cursor: pointer; white-space: nowrap;
  transition: color .15s, border-color .15s, background .15s;
}
.depthbtn:hover { color: var(--ink); border-color: var(--accent); background: var(--accent-soft); }
.depthwrap.open .depthbtn { color: var(--ink); border-color: var(--accent); }
.depthbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.depthglyph { width: 15px; height: 15px; flex: 0 0 auto; }
.depthcaret {
  width: 0; height: 0; margin-left: 1px;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .18s;
}
.depthwrap.open .depthcaret { transform: rotate(180deg); }

.depthmenu {
  /* Right-aligned to the button, not left: this control sits at the right
     end of the composer, so a left-aligned panel would grow off the edge of
     the viewport and clip. Anchored here it opens leftward, into the page. */
  position: absolute; right: 0; left: auto; bottom: calc(100% + 10px);
  min-width: 208px; z-index: 40;
  /* A short viewport cannot fit three two-line rows above a sticky composer.
     Scrolling the panel beats letting its top row open off the screen, which
     is the failure mode that has no recovery: the item is simply unreachable. */
  max-height: min(66vh, 300px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: 14px;
  padding: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .06), 0 18px 44px rgba(0, 0, 0, .16);
  transform: scaleY(0); transform-origin: 100% 100%;
  will-change: transform;
}
.depthitem {
  /* Two columns so the tick has a place of its own: the name and the hint
     stack in the first, the check sits centred in the second. The float it
     replaces drifted off the row's baseline on whichever row happened to be
     two lines. */
  display: grid; grid-template-columns: 1fr 15px; align-items: center;
  column-gap: 8px;
  width: 100%; text-align: left;
  background: none; border: 0; border-radius: 9px;
  padding: 9px 11px; cursor: pointer;
  color: var(--muted); font: inherit;
  opacity: 0; transform: translateY(6px);
  transition: opacity .18s, transform .18s, color .15s, background .15s;
}
.depthwrap.open .depthitem { opacity: 1; transform: none; }
.depthitem:nth-child(1) { transition-delay: .04s; }
.depthitem:nth-child(2) { transition-delay: .08s; }
.depthitem:nth-child(3) { transition-delay: .12s; }
.depthitem:nth-child(4) { transition-delay: .16s; }
/* The Pro seat, spent (paid account, day's seats used): visible so the state
   can be read, dimmed so it does not invite the press, and app.js refuses
   the choose either way. Both forms of the selector because the entrance
   rule above outranks a bare .off while the menu is open. */
.depthwrap.open .depthitem.off, .depthitem.off { opacity: .45; }
.depthitem.off .dname { color: var(--muted); }
.depthitem:hover, .depthitem:focus-visible { background: var(--accent-soft); color: var(--ink); outline: none; }
.depthitem .dname { grid-column: 1; font-size: .9rem; font-weight: 600; color: var(--ink); }
.depthitem .dhint { grid-column: 1; font-size: .76rem; margin-top: 1px; }
.depthitem.on .dname { color: var(--signal-ink); }
.depthitem.on .dhint { color: var(--muted); }
/* The chosen row carries a mark as well as a colour: colour alone is not a
   state a person who cannot see it can read. */
.depthitem[aria-checked="true"]::after {
  content: "\2713"; grid-column: 2; grid-row: 1 / span 2;
  justify-self: center; color: var(--signal-ink); font-weight: 700;
}

/* Mobile: the label is the first thing to go -- the glyph still says what
   the control is and the accessible name carries the position. */
@media (max-width: 640px) {
  .depthbtn { height: 34px; padding: 0 9px; gap: 4px; }
  .depthlab { display: none; }
  .depthmenu { min-width: 186px; }
}

/* ── the send button's own motion ────────────────────────────────────────
   Magnetic drift and press squish, from the same kit. The element is
   transformed by a spring in app.js; this only says the transform is cheap
   and that the glyph rides inside the button's own box. */
.askbox button.send { will-change: transform; }
.askbox button.send .sendglyph { display: block; }

/* ── the scroll rail ─────────────────────────────────────────────────────
   The liquid scroll rail: how far down a long answer the reader is, with a
   droplet that swells with scroll speed. Purely an indicator -- it takes no
   pointer events at all, so it can never eat a click meant for the page --
   and it stays out of the way entirely on a phone, where the thread is
   short, the thumb is the scrollbar, and a rail would be furniture. */
.scrollrail {
  position: fixed; right: 12px; top: 50%; transform: translateY(-50%);
  width: 7px; height: 168px; z-index: 20;
  background: var(--surface-hover);
  border: 1px solid var(--line); border-radius: 99px;
  pointer-events: none; opacity: 0; transition: opacity .3s;
}
.scrollrail.on { opacity: 1; }
.scrollrail .railfill {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 0; background: var(--signal); border-radius: 99px;
}
.scrollrail .raildot {
  position: absolute; left: 50%; top: 0;
  width: 11px; height: 11px; margin-left: -5.5px;
  border-radius: 50%; background: var(--signal);
}
@media (max-width: 900px) { .scrollrail { display: none; } }

/* ── phone compaction (2026-09-27, Tj's iPhone 17 Pro screenshots) ───────
   The text-size ladder now scales to 85% under 640px (app.js), which pulls
   the whole rem-based UI back to phone scale. These overrides are the rest
   of the pass: the header must hold ONE row (Menu + wordmark + Upgrade),
   and the composer/pill chrome tightens to the density the references use.
   Written for 402px; everything here is proportional, so 320px devices get
   the same shapes one step denser. */
@media (max-width: 640px) {
  header { padding: 8px 12px; gap: 8px; }
  .menubtn { padding: 7px 11px; font-size: .85rem; }
  .brand h1 { font-size: 1.02rem; }

  main { padding: 14px 14px 8px; }
  .composer { padding: 12px 14px 16px; }
  .system { padding: 14px 14px 28px; }

  .acts { gap: 5px; margin-top: 9px; }

  .sidebar { width: min(20rem, 86vw); }
}
