/* Quicksave — shared styles for the marketing pages.
   Palette and motion mirror ios/Kit/Sources/Kit/DesignSystem.
   Dark-only by design: no light mode exists or is planned. */

/* Palette — Kit/DesignSystem/Palette.swift. Dark-only, by design: no light mode
   exists or is planned, so there is no prefers-color-scheme branch here. */
:root {
  --void:      #14142B;   /* the app's ground — the site's is --ground, below */
  --surface:   #1C1C38;
  --violet:    #7C6CFF;
  --mint:      #3EE6A8;
  --amber:     #FFC94D;
  --text:      #F4F4FA;
  --muted:     #8E8EA8;
  --hairline:  #2A2A4A;
  --ink-mint:  #04342C;

  /* The website's ground is a desaturated Void. #14142B is hue 240 at 37%
     saturation, which reads purple across a whole page — the same reason the
     scrim was neutralised. It also drives iOS Safari's toolbar tint through
     theme-color, which is where it was most obvious: a hard purple bar under a
     neutral page. Void stays in the palette because it is still the app's. */
  --ground:    #12131A;

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;

  /* Four steps and nothing between them. The page previously ran 19/17/16/15/
     12/11 with no rule behind the gaps, which is why several things read as
     accidentally small. Anything that wants emphasis moves a whole step or
     changes weight and colour — it does not invent a size. */
  --text-xl: clamp(19px, 5.2vw, 23px);   /* the hook */
  --text-lg: clamp(16px, 4.2vw, 17px);   /* reserved: no current user */
  --text-md: 16px;                       /* body, inputs, buttons */
  --text-sm: 13px;                       /* labels */
  --text-xs: 12px;                       /* fine print */
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--ground);
  color: var(--text);
  font-family: var(--sans);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The landing page is one screen, centred. A document is not — it flows from
   the top, or the first line of a long policy starts halfway down the window. */
body.landing {
  display: grid;
  place-items: center;
  /* Vertical rhythm is fluid so the whole page still lands on one screen on a
     short phone, without cramping it on a desktop. */
  padding: clamp(24px, 4.5vh, 40px) 24px calc(clamp(24px, 4.5vh, 40px) + env(safe-area-inset-bottom));
}

body.doc {
  padding: 0 24px calc(64px + env(safe-area-inset-bottom));
}

body.landing main {
  z-index: 2;
  position: relative;
  width: 100%;
  max-width: 480px;
  /* A grid item's automatic minimum size is its min-content width, so without
     this the nowrap button below props the whole column open and nothing
     wraps on a small phone. */
  min-width: 0;
  text-align: center;
}

/* --- Wordmark ------------------------------------------------------------ */

/* The wordmark is drawn, not set. DESIGN.md specifies a pixel face with a green
   "Q" mark and records that no font file is vendored — SF Pro is a placeholder
   there too. Geometry means no font to load and crisp edges at any size. */
.wordmark {
  margin: 0 0 8px;
}

.wordmark img {
  display: block;
  width: min(330px, 74%);
  height: auto;
  margin: 0 auto;
  /* The glyphs are a 5x7 grid; let the browser keep them square-edged rather
     than smoothing them into mush when it scales. */
  image-rendering: pixelated;
}

/* --- Mascot -------------------------------------------------------------- */

.stage {
  width: min(224px, 54vw, 26vh);
  margin: 0 auto clamp(20px, 3.5vh, 36px);
  /* QuipDrift.float — a slow vertical hover, 3.2s for one full cycle. */
  animation: float 3.2s ease-in-out infinite;
}

.quip {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.55));
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3.2%); }
}

/* Quip wears one face at a time. The celebrate face is revealed on success. */
.quip-face--celebrate { display: none; }
.is-joined .quip-face--idle { display: none; }
.is-joined .quip-face--celebrate { display: block; }

/* --- Copy ---------------------------------------------------------------- */

.lede {
  margin: 0 auto clamp(18px, 3vh, 26px);
  max-width: 27em;
  font-size: var(--text-md);
  color: var(--muted);
  text-wrap: pretty;
}

/* --- Waitlist ------------------------------------------------------------ */

