/* Sporadic — sporadic.app
   Palette sampled from the 4.0.0 app screenshots and the radius scale taken
   from the app's GlobalSettings, so the site and the app read as the same
   product. Rethink Sans is the app's own typeface. */

:root {
  --bg: #e6e6e2;
  --surface: #fff;
  --ink: #24375b;
  --ink-deep: #1e2f50;
  --ink-muted: #5d6c88;
  --accent: #b337ff;
  --accent-bright: #c66aff;
  /* BadgePurple in the app's asset catalog -- the countdown pill's colour. */
  --badge-purple: #a108ff;
  /* ButtonTop/ButtonBottom and the lime hold-fill from the app's complete button. */
  /* TextPrimary in the app's asset catalog. */
  --ink-strong: #1c2948;
  --button-top: #1c2948;
  --button-bottom: #0d162b;
  --lime-light: #def955;
  --lime-dark: #b8ea15;
  --hairline: #d3d4cf;

  --measure: 1120px;
  /* Radius scale lifted from the app's GlobalSettings: cardCornerRadius 24 for
     large cards, controlCornerRadius 16 for buttons and full-width controls,
     and 4 for the Pro chip in the Stacks list. */
  --radius: 24px;
  --radius-control: 16px;
  --radius-chip: 4px;
  --pad: clamp(20px, 5vw, 40px);
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Rethink Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img {
  display: block;
  max-width: 100%;
}

a {
  color: var(--accent);
}

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding-inline: var(--pad);
}

/* ---------- header ---------- */

.masthead {
  display: flex;
  align-items: center;
  gap: 14px;
  padding-block: 22px;
}

.masthead__icon {
  width: 44px;
  height: 44px;
}

.masthead__social {
  margin-left: auto;
}

.masthead__name {
  margin: 0;
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ---------- social ---------- */

.social {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* 44px tap target around a 20px glyph, per Apple's minimum -- the icons stay
   small but stay comfortably tappable. */
.social__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--ink-muted);
  transition: color 0.15s ease, background-color 0.15s ease;
}

.social__link:hover {
  color: var(--ink-strong);
  background: color-mix(in srgb, var(--ink-strong) 7%, transparent);
}

.social__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Pulled to the edges so the 44px targets do not read as extra indentation. */
.masthead__social {
  margin-right: -12px;
}

.footer__social {
  margin-left: -12px;
}

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

/* ---------- buttons ---------- */

/* Apple's official badge, served from their marketing toolbox. Their guidelines
   fix the artwork and the proportions, so it only ever gets sized here -- never
   recoloured, outlined, or given a background. */
.appstore {
  display: inline-block;
  line-height: 0;
}

.appstore img {
  width: auto;
  height: 50px;
}


/* Mirrors StackSecondaryButtonLabel: a filled panel rather than an outline,
   bold 17px label, controlHeight 50, controlCornerRadius. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 12px 28px;
  border-radius: var(--radius-control);
  font-size: 17px;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.btn--secondary {
  background: var(--surface);
  color: var(--ink-strong);
}

.btn--secondary:hover {
  background: #f4f4f1;
}

/* ---------- hero ---------- */

.hero {
  padding-block: clamp(40px, 7vw, 80px) clamp(32px, 5vw, 56px);
}

.hero__inner {
  display: grid;
  gap: clamp(32px, 5vw, 56px);
  align-items: center;
}

.hero__copy {
  max-width: 60ch;
}

/* The phone is a desktop-only flourish. Narrower than this there is no room to
   set it beside the copy, and stacking it underneath just pushes the page down
   for no gain -- so it is dropped rather than reflowed.
   It is a background rather than an <img> so narrow viewports genuinely skip
   the download: an <img> is fetched even when display:none, loading="lazy"
   included (measured), whereas a background behind an unmatched media query
   is never requested. */
.hero__phone {
  display: none;
}

@media (min-width: 900px) {
  .hero__inner {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  }

  .hero__phone {
    display: block;
    width: 100%;
    max-width: 400px;
    aspect-ratio: 620 / 1253;
    background: url("../images/hero-phone.jpg") center / contain no-repeat;
    justify-self: center;
  }
}

.hero h1 {
  margin: 0 0 20px;
  font-size: clamp(38px, 5.6vw, 60px);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.hero h1 em {
  color: var(--accent);
  font-style: normal;
}

.hero__lede {
  margin: 0 0 32px;
  font-size: clamp(18px, 2.3vw, 21px);
  color: var(--ink-muted);
}

/* ---------- global challenge ---------- */

/* An angled full-bleed band that cuts across the page instead of sitting in the
   column with everything else. The section clips the rotation so the tilt never
   widens the document, and the band is over-wide so its corners stay off-screen. */
.global {
  overflow: hidden;
  padding-block: clamp(28px, 5vw, 48px);
}

.global__band {
  width: 108vw;
  margin-inline: -4vw;
  padding-block: clamp(26px, 4vw, 40px);
  /* White, not lime: the complete button fills lime as it is held, which is
     invisible on a lime ground. A panel also matches the app's own surfaces,
     and still reads as a distinct band against the warm grey page. */
  background: var(--surface);
  transform: rotate(-1.4deg);
}

/* Counter-rotated so the band reads as tilted tape while the content inside
   stays level and legible.

   It cannot use .wrap: the band is deliberately wider than the viewport, so a
   container centred inside *it* lands 4vw left of every other section. Pinning
   the inner to 100vw re-centres it on the viewport, and the padding reproduces
   .wrap's own measure so the text lines up with the headings above and below. */
.global__inner {
  flex-direction: column;
  align-items: stretch;
  width: 100vw;
  margin-inline: auto;
  padding-inline: max(var(--pad), calc((100vw - var(--measure)) / 2 + var(--pad)));
  display: flex;
  flex-wrap: wrap;
  gap: 20px 32px;
  justify-content: space-between;
  transform: rotate(1.4deg);
}

.global__text {
  min-width: 0;
}

.global__eyebrow {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.global__headline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.global__name {
  margin: 0;
  font-size: clamp(24px, 3.4vw, 34px);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--ink-strong);
}

.global__total {
  margin: 10px 0 0;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-muted);
}

