/* Fresh — a one-thumb GitHub feed.
   Dark-only by design: this is a full-bleed media feed, and a light theme
   behind edge-to-edge preview images just flares. */

:root {
  --bg: #0a0a0c;
  --surface: #141419;
  --text: #f4f4f6;
  --muted: #9a9aa6;
  --line: #26262e;
  --accent: #ff3b5c;   /* like */
  --gold: #ffc83d;     /* save / stars */
  --rail: 62px;
  --chrome-h: 92px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
  overscroll-behavior: none;          /* kill rubber-band bounce past the feed */
  -webkit-font-smoothing: antialiased;
}
body { overflow: hidden; }

/* ---------- top chrome ---------- */
#chrome {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  padding-top: env(safe-area-inset-top);
  background: linear-gradient(180deg, rgba(10,10,12,.96) 60%, rgba(10,10,12,0));
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
}

#tabs { display: flex; gap: 4px; padding: 8px 12px 2px; }
.tab {
  flex: 0 0 auto;
  background: none;
  border: 0;
  color: var(--muted);
  font: 600 15px/1 inherit;
  letter-spacing: .2px;
  padding: 8px 12px;
  border-radius: 999px;
  transition: color .15s;
}
.tab.is-active { color: var(--text); }
.tab.is-active::after {
  content: "";
  display: block;
  height: 2px;
  margin: 6px auto 0;
  width: 18px;
  border-radius: 2px;
  background: var(--text);
}

.chips {
  display: flex;
  gap: 6px;
  padding: 4px 12px 10px;
  overflow-x: auto;
  scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  font: 500 12.5px/1 inherit;
  padding: 7px 11px;
  border-radius: 999px;
  white-space: nowrap;
}
.chip.is-active { background: var(--text); color: #000; border-color: var(--text); }
.chip .n { opacity: .5; margin-left: 4px; font-variant-numeric: tabular-nums; }

/* ---------- the feed ---------- */
#feed {
  height: 100dvh;                     /* dvh so iOS toolbars don't clip a card */
  overflow-y: auto;
  scroll-snap-type: y mandatory;      /* one repo per screen, native paging */
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
#feed::-webkit-scrollbar { display: none; }

/* One full-bleed layer, not a media row + a text row. Splitting them left the
   2:1 OG card floating with ~230px of dead blur above and below it, and a hard
   black band under it. Overlaying the caption on the media closes that gap. */
.card {
  position: relative;
  height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;           /* never fly past two cards in one flick */
  overflow: hidden;
}

/* ---------- hero ---------- */
/* GitHub's OG cards are 1200x600. Cropping that landscape image to fill a tall
   phone hero (object-fit: cover) blew it up ~3x and made it unreadable, and the
   card's near-white background swallowed the white action icons. So: show the
   card CONTAINED at its true aspect ratio, and fill the surrounding space with
   a blurred, darkened copy of itself — which also guarantees contrast for the
   rail. Both <img> use the same src, so it costs one network fetch. */
.hero {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #0d0d12;
}
.bg {
  position: absolute;
  inset: -8%;
  width: 116%;
  height: 116%;
  object-fit: cover;
  filter: blur(40px) saturate(150%) brightness(.42);
  opacity: 0;
  transition: opacity .4s ease;
}
.bg.is-ready { opacity: 1; }

/* A 2:1 card can never fill a 390x844 portrait, so rather than leave it looking
   stranded mid-screen, present it as a deliberate floating card: inset, rounded,
   shadowed, over its own blurred backdrop. */
.shot {
  position: absolute;
  z-index: 1;
  left: 5%;
  width: 90%;
  top: 40%;
  transform: translateY(-50%);
  object-fit: contain;
  border-radius: 16px;
  box-shadow: 0 26px 64px rgba(0,0,0,.6), 0 2px 0 rgba(255,255,255,.07) inset;
  opacity: 0;
  transition: opacity .35s ease;
}
.shot.is-ready { opacity: 1; }
.shot.is-failed, .bg.is-failed { display: none; }

/* Only shown when the OG card 404s (common for very new repos). */
.fallback {
  position: absolute;
  z-index: 1;
  top: 42%;
  transform: translateY(-50%);
  display: none;
  padding: 0 32px;
  font: 700 26px/1.25 -apple-system, system-ui, sans-serif;
  color: #3a3a48;
  text-align: center;
  word-break: break-word;
}
.hero.no-image .fallback { display: block; }

.scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(10,10,12,.8) 0 12%, rgba(10,10,12,0) 30%),
    linear-gradient(0deg, rgba(10,10,12,.96) 0 22%, rgba(10,10,12,.7) 38%, rgba(10,10,12,0) 58%);
}