form { margin: 0 0 clamp(24px, 4vh, 36px); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* One line where there is room for one; on a small phone the button drops to
   its own full-width row rather than squeezing the field to nothing. */
.field {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

input[type="email"] {
  flex: 1 1 180px;
  min-width: 0;
  padding: 13px 15px;
  font: inherit;
  font-size: var(--text-md); /* below 16px iOS Safari zooms the page on focus */
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

input[type="email"]::placeholder { color: var(--muted); }

input[type="email"]:focus-visible {
  outline: none;
  border-color: var(--violet);
  box-shadow: 0 0 0 3px rgba(124, 108, 255, 0.24);
}

/* Secondary. The filled mint belongs to the follow above it — that is the ask
   this page leads with — so the waitlist submit is outlined. Still obviously a
   button; just not the loudest thing on the page. */
button {
  flex: 1 1 auto;
  padding: 13px 20px;
  font: inherit;
  font-size: var(--text-md);
  font-weight: 600;
  white-space: nowrap;
  color: var(--mint);
  background: rgba(62, 230, 168, 0.08);
  border: 1px solid rgba(62, 230, 168, 0.45);
  border-radius: 12px;
  cursor: pointer;
  /* Mechanical press: the lift collapses and the control seats into the
     surface. Nothing on this page translates. */
  transition: transform 140ms ease, background 140ms ease, border-color 140ms ease;
}

button:hover:not(:disabled) {
  background: rgba(62, 230, 168, 0.14);
  border-color: var(--mint);
}

button:active:not(:disabled) { transform: scale(0.97); }

button:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
}

button:disabled { opacity: 0.6; cursor: default; }

.status {
  margin: 12px 0 0;
  min-height: 1.4em;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.status[data-state="ok"]    { color: var(--mint); }
.status[data-state="error"] { color: var(--amber); }

/* The form is replaced wholesale on success — a disabled field left on screen
   is the kind of stuck internal state the app itself refuses to show. */
.is-joined form { display: none; }

/* --- Coming soon --------------------------------------------------------- */

.soon {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding-top: clamp(22px, 3.5vh, 32px);
  border-top: 1px solid var(--hairline);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 14px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  /* Faded: the app is not on the store yet, and the badge must not read as a
     live download link. It is deliberately not an <a>. */
  opacity: 0.38;
  filter: grayscale(1);
  user-select: none;
}

.badge svg { display: block; flex: 0 0 auto; }

.badge-text {
  text-align: left;
  line-height: 1.15;
  color: var(--text);
}

.badge-text small {
  display: block;
  font-size: 8px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.85;
}

.badge-text strong {
  display: block;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Deliberately not uppercased: `text-transform` would render "iOS" as "IOS". */
.soon p {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* --- Reduce Motion -------------------------------------------------------
   The drift and the blink both travel and carry no information, so they go
   entirely rather than being shortened. */
@media (prefers-reduced-motion: reduce) {
  .stage { animation: none; }
  .quip animate { display: none; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
   Documents — privacy policy and anything else that is mostly prose.
   --------------------------------------------------------------------------- */

.doc main {
  max-width: 42rem;
  margin: 0 auto;
}

.doc-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 28px 0 40px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--muted);
}

.doc-nav a {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text);
  text-decoration: none;
  font-weight: 700;
  letter-spacing: 0.34em;
}

.doc-nav a:hover { color: var(--mint); }
.doc-nav img { width: 26px; height: 26px; }

.doc h1 {
  font-size: clamp(30px, 6vw, 40px);
  line-height: 1.1;
  letter-spacing: -0.022em;
  margin: 0 0 10px;
}

.doc .updated {
  margin: 0 0 40px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
}

.doc h2 {
  font-size: 20px;
  letter-spacing: -0.012em;
  margin: 44px 0 12px;
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
}

.doc h3 {
  font-size: 15px;
  margin: 26px 0 8px;
  color: var(--text);
}

.doc p, .doc li { color: var(--muted); }
.doc p { margin: 0 0 14px; }
.doc strong { color: var(--text); font-weight: 600; }
.doc a { color: var(--mint); }

.doc ul { margin: 0 0 14px; padding-left: 20px; }
.doc li { margin-bottom: 7px; }

/* The summary box. The one thing a reader who reads nothing else should see. */
.callout {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--mint);
  border-radius: 12px;
  padding: 20px 22px;
  margin: 0 0 40px;
}

.callout p:last-child, .callout ul:last-child { margin-bottom: 0; }

/* Numbered walkthrough of what happens to a shared link. */
.steps { counter-reset: step; list-style: none; padding-left: 0; }

.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 38px;
  margin-bottom: 12px;
}

.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 1px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--mint);
}

/* Subprocessors. Scrolls in its own box rather than widening the page. */
.table-wrap { overflow-x: auto; margin: 0 0 14px; }

.doc table {
  width: 100%;
  min-width: 30rem;
  border-collapse: collapse;
  font-size: 14px;
}

.doc th, .doc td {
  text-align: left;
  padding: 9px 12px 9px 0;
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
}

.doc th {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
}

