/*
  Knock — the one stylesheet for every page of itsknock.com.

  The site is meant to look like the app. Colours, type and spacing are the
  light-appearance values of knock-ios/Knock/Design/Theme.swift; when they
  change there, change them in the first block here and every page follows.

  Contents
    1. Values       colours, edges, widths
    2. Base         page, links, focus
    3. Frame        header, footer, the column every page hangs from
    4. Type         the app's text roles
    5. Parts        capsule buttons, the knock disc, a letter face, panels
    6. Home         statement, the three beats, facts, where it stands
    7. Screens      the app's screens, drawn with the app's own parts
    8. Text pages   privacy, terms, support, delete-account
    9. Invitation   /add/<code>
   10. Motion       how things answer a touch, and when they hold still
*/


/* 1. Values ------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* Theme.swift, light appearance */
  --bg: #ffffff;
  --surface: #f4f4f5;
  --text: #0e0e10;
  --secondary: #6b6b70;
  --tertiary: #a1a1a8;                /* too faint for text; marks and markers only */
  --separator: #e6e6e8;
  --ember-rgb: 240 78 35;             /* #f04e23 */
  --ember: rgb(var(--ember-rgb));     /* marks a knock, and nothing else */
  --hairline: rgb(14 14 16 / 0.1);    /* the edge of a photo or a face */

  --text-edge: 20px;                  /* where text starts on a phone */
  --media-edge: 12px;                 /* where photos start on a phone */
  --card-radius: 28px;                /* PhotoCard */

  --page: 1040px;                     /* the widest the page gets */
  --prose: 640px;                     /* the widest a column of reading text gets */
  --gutter: var(--text-edge);

  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (min-width: 700px) {
  :root { --gutter: 40px; }
}


/* 2. Base --------------------------------------------------------------- */

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

html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 1rem/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

main { flex: 1; }

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { display: block; max-width: 100%; }
a { color: inherit; }
strong { font-weight: 600; }

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

/* Rounds the ring on a plain link. Written so that it never overrides a button's own shape. */
:where(a:focus-visible) { border-radius: 6px; }

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


/* 3. Frame -------------------------------------------------------------- */

/* Everything hangs from one left edge, as in the app. */
.shell {
  width: 100%;
  max-width: calc(var(--page) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: max(var(--gutter), env(safe-area-inset-left)) max(var(--gutter), env(safe-area-inset-right));
}

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 14px;
}

/* The wordmark is the app's: lower case, in the title role. */
.wordmark {
  padding-block: 5px;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.4px;
  text-decoration: none;
}

.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 24px;
  padding-block: 72px 28px;
  color: var(--secondary);
  font-size: 0.8125rem;
}

.site-footer nav {
  display: flex;
  gap: 22px;
}

.site-footer a {
  padding-block: 12px;
  text-decoration: none;
}


/* 4. Type --------------------------------------------------------------- */

/* The app's roles (TextRole in Theme.swift), in the order of their size:
   moment 38 bold · title 28 bold · headline 20 semibold · name 16 semibold
   body 16 · button 17 semibold · meta 13                                   */

.moment {
  font-size: clamp(2.375rem, 1.2rem + 4.2vw, 3.875rem);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.021em;
  text-wrap: balance;
}