/* double-tap heart */
.burst {
  position: absolute;
  z-index: 4;
  font-size: 110px;
  color: #fff;
  opacity: 0;
  transform: scale(.3);
  text-shadow: 0 8px 40px rgba(0,0,0,.5);
  pointer-events: none;
}
.burst.go { animation: burst .75s cubic-bezier(.17,.89,.32,1.28); }
@keyframes burst {
  0%   { opacity: 0; transform: scale(.3); }
  25%  { opacity: .95; transform: scale(1.1); }
  55%  { opacity: .95; transform: scale(.95); }
  100% { opacity: 0; transform: scale(1.3); }
}

/* ---------- meta ---------- */
.meta {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 calc(var(--rail) + 8px) calc(26px + env(safe-area-inset-bottom)) 18px;
}
.who { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--surface); flex: 0 0 auto; }
.names { display: flex; flex-direction: column; min-width: 0; }
.repo { font-weight: 700; font-size: 16px; letter-spacing: -.1px; text-shadow: 0 1px 6px rgba(0,0,0,.75); }
.owner { font-size: 12px; color: #c2c2cc; text-shadow: 0 1px 5px rgba(0,0,0,.75); }

.desc {
  margin: 0 0 10px;
  color: #e2e2ea;
  text-shadow: 0 1px 6px rgba(0,0,0,.8);
  font-size: 14px;
  /* The -webkit-box trio is the combination iOS Safari actually honours.
     Adding the unprefixed `line-clamp` alongside it made Chrome compute
     display:flow-root and clip the text mid-word instead of wrapping. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.topics { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; max-height: 26px; overflow: hidden; }
.topic {
  font-size: 11.5px;
  color: #c3c3ff;
  background: rgba(60,60,120,.42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid rgba(160,160,255,.26);
  padding: 3px 8px;
  border-radius: 999px;
}

.stats { display: flex; align-items: center; gap: 14px; font-size: 12.5px; color: #c2c2cc; font-variant-numeric: tabular-nums; text-shadow: 0 1px 5px rgba(0,0,0,.8); }
.lang { display: inline-flex; align-items: center; gap: 5px; }
.dot { width: 9px; height: 9px; border-radius: 50%; background: #666; display: inline-block; }
.stars::before { content: "★ "; color: var(--gold); }
.gain { color: #4ade80; font-weight: 600; }
.gain:empty { display: none; }

/* ---------- action rail ---------- */
.rail {
  position: absolute;
  right: 6px;
  bottom: calc(26px + env(safe-area-inset-bottom));
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: var(--rail);
}
.act {
  background: none;
  border: 0;
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 0;
}
.act svg { width: 29px; height: 29px; fill: currentColor; filter: drop-shadow(0 2px 8px rgba(0,0,0,.6)); transition: transform .18s; }
.act:active svg { transform: scale(.86); }
.act .lbl { font-size: 10px; color: rgba(255,255,255,.72); text-shadow: 0 1px 4px rgba(0,0,0,.7); }
.act.like.on { color: var(--accent); }
.act.save.on { color: var(--gold); }
.act.on svg { animation: pop .3s ease; }
@keyframes pop { 50% { transform: scale(1.28); } }

/* ---------- states ---------- */
.notice {
  height: 100dvh;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 10px;
  text-align: center;
  padding: 40px 32px;
  color: var(--muted);
  scroll-snap-align: start;
}
.notice b { color: var(--text); font-size: 17px; }
.notice button {
  margin-top: 6px;
  background: var(--text);
  color: #000;
  border: 0;
  border-radius: 999px;
  font: 600 14px/1 inherit;
  padding: 12px 20px;
}
.spinner {
  width: 22px; height: 22px;
  border: 2px solid var(--line);
  border-top-color: var(--text);
  border-radius: 50%;
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

#toast {
  position: fixed;
  left: 50%;
  bottom: calc(34px + env(safe-area-inset-bottom));
  transform: translate(-50%, 20px);
  z-index: 40;
  background: rgba(28,28,34,.96);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 13px;
  padding: 10px 16px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s, transform .2s;
  backdrop-filter: blur(12px);
  max-width: 82vw;
  text-align: center;
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }

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

/* ==========================================================================
   Desktop / tablet
   --------------------------------------------------------------------------
   The phone layout is one full-height card per screen with the caption
   overlaid on the media. That is right for a thumb and wrong for a laptop —
   on a MacBook Air it would show a single repo per 1440px screen.
   From 760px up the same DOM re-flows into a scannable card grid: media on
   top, caption below it on a solid surface, actions as an icon row. No
   scroll-snap, no ambient blur, no text shadows — none of which earn their
   keep once the text sits on a solid background.
   ========================================================================== */
@media (min-width: 760px) {
  body { overflow: hidden; }

  #chrome {
    background: rgba(10,10,12,.86);
    border-bottom: 1px solid var(--line);
  }
  #tabs, .chips { max-width: 1580px; margin: 0 auto; padding-left: 28px; padding-right: 28px; }
  .chips { padding-bottom: 12px; }

  /* Flex wrap, not grid. CSS Grid auto-row sizing measured every card's
     contribution as ~52px (confirmed: computed grid-template-rows was
     51.59px per row) while an isolated clone of the same card at the same
     width measured 471px — so 471px cards overlapped inside 52px rows and a
     relayout did not correct it. Flex lines size from real content. */
  #feed {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
    align-content: flex-start;
    /* Items must size to their own content. The default `stretch` made each
       card adopt the row height, while the row was auto-sized from an
       intrinsic contribution that ignored the hero's ratio box (its image is
       absolutely positioned, so the hero has no in-flow content) — cards
       collapsed to 52px. Card heights are uniform by construction anyway:
       fixed 2:1 media, 3-line description, single topic row. */
    align-items: flex-start;
    padding: 152px 28px 72px;
    max-width: 1640px;
    margin: 0 auto;
    scroll-snap-type: none;            /* snapping a grid fights the mouse wheel */
  }

  .card {
    flex: 1 1 330px;
    max-width: 560px;
    height: auto;
    scroll-snap-align: none;
    scroll-snap-stop: normal;
    /* Block, not flex. As a flex column the grid sized the row from an
       intrinsic height that ignored the hero's ratio box, clamping every card
       to 52px with its image overflowing. Block layout measures it correctly. */
    display: block;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 16px;
    transition: transform .18s ease, border-color .18s ease;
  }
  .card:hover { transform: translateY(-3px); border-color: #3a3a46; }

  /* Media becomes a fixed 2:1 band — the OG card's native ratio, so `cover`
     crops nothing here.
     The image stays absolutely positioned inside the ratio box. Making it
     static instead re-inlined the <img> (display:inline ignores height), and
     `height:100%` against an aspect-ratio parent formed a percentage cycle
     that collapsed the hero and the whole card to 0. */
  /* The image is IN FLOW here and sizes the hero itself. Every ratio-box
     variant (aspect-ratio or padding-top on .hero, image absolute inside)
     contributed zero intrinsic height, so grid rows auto-sized to ~72px and
     the 471px cards overlapped each other. An in-flow replaced element with a
     definite width and aspect-ratio has a real height the grid can measure. */
  .hero {
    position: relative;
    width: 100%;
    min-height: 60px;                  /* keeps shape if the image 404s */
    border-radius: 15px 15px 0 0;
  }
  .bg, .scrim { display: none; }
  .shot {
    position: static;
    display: block;
    inset: auto;
    transform: none;
    width: 100%;
    height: auto;
    aspect-ratio: 2 / 1;               /* the OG card's native ratio */
    object-fit: cover;
    border-radius: 0;
    box-shadow: none;
  }
  .fallback {
    top: 50%;
    left: 0;
    right: 0;
    font-size: 19px;
    padding: 0 18px;
  }

  .meta {
    position: static;
    padding: 14px 16px 10px;
  }
  /* Shadows existed to lift text off imagery; here it sits on a solid surface. */
  .repo, .owner, .desc, .stats { text-shadow: none; }
  /* Reserve the full 3 lines and one topic row even when a repo has a short
     description or no topics, so cards in a row end level. (3em was under two
     lines at this line-height, which is why heights still varied 428-473px.) */
  .desc { -webkit-line-clamp: 3; min-height: calc(3 * 1.45em); }
  .topics { min-height: 26px; margin-bottom: 8px; }
  .topic { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(110,110,255,.13); }

  .rail {
    position: static;
    flex-direction: row;
    align-items: center;
    width: auto;
    gap: 4px;
    padding: 4px 8px 10px;
    border-top: 1px solid var(--line);
    margin: 0 8px;
  }
  .act {
    flex-direction: row;
    gap: 6px;
    padding: 8px 10px;
    border-radius: 9px;
    color: var(--muted);
    transition: background .15s, color .15s;
  }
  .act:hover { background: rgba(255,255,255,.07); color: var(--text); }
  .act svg { width: 18px; height: 18px; filter: none; }
  .act .lbl { font-size: 11.5px; color: inherit; text-shadow: none; }
  .act.like.on, .act.save.on { color: inherit; }
  .act.like.on { color: var(--accent); }
  .act.save.on { color: var(--gold); }
  .act.share .lbl, .act.open .lbl { display: none; }   /* keep the row compact */

  .burst { font-size: 76px; }

  /* Notices are full-width rows inside the grid, not full-height screens. */
  .notice {
    flex: 1 0 100%;
    max-width: none;
    height: auto;
    min-height: 180px;
    padding: 32px;
    scroll-snap-align: none;
  }
}

@media (min-width: 1180px) {
  .card { flex-basis: 360px; }
}

/* ---------- pull-to-refresh + quick actions ---------- */
#tabs { align-items: center; }
.tab-actions { margin-left: auto; display: flex; gap: 2px; padding-right: 4px; }

.icon-btn {
  background: none;
  border: 0;
  color: var(--muted);
  padding: 8px;
  border-radius: 10px;
  line-height: 0;
  transition: color .15s, background .15s, transform .15s;
}
.icon-btn svg { width: 19px; height: 19px; fill: currentColor; }
.icon-btn:hover { color: var(--text); background: rgba(255,255,255,.07); }
.icon-btn:active { transform: scale(.88); }
#refresh.spin svg { animation: spin .8s linear infinite; }

/* The pull indicator sits under the chrome and is dragged into view. */
#puller {
  position: fixed;
  left: 50%;
  top: calc(env(safe-area-inset-top) + 100px);
  z-index: 15;
  transform: translate(-50%, 0);
  opacity: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(28,28,34,.94);
  border: 1px solid var(--line);
  color: var(--muted);
  pointer-events: none;
  transition: opacity .15s, transform .18s;
  backdrop-filter: blur(10px);
}
#puller svg { width: 17px; height: 17px; fill: currentColor; transition: transform .18s; }
#puller.ready { color: var(--text); border-color: #4a4a58; }
#puller.ready svg { transform: rotate(180deg); }
#puller.spin svg { animation: spin .8s linear infinite; }

/* Touch-only gesture; on a pointer device the toolbar button is the way in. */
@media (hover: hover) and (pointer: fine) {
  #puller { display: none; }
}

/* ---------- status line ---------- */
/* Position within the feed plus snapshot age. Previously the only hint of how
   fresh the data was sat at the very end of the feed, which nobody scrolls to. */
.status {
  display: flex;
  gap: 8px;
  align-items: baseline;
  padding: 0 14px 8px;
  font-size: 11px;
  color: #6f6f7d;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
}
.status .pos { color: var(--muted); font-weight: 600; }
.status .sep { opacity: .45; }
@media (min-width: 760px) {
  .status { max-width: 1580px; margin: 0 auto; padding-left: 28px; padding-right: 28px; }
}