.doc td { color: var(--muted); }
.doc td:first-child { color: var(--text); white-space: nowrap; }

.doc footer {
  margin-top: 52px;
  padding-top: 22px;
  border-top: 1px solid var(--hairline);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* Privacy link under the badge. Muted to sit below the CTA in the reading
   order without disappearing — App Store Connect needs this URL to exist. */
.fineprint { margin: 0; }

.fineprint a {
  font-family: var(--mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 1px;
}

.fineprint a:hover { color: var(--mint); border-bottom-color: var(--mint); }

/* 404 — Quip's face standing in for the mascot, since the full body wants the
   inline SVG the landing page carries and this page does not need it. */
.lost {
  display: block;
  margin: 0 auto clamp(20px, 3.5vh, 32px);
  border-radius: 18px;
  opacity: 0.9;
}

/* ---------------------------------------------------------------------------
   The feed, as the page's background.

   Columns of clips drifting past in alternating directions — the thing the app
   exists to rescue something from. Artwork is the generated set the onboarding
   uses; nothing here is anyone else's cover art, logo or UI.
   --------------------------------------------------------------------------- */

.feed {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: flex;
  justify-content: center;
  gap: 8px;
  overflow: hidden;
  /* Enough columns to cover a wide desktop; the surplus is simply clipped on a
     phone, because the row is centred. */
  --col: clamp(88px, 9.5vw, 148px);
}

.feed-col { flex: 0 0 var(--col); }

.feed-track {
  display: flex;
  flex-direction: column;
  gap: 8px;
  will-change: transform;
}

/* Two identical sets per column, shifting by exactly one, so the loop has no
   seam. Each column runs at its own pace or they lock into a visible rhythm. */
.feed-col--up   .feed-track { animation: drift-up   var(--dur) linear infinite; }
.feed-col--down .feed-track { animation: drift-down var(--dur) linear infinite; }

@keyframes drift-up   { from { transform: translateY(0);    } to { transform: translateY(-50%); } }
@keyframes drift-down { from { transform: translateY(-50%); } to { transform: translateY(0);    } }

/* Half the columns are dropped on a phone. Twelve tracks animating under a
   backdrop filter is more than a mid-range device should be asked for, and at
   that width only a few are visible anyway. */
@media (max-width: 700px) {
  .feed-col:nth-child(n + 7) { display: none; }
}

.clip {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  background: var(--surface);
  line-height: 0;
}

.clip img { width: 100%; height: auto; display: block; }

/* A scrim, so a play count stays readable over a bright frame. */
.clip::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 42%;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
  pointer-events: none;
}

.clip-views {
  position: absolute;
  left: 8px;
  bottom: 7px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-weight: 500;
  /* Tracks the column width rather than sitting at a fixed size, so the overlay
     stays in proportion whether a tile is 88px or 148px wide. */
  font-size: clamp(11px, 1.05vw, 13px);
  letter-spacing: 0.02em;
  color: rgba(244, 244, 250, 0.92);
}

/* The play glyph, drawn rather than shipped — inlining an SVG here would mean
   144 copies of the same triangle. */
.clip-views::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 0.46em solid currentColor;
  border-top: 0.32em solid transparent;
  border-bottom: 0.32em solid transparent;
}

/* Not a palette colour on purpose: Honest Coral is reserved for failure, and
   this is a like on someone else's post rather than anything the app says. */
.clip-liked {
  position: absolute;
  top: 7px;
  right: 8px;
  z-index: 1;
  font-size: clamp(15px, 1.5vw, 19px);
  line-height: 1;
  color: #FF3B5C;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}

.clip-liked::before { content: "\2665"; }

/* ---------------------------------------------------------------------------
   Legibility.

   Two layers. A flat scrim knocks the whole feed back so no bright frame
   competes; a masked backdrop blur softens the column the copy sits in.

   The scrim is deliberately *not* Void. #14142B is hue 240 at 37% saturation,
   and spread over the page at 0.87 alpha it tinted everything indigo — the
   whole page measured hue 241. A scrim's job is to darken, not to colour, so
   this is the same darkness at a third of the saturation.

   The blur is narrow and tall, and its width is a fixed 360px rather than a
   viewport fraction. A clamp with a 210px floor looked fine on a wide desktop
   and quietly failed in the middle of the range: at ~770px the solid part of
   the mask was 336px across while the text column is 480px, so the outer third
   of every line sat on unblurred artwork. 360px at a 72% stop keeps the solid
   region wider than the column at every width. Height
   is the opposite — the column is tall and keeps growing, so chasing it with a
   short ellipse just means the footer keeps falling into the fade.

   A hard-edged container was tried and was worse: it read as a card sitting on
   a photo rather than as one image.
   --------------------------------------------------------------------------- */

.feed::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(66% 62% at 50% 47%, rgba(18, 19, 26, 0.87), rgba(18, 19, 26, 0.62) 58%, rgba(18, 19, 26, 0.46)),
    linear-gradient(rgba(18, 19, 26, 0.62), transparent 20%, transparent 66%, rgba(18, 19, 26, 0.88));
}