.title {
  font-size: clamp(1.75rem, 1.45rem + 1.1vw, 2.25rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.headline {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.25;
}

.name {
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.3;
}


/* 5. Parts -------------------------------------------------------------- */

/* A capsule button. Filled with the text colour, it is the one thing to do. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 54px;
  padding: 0 26px;
  border-radius: 999px;
  background: var(--text);
  color: var(--bg);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
}

/* The second kind: a quiet capsule on the surface colour. */
.button.is-quiet {
  min-height: 44px;
  padding: 0 20px;
  background: var(--surface);
  color: var(--text);
  font-size: 1rem;
}

/* Someone whose name isn't known yet: a soft disc of light (UnknownFace).
   Set --size where it is used. */
.disc {
  display: block;
  flex: none;
  width: var(--size, 60px);
  height: var(--size, 60px);
  border-radius: 50%;
  background:
    radial-gradient(closest-side, rgb(var(--ember-rgb) / 0.55), rgb(var(--ember-rgb) / 0.225))
    var(--bg);
  box-shadow: inset 0 0 0 1px rgb(var(--ember-rgb) / 0.35);
}

/* Someone without a portrait: their first letter (Face). */
.face {
  display: grid;
  flex: none;
  place-items: center;
  width: var(--size, 56px);
  height: var(--size, 56px);
  border-radius: 50%;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px rgb(14 14 16 / 0.05);
  color: var(--secondary);
  font-size: calc(var(--size, 56px) * 0.42);
  font-weight: 600;
  line-height: 1;
}

/* A large soft shape that sets something apart from the page. */
.panel {
  padding: 24px var(--text-edge);
  border-radius: var(--card-radius);
  background: var(--surface);
}


/* 6. Home --------------------------------------------------------------- */

/* A state, said the way the app says "Knocked": outlined, not filled. */
.state {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 0 16px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--separator);
  color: var(--secondary);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
}

/* Easier to hit than it looks. */
.state::after {
  position: absolute;
  inset: -6px 0;
  content: "";
}

.hero { padding-top: clamp(44px, 9vw, 104px); }
.hero h1 span { display: block; }

.lead {
  max-width: 31em;
  margin-top: 16px;
  color: var(--secondary);
  font-size: 1.0625rem;
  line-height: 1.45;
  text-wrap: pretty;
}

@media (min-width: 700px) {
  .lead { margin-top: 22px; font-size: 1.25rem; }
}

/* The three beats. On a phone they follow one another like photos in the app:
   the picture runs to the media edge, the words start at the text edge. */
.beats {
  display: grid;
  gap: 44px;
  margin-top: clamp(40px, 7vw, 76px);
}

.beat > .screen { margin-inline: calc(var(--media-edge) - var(--gutter)); }
.beat > h3 { margin-top: 18px; }
.beat > p { margin-top: 6px; color: var(--secondary); }

/* Wider than a phone: picture and words side by side. */
@media (min-width: 620px) {
  .beat {
    display: grid;
    grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
    align-items: center;
    column-gap: 40px;
  }

  .beat > .screen { grid-row: 1 / span 2; margin-inline: 0; }
  .beat > h3 { align-self: end; margin-top: 0; }
  .beat > p { align-self: start; max-width: 24em; }
}

/* Wide enough for all three at once: the whole of Knock in one row. */
@media (min-width: 960px) {
  .beats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 28px; }
  .beat { display: block; }
  .beat > h3 { margin-top: 20px; }
  .beat > p { max-width: none; }
}

.section { margin-top: clamp(80px, 12vw, 136px); }

.facts {
  display: grid;
  gap: 26px;
  margin-top: 26px;
}

.facts p { margin-top: 6px; color: var(--secondary); }

@media (min-width: 960px) {
  .facts { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 28px; margin-top: 32px; }
}

/* Where Knock stands. On a phone the panel runs to the media edge, like a photo. */
.standing { margin-inline: calc(var(--media-edge) - var(--gutter)); padding-block: 28px; }
.standing p { max-width: 36em; margin-top: 10px; color: var(--secondary); }
.standing .button { margin-top: 20px; background: var(--bg); }

@media (min-width: 620px) {
  .standing { margin-inline: 0; padding: 40px; }
}

/* With room to spare, the address sits beside the words instead of under them. */
@media (min-width: 960px) {
  .standing { display: grid; grid-template-columns: 1fr auto; align-items: center; column-gap: 40px; padding: 48px; }
  .standing > * { grid-column: 1; }
  .standing .button { grid-column: 2; grid-row: 1 / span 3; margin-top: 0; }
}


/* 7. Screens ------------------------------------------------------------ */

/* A screen of the app at the size and shape of one of its photos (3:4).
   Sizes inside are the app's own, in points: `calc(56 * var(--pt))` is 56 pt.
   One point is 1/356 of the card's width, so a screen keeps its proportions
   at any size, and on a phone a point comes out at about its real size.
   Where a browser can't measure the card, the window stands in. */
.screen {
  --pt: clamp(0.82px, 0.25vw, 0.92px);
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 3 / 4;
  border-radius: var(--card-radius);
  background: var(--bg);
  container-type: inline-size;
  -webkit-user-select: none;
  user-select: none;
}

@supports (width: 1cqw) {
  .screen { --pt: 0.2809cqw; }
}

/* The hairline goes on top, so a photo underneath can't cover it. */
.screen::after {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--hairline);
  content: "";
  pointer-events: none;
}

.screen-body {
  position: absolute;
  inset: 0;
  font-size: calc(16 * var(--pt));
  line-height: 1.25;
}

.screen-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Home: the wordmark, the knock line, and friends to knock on. */
.app-wordmark {
  padding: calc(20 * var(--pt)) calc(20 * var(--pt)) 0;
  font-size: calc(28 * var(--pt));
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.014em;
}