/* Mirrors CountdownBadge exactly: clock glyph, capsule, BadgePurple on a 10%
   wash. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-radius: 999px;
  padding: 4px 10px 4px 7px;
  background: color-mix(in srgb, var(--badge-purple) 10%, transparent);
  color: var(--badge-purple);
  font-size: 14px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The app's HoldToCompleteButton: navy gradient, 50px tall, controlCornerRadius,
   with the lime progress fill sweeping left to right over the same 1.5s. */
/* Full width until there is room to sit it beside the text, matching the app,
   where every control is frame(maxWidth: .infinity). Capped at 260 once the row
   goes horizontal so it does not stretch across the band. */
.global__complete {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  width: 100%;
  height: 50px;
  padding-inline: 28px;
  border-radius: var(--radius-control);
  background: linear-gradient(180deg, var(--button-top), var(--button-bottom));
  color: #fff;
  font-size: 17px;
  font-weight: 700;
  text-decoration: none;
  transition: transform 0.2s ease;
}

.global__complete:active {
  transform: scale(0.97);
}

.global__fill {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--lime-light), var(--lime-dark));
}

.global__complete.is-holding .global__fill {
  transform: scaleX(1);
  transition: transform 1.5s linear;
}

.global__completeLabel {
  position: relative;
}

.global__complete.is-holding .global__completeLabel {
  color: var(--button-top);
  transition: color 0.9s linear;
}

/* ---------- stacks ---------- */

.stacks {
  padding-block: clamp(40px, 7vw, 72px);
  border-top: 1px solid var(--hairline);
}

/* When the banner is showing it separates the two sections itself; the rule is
   only needed on the days it stays hidden. */
.global:not([hidden]) + .stacks {
  border-top: none;
}

.stacks h2,
.gallery h2 {
  margin: 0 0 8px;
  font-size: clamp(28px, 4.4vw, 40px);
  font-weight: 600;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.section__lede {
  margin: 0 0 40px;
  color: var(--ink-muted);
  max-width: 58ch;
}

.stacks__grid {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.stack {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border-radius: var(--radius);
}

/* The photo dissolves into the card instead of ending on a hard edge, using the
   same stops the app masks its stack header with (StacksPage.headerView).
   Masking the image rather than laying a white scrim over it means the fade
   works against whatever colour the card happens to be. */
.stack > img {
  width: 100%;
  height: 168px;
  object-fit: cover;
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 0%,
    #000 45%,
    rgba(0, 0, 0, 0.6) 72%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    #000 0%,
    #000 45%,
    rgba(0, 0, 0, 0.6) 72%,
    transparent 100%
  );
}

/* Custom Stack ships no photograph. It stays a solid tinted tile: there is no
   image to dissolve, so the fade the photos use only weakened it. */
.stack__tile {
  display: grid;
  place-items: center;
  height: 168px;
  color: #fff;
  background: linear-gradient(140deg, var(--accent-bright), var(--accent));
}

.stack__body {
  padding: 20px 22px 24px;
}

.stack__body h3 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
  font-size: 19px;
  font-weight: 600;
}

.stack__body p {
  margin: 0;
  font-size: 16px;
  color: var(--ink-muted);
}

.tag {
  border-radius: var(--radius-chip);
  padding: 4px 8px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
}

.tag--free {
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  color: var(--ink);
}

/* ---------- gallery ---------- */

.gallery {
  padding-block: clamp(40px, 7vw, 72px);
  border-top: 1px solid var(--hairline);
}

/* Full-bleed rail: the strip scrolls edge to edge, but its first card still
   lines up with the text above it. */
.gallery__rail {
  display: flex;
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Without a matching scroll-padding the snapport ignores padding-inline and
     snaps the first card flush to the edge, out of line with the heading. */
  scroll-padding-inline: var(--pad);
  padding-inline: var(--pad);
  padding-bottom: 16px;
  margin-inline: calc(50% - 50vw);
  scrollbar-width: thin;
}

/* min-width on the items is deliberate: a flex item's automatic minimum size is
   its content size, which would hold these images at their intrinsic 560px and
   ignore the basis entirely. */
.gallery__rail > * {
  flex: 0 0 auto;
  width: clamp(220px, 62vw, 300px);
  min-width: 0;
  scroll-snap-align: start;
}

.gallery__rail img {
  height: auto;
  border-radius: var(--radius);
}

/* Centre the rail's content on wide screens without breaking the bleed. */
@media (min-width: 700px) {
  .global__inner {
    flex-direction: row;
    align-items: center;
  }

  /* Must stay after .global__complete's own width:100%: media queries add no
     specificity, so an earlier block would lose on source order. */
  .global__complete {
    width: auto;
    min-width: 260px;
  }
}

@media (min-width: 1200px) {
  .gallery__rail {
    padding-inline: max(var(--pad), calc((100vw - var(--measure)) / 2 + var(--pad)));
  }
}

/* ---------- footer ---------- */

.footer {
  padding-block: clamp(40px, 6vw, 64px);
  border-top: 1px solid var(--hairline);
}

.footer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  margin-bottom: 28px;
}

.footer__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  align-items: center;
  font-size: 15px;
  color: var(--ink-muted);
}

.footer__meta a {
  color: var(--ink-muted);
}

.footer__meta p {
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