.feed-veil {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  backdrop-filter: blur(17px) saturate(0.82);
  -webkit-backdrop-filter: blur(17px) saturate(0.82);
  /* The mask applies to the filter as well as the paint, which is what lets the
     blur stop at the edge of the copy. */
  -webkit-mask-image: radial-gradient(ellipse 360px 62vh at 50% 50%, #000 72%, transparent 100%);
          mask-image: radial-gradient(ellipse 360px 62vh at 50% 50%, #000 72%, transparent 100%);
}

/* On a phone the copy fills the width, so a blur wide enough to sit behind it is
   wider than the screen — 518px of solid blur against a 390px viewport, with the
   artwork erased edge to edge. There is no column to hug, so instead of blurring
   harder the blur backs off and the scrim does the work: the frames stay
   recognisable, just dimmer. */
@media (max-width: 700px) {
  /* Secondary text lifts a step on a phone. Measured against the background
     actually behind it: at #8E8EA8 the copy hit 3.65:1 over the brightest
     frames, under the 4.5:1 AA needs for body text. Darkening the scrim would
     have fixed it by hiding the artwork, which is the thing we are trying to
     keep. */
  body.landing { --muted: #A8A8BE; }

  .feed-veil {
    backdrop-filter: blur(8px) saturate(0.95);
    -webkit-backdrop-filter: blur(8px) saturate(0.95);
  }

  /* Lighter than the desktop scrim, not heavier. Darkening to legibility just
     turns the wall black, which hides the frames as thoroughly as the blur did.
     A soft blur keeps their shape and colour; the scrim only has to take the
     edge off. */
  .feed::before {
    background:
      radial-gradient(84% 62% at 50% 47%, rgba(18, 19, 26, 0.72), rgba(18, 19, 26, 0.60) 60%, rgba(18, 19, 26, 0.52)),
      linear-gradient(rgba(18, 19, 26, 0.62), transparent 16%, transparent 78%, rgba(18, 19, 26, 0.78));
  }
}

/* Motion here is decoration and carries nothing, so it stops entirely. */
@media (prefers-reduced-motion: reduce) {
  .feed-col--up .feed-track,
  .feed-col--down .feed-track { animation: none; }

}

/* ---------------------------------------------------------------------------
   Building in public.

   #BuildInPublic is a judged category worth $30k/$20k/$10k, and per
   content/video-style-guide.md the two-brothers premise is the account's whole
   differentiator. It was a mono footnote under the store badge; it is now a
   block of its own, above the store links rather than below them.
   --------------------------------------------------------------------------- */

/* Sits between the tagline and the pitch now, not in the footer, so it needs
   air rather than a rule to separate it. */
.build {
  margin: clamp(16px, 2.5vh, 22px) 0 clamp(18px, 3vh, 26px);
}

/* The line carries the block: mono for the scoreboard read, but at a size and
   weight that makes it the anchor rather than metadata. */
.build-stats {
  margin: 0 0 10px;
  font-family: var(--mono);
  font-size: var(--text-xl);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0.01em;
  color: var(--text);
  text-wrap: balance;
}

/* The follow was 11px — the same size as the privacy link, for the call to
   action of a judged category. The handle now sits a step above body copy and
   the label carries the context, so the size goes to the part worth reading. */
/* The live half of "60 days". Amber because the design system reserves it for
   countdowns and things not out yet, which is exactly what this is — and it
   keeps the timer distinct from the mint follow beneath it. */
.build-day {
  margin: 0 0 12px;
  font-family: var(--mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--amber);
}

.build-follow {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.build-follow span {
  font-family: var(--mono);
  font-size: var(--text-sm);
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* The primary action. #BuildInPublic is the judged category and the follow is
   what feeds it, so it gets the filled mint the waitlist submit used to hold. */
.build-follow a {
  display: inline-block;
  padding: 12px 22px;
  font-family: var(--mono);
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-mint);
  background: var(--mint);
  border-radius: 12px;
  text-decoration: none;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  transition: transform 140ms ease, box-shadow 140ms ease, opacity 140ms ease;
}

.build-follow a:hover { opacity: 0.92; }

.build-follow a:active {
  transform: scale(0.97);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.build-follow a:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
}