.app-knock-line {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--pt));
  height: calc(52 * var(--pt));
  margin-top: calc(14 * var(--pt));
  padding-inline: calc(20 * var(--pt));
}

.app-knock-line .disc { --size: calc(32 * var(--pt)); }
.app-knock-line p { flex: 1; min-width: 0; }
.app-knock-line strong { display: block; }
.app-knock-line span { color: var(--secondary); font-size: calc(13 * var(--pt)); }

.app-capsule {
  display: grid;
  flex: none;
  place-items: center;
  width: calc(100 * var(--pt));
  height: calc(38 * var(--pt));
  border-radius: 999px;
  background: var(--surface);
  font-weight: 600;
}

.app-capsule.is-filled {
  width: auto;
  height: calc(36 * var(--pt));
  padding-inline: calc(16 * var(--pt));
  background: var(--text);
  color: var(--bg);
}

.app-hint {
  padding: calc(40 * var(--pt)) calc(20 * var(--pt)) calc(8 * var(--pt));
  color: var(--secondary);
}

.app-friends li {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--pt));
  height: calc(76 * var(--pt));
  padding-inline: calc(20 * var(--pt));
}

.app-friends .face { --size: calc(56 * var(--pt)); }
.app-friends strong { flex: 1; }

/* The photo, just taken, and the one button under it. */
.screen.is-dark { background: #000; color: #fff; }

.app-shot {
  position: absolute;
  inset: calc(8 * var(--pt)) calc(8 * var(--pt)) calc(88 * var(--pt));
  overflow: hidden;
  border-radius: calc(22 * var(--pt));
}

.app-pill {
  position: absolute;
  top: calc(12 * var(--pt));
  left: calc(12 * var(--pt));
  display: grid;
  place-items: center;
  height: calc(36 * var(--pt));
  padding-inline: calc(14 * var(--pt));
  border-radius: 999px;
  background: rgb(0 0 0 / 0.4);
  font-weight: 600;
}

.app-send {
  position: absolute;
  right: calc(20 * var(--pt));
  bottom: calc(16 * var(--pt));
  left: calc(20 * var(--pt));
  display: grid;
  place-items: center;
  height: calc(54 * var(--pt));
  border-radius: 999px;
  background: #fff;
  color: #000;
  font-size: calc(17 * var(--pt));
  font-weight: 600;
}

/* Who was it: the one screen here that answers a touch, like the real one.
   Whoever knocked is a disc with a warm light behind it; a tap says who it is. */
.reveal summary {
  position: absolute;
  inset: 0;
  display: block;
  cursor: pointer;
  font-size: calc(16 * var(--pt));
  line-height: 1.25;
  list-style: none;
  text-align: center;
  -webkit-tap-highlight-color: transparent;
}

.reveal summary::-webkit-details-marker { display: none; }

/* Its focus ring is drawn inside the card and on top of the photo, where an outline would be hidden. */
.reveal summary:focus-visible { outline: none; }
.reveal summary:focus-visible::after {
  position: absolute;
  inset: 6px;
  border-radius: calc(var(--card-radius) - 6px);
  box-shadow: 0 0 0 2px #fff;
  content: "";
  pointer-events: none;
}

/* The photo that was just sent, out of focus and darkened. */
.reveal .screen-photo { filter: brightness(0.44); }

.reveal-pane {
  position: absolute;
  top: calc(116 * var(--pt));
  left: 50%;
  width: calc(112 * var(--pt));
  height: calc(112 * var(--pt));
  margin-left: calc(-56 * var(--pt));
}

/* The warm light behind someone not yet known. */
.reveal-pane::before {
  position: absolute;
  inset: -48%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(var(--ember-rgb) / 0.62) 30%, rgb(var(--ember-rgb) / 0.3) 62%, rgb(var(--ember-rgb) / 0));
  content: "";
}

.reveal-face {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #303033;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.26);
  color: rgb(160 160 168 / 0);
  font-size: calc(47 * var(--pt));
  font-weight: 600;
  line-height: 1;
}

.reveal-ask,
.reveal-answer {
  position: absolute;
  right: calc(16 * var(--pt));
  left: calc(16 * var(--pt));
  display: block;
}

.reveal-ask { top: calc(264 * var(--pt)); }
.reveal-ask strong { display: block; font-size: calc(20 * var(--pt)); }
.reveal-ask span { display: block; margin-top: calc(8 * var(--pt)); color: rgb(255 255 255 / 0.65); }

.reveal-answer { top: calc(240 * var(--pt)); opacity: 0; }
.reveal-answer span { display: block; font-weight: 600; }
.reveal-answer strong {
  display: block;
  margin-top: calc(32 * var(--pt));
  font-size: calc(38 * var(--pt));
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.021em;
}

/* Open: the light goes out, and the name is said. */
.reveal[open] .reveal-pane::before { opacity: 0; transform: scale(1.4); }
.reveal[open] .reveal-face { background: #1c1c1f; box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.1); color: #a0a0a8; }
.reveal[open] .reveal-ask { opacity: 0; }
.reveal[open] .reveal-answer { opacity: 1; }


/* 8. Text pages --------------------------------------------------------- */

.doc { padding-top: clamp(32px, 6vw, 72px); }
.doc > * { max-width: var(--prose); }

.doc h1 {
  font-size: clamp(1.75rem, 1.3rem + 2vw, 2.5rem);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.016em;
}

.doc-date { margin-top: 8px; color: var(--secondary); font-size: 0.9375rem; }
.doc-intro { margin-top: 22px; font-size: 1.0625rem; line-height: 1.5; }

.doc h2 { margin-top: 48px; font-size: 1.25rem; font-weight: 600; line-height: 1.25; }
.doc h3 { margin-top: 28px; font-size: 1rem; font-weight: 600; line-height: 1.3; }

.doc p,
.doc ul,
.doc address { margin-top: 12px; line-height: 1.6; }

.doc h3 + p { margin-top: 4px; }
.doc address { font-style: normal; }

.doc ul { padding-left: 1.2em; list-style: disc; }
.doc li + li { margin-top: 8px; }
.doc li::marker { color: var(--tertiary); }

.doc a:not(.button) {
  text-decoration: underline;
  text-decoration-color: var(--tertiary);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.doc .button { margin-top: 18px; }
.doc .panel { margin-top: 28px; border-radius: 22px; padding: 20px; }
.doc .panel p { margin-top: 0; }


/* 9. Invitation --------------------------------------------------------- */

.invite {
  max-width: calc(440px + 2 * var(--gutter));
  padding-top: clamp(40px, 9vh, 96px);
  text-align: center;
}

.invite .disc { --size: 112px; margin-inline: auto; }
.invite h1 { margin-top: 28px; }
.invite .lead { margin-top: 10px; margin-inline: auto; font-size: 1rem; }

.ways {
  display: grid;
  gap: 12px;
  margin-top: 40px;
  margin-inline: calc(var(--media-edge) - var(--gutter));
  text-align: left;
}

.way p { margin-top: 6px; color: var(--secondary); }

@media (min-width: 620px) {
  .ways { margin-inline: 0; }
}


/* 10. Motion ------------------------------------------------------------ */

/* Every tappable thing answers a touch the same way: it gives slightly
   (PressStyle). Quick on the way down, a little spring on the way back. */
.button,
.state,
.reveal-pane {
  transition: transform 0.3s cubic-bezier(0.34, 1.45, 0.64, 1), opacity 0.3s ease;
}

/* They answer by giving, so the phone's own grey flash on a tap is not needed on top. */
.button,
.state { -webkit-tap-highlight-color: transparent; }

.button:active,
.state:active,
.reveal summary:active .reveal-pane {
  transform: scale(0.96);
  opacity: 0.92;
  transition-duration: 0.18s;
  transition-timing-function: ease-out;
}

/* The reveal: 0.45 s for the face, a little longer for the light (RevealScreen).
   A transition belongs to the state being entered. Opening, the question goes
   first and the name arrives after it; closing, it is the other way round, so
   the two are never on top of each other. */
.reveal-face { transition: background-color 0.45s ease, box-shadow 0.45s ease, color 0.15s ease; }
.reveal-pane::before { transition: opacity 0.6s ease-out, transform 0.6s ease-out; }
.reveal-ask { transition: opacity 0.25s ease 0.15s; }
.reveal-answer { transition: opacity 0.15s ease; }

.reveal[open] .reveal-face { transition: background-color 0.45s ease, box-shadow 0.45s ease, color 0.3s ease 0.15s; }
.reveal[open] .reveal-ask { transition: opacity 0.15s ease; }
.reveal[open] .reveal-answer { transition: opacity 0.4s ease 0.15s; }

@media (hover: hover) {
  .button:hover { opacity: 0.86; }
  .state:hover,
  .site-footer a:hover { color: var(--text); }
  .doc a:not(.button):hover { text-decoration-color: currentColor; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }

  .button:active,
  .state:active,
  .reveal summary:active .reveal-pane { transform: none; }
}
