﻿/* ---------------------------------------------------------------------------
   Caption Contest — player site

   Hand-written rather than a framework on purpose. The audience is on mid-range
   Android phones on mobile data, and a CSS framework plus an icon font is most
   of a second before anything appears.

   Two webfonts are the one indulgence, loaded with display=swap and a real
   fallback stack so text is readable from the first paint. Default system fonts
   were the single biggest reason the site read as unfinished — everything else
   here is colour, weight and space.
--------------------------------------------------------------------------- */

:root {
  /* Surfaces. A cool near-white rather than cream: the beige read as washed
     out, and a faint violet tint makes the accent look deliberate. */
  --paper:      #F6F5FC;
  --card:       #FFFFFF;
  --ink:        #14131C;
  --ink-soft:   #55536B;
  --ink-faint:  #86849B;
  --line:       #E5E2F2;
  --line-soft:  #F1EFFA;

  /* Violet carries the brand, amber carries anything to do with winning.
     Two strong colours with clearly separate jobs, so neither is decoration. */
  --accent:      #6D3BF5;
  --accent-hot:  #8B5CFF;
  --accent-ink:  #FFFFFF;
  --accent-soft: #EFE9FF;

  --prize:      #B45309;
  --prize-bright: #F59E0B;
  --prize-soft: #FFF4DF;

  --good:      #0F7A55;
  --good-soft: #E3F6EE;
  --warn:      #B4520B;
  --warn-soft: #FFEFE2;

  --radius:    18px;
  --radius-sm: 11px;

  /* Layered rather than a single blur: a tight contact shadow plus a wide soft
     one is what stops cards looking pasted on. */
  --shadow:    0 1px 2px rgba(20,19,28,.05), 0 10px 30px -14px rgba(20,19,28,.30);
  --shadow-lg: 0 2px 4px rgba(20,19,28,.06), 0 24px 48px -20px rgba(56,20,140,.35);

  --gutter: 1rem;
  --max: 64rem;

  --font-display: "Bricolage Grotesque", "Segoe UI", system-ui, sans-serif;
  --font-body: Inter, system-ui, -apple-system, "Segoe UI", Roboto,
               "Noto Sans", "Noto Sans Devanagari", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:      #0D0C14;
    --card:       #17161F;
    --ink:        #EDEBF5;
    --ink-soft:   #A6A2BD;
    --ink-faint:  #78748F;
    --line:       #2A2838;
    --line-soft:  #201E2C;

    --accent:      #A784FF;
    --accent-hot:  #C0A5FF;
    --accent-ink:  #17161F;
    --accent-soft: #241C3D;

    --prize:      #FBBF24;
    --prize-bright: #FCD34D;
    --prize-soft: #2A2011;

    --good:      #4ED8A0;
    --good-soft: #10261F;
    --warn:      #F59E5B;
    --warn-soft: #2A1B11;

    --shadow:    0 1px 2px rgba(0,0,0,.6), 0 10px 30px -14px rgba(0,0,0,.9);
    --shadow-lg: 0 2px 4px rgba(0,0,0,.6), 0 24px 48px -20px rgba(0,0,0,.95);
  }
}

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

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

a { color: var(--accent); text-underline-offset: 2px; font-weight: 500; }

:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--card); color: var(--ink);
  padding: .6rem 1rem; border-radius: var(--radius-sm); z-index: 100;
}
.skip:focus { left: 1rem; top: 1rem; }

/* ── Shell ─────────────────────────────────────────────────────── */
.wrap { width: 100%; max-width: var(--max); margin: 0 auto; padding: 0 var(--gutter); }

.site-header {
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  position: sticky; top: 0; z-index: 20;
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; min-height: 3.75rem;
}
.brand {
  font-family: var(--font-display);
  font-weight: 800; font-size: 1.2rem; letter-spacing: -.03em;
  color: var(--ink); text-decoration: none; display: flex; align-items: center; gap: .55rem;
}
.brand .mark {
  /* A glyph, not a tile: no background and no shadow, because a shadow would
     follow the square element box rather than the letterform. The stroke and
     tail are currentColor, so the mark picks up --accent - which the dark
     block above already lightens to #A784FF. One rule covers both themes.
     The amber dot is fixed in the artwork; amber means winning everywhere
     else in this product and should not drift with the reader's theme. */
  width: 2rem; height: 2rem; flex: none;
  color: var(--accent);
}
.header-actions { display: flex; align-items: center; gap: .9rem; font-size: .93rem; }
.header-actions a { color: var(--ink-soft); text-decoration: none; font-weight: 600; }
.header-actions a:hover { color: var(--accent); }
.whoami { max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A sign-out has to be a POST — a link that logs you out gets followed by
   anything that prefetches URLs — so it is a button dressed as a link. */
.inline-form { display: inline; margin: 0; }
.linkish {
  background: none; border: 0; padding: 0; font: inherit; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; text-decoration: none;
}
.linkish:hover { color: var(--accent); }

/* Below 40rem the header becomes two rows: brand, then the nav beneath it.
   Signed in there are four items - Contests, Winners, the player's name and
   Sign out - and on a 375px phone they measured to x=402 against a 375px
   viewport, so EVERY page scrolled sideways by 27px. The previous rule here
   only tightened the gap and the font size, which was never going to close a
   27px gap; the row simply cannot hold four items next to a brand at phone
   widths.

   Two rows rather than a hamburger: no JavaScript, nothing to open, and every
   destination stays one tap away. It costs ~17px of sticky header height.

   The name is the only flexible item (flex: 0 1 auto with min-width: 0, so it
   can shrink below its content and ellipsis). The links and the sign-out form
   are flex: none so they never squash. That is what makes this hold at any
   width instead of at the handful someone thought to test - the name gives up
   space as the screen narrows: 128px at 375, 107px at 344, 83px at 320, all
   with zero overflow. */
@media (max-width: 40rem) {
  .site-header .wrap {
    flex-wrap: wrap; gap: .35rem 1rem; min-height: 0;
    padding-top: .55rem; padding-bottom: .55rem;
  }
  .header-actions {
    width: 100%; justify-content: space-between;
    gap: .65rem; font-size: .88rem;
  }
  .whoami { max-width: 8rem; min-width: 0; flex: 0 1 auto; }
  .header-actions > a:not(.whoami),
  .header-actions .inline-form { flex: none; }
}

.visually-hidden-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

main { padding: 1.5rem 0 4rem; }

.site-footer {
  border-top: 1px solid var(--line);
  padding: 1.35rem 0 1.75rem;
  color: var(--ink-faint); font-size: .88rem;
}
.site-footer a { color: var(--ink-soft); }

/* The two blocks sit on one line and centre against each other; below 40rem
   flex-wrap drops the profiles onto their own line, which is the only thing
   that fits a 360px phone. space-between rather than a gap so the profiles
   stay pinned right on a wide screen and hard left once they wrap. */
.footer-inner {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: .5rem 1.5rem;
}
.footer-text { min-width: 0; }
.footer-brand { margin: 0 0 .2rem; }
.footer-links { margin: 0; font-size: .9rem; }
.footer-links span { margin: 0 .4rem; color: var(--ink-faint); }

.footer-social {
  margin: 0;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .1rem;
  font-size: .9rem;
}

/* A round target rather than the glyph alone. 2.35rem is 38px, close enough
   to a thumb on a phone; an 18px icon with no padding is a miss that reads as
   a broken link. The circle only appears on hover, so at rest the row is five
   marks and not five buttons. Negative margin on the ends pulls the padding
   back so the icons optically align with the text above them. */
.footer-social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.35rem; height: 2.35rem; border-radius: 999px;
  color: var(--ink-soft);
  transition: color .15s ease, background-color .15s ease;
}
.footer-social a:hover {
  color: var(--accent);
  background: var(--accent-soft);
}
/* The fallback, for a Site:Social entry with no artwork - it keeps its name
   and needs room for words rather than a square. */
.footer-social a.social-word {
  width: auto; height: auto; padding: .3rem .5rem; border-radius: var(--radius-sm);
}

@media (max-width: 40rem) {
  /* Wrapped, so the profiles start at the left edge under the links rather
     than floating to the right of nothing. */
  .footer-inner { justify-content: flex-start; }
  .footer-social { margin-left: -.55rem; }
}

/* ── Sharing ─────────────────────────────────────────────────────
   The control beside a player's own vote arrow, and the fallback menu it
   opens where navigator.share does not exist. Positioned relative so the
   menu can hang off it without the row needing a stacking context. */
.entry-share { position: relative; display: inline-flex; margin-left: auto; }

.share-btn {
  background: none; border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink-soft); font: inherit; font-size: .78rem; font-weight: 600;
  padding: .15rem .6rem; cursor: pointer; line-height: 1.6;
}
.share-btn:hover { border-color: var(--accent); color: var(--accent); }

.share-menu {
  position: absolute; right: 0; top: calc(100% + .35rem); z-index: 5;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-sm); box-shadow: 0 8px 24px -8px rgba(0,0,0,.28);
  padding: .3rem; display: flex; flex-direction: column; min-width: 8.5rem;
}
.share-menu a,
.share-menu button {
  background: none; border: 0; text-align: left; font: inherit;
  font-size: .85rem; color: var(--ink); text-decoration: none;
  padding: .4rem .55rem; border-radius: 7px; cursor: pointer; white-space: nowrap;
}
.share-menu a:hover,
.share-menu button:hover { background: var(--accent-soft); color: var(--accent); }

/* The hidden attribute has to be told to win here.
   [hidden] { display: none } is a user-agent rule, and ANY author rule that
   sets display beats it - so the display:flex above left the menu permanently
   open, with the attribute toggling nothing anybody could see. Caught by
   reading computed style on the live page; the markup looked perfectly
   correct, because it was. */
.share-menu[hidden] { display: none; }
.share-menu button.is-done { color: var(--good); }

/* The vote control keeps its place at the end of the row; with a share
   button present, the meta row needs the gap rather than the margin-left
   auto that used to do the spacing on its own. */
.entry-share + span { margin-left: .5rem !important; }

/* ── Type ──────────────────────────────────────────────────────── */
/* The display face does the heavy lifting. Tight tracking and a heavy weight
   is most of the difference between "a page" and "a product". */
h1, h2, .brand, .stat-value, .btn, .lead-mark {
  font-family: var(--font-display);
}

h1 {
  font-size: clamp(2rem, 1.4rem + 2.8vw, 3.1rem);
  font-weight: 800; line-height: 1.02; letter-spacing: -.04em;
  margin: 0 0 .6rem; text-wrap: balance;
}
h2 {
  font-size: clamp(1.35rem, 1.15rem + .8vw, 1.7rem);
  font-weight: 800; line-height: 1.12; letter-spacing: -.03em;
  margin: 0 0 .6rem; text-wrap: balance;
}
h3 { font-size: 1.05rem; font-weight: 700; line-height: 1.3; margin: 0 0 .3rem; }
p { margin: 0 0 1rem; }
.lede { color: var(--ink-soft); font-size: 1.1rem; }
.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); font-size: .88rem; }

/* ── Contest description ───────────────────────────────────────────
   The description is author-typed plain text and Razor HTML-encodes it on the
   way out. That encoding is what stops a Sponsor - who can manage contests -
   putting markup on a page every player sees, so it stays. The side effect is
   that HTML then collapses the author's newlines, which is how a line-per-item
   contest brief arrived on the page as one wall of prose.

   pre-line is the whole fix: newlines are honoured, runs of spaces are still
   collapsed, the text wraps normally, and no markup becomes possible. */
.contest-desc { white-space: pre-line; }

/* The same text on the home page sits in a card in a list, where honouring a
   dozen line breaks would make one contest tower over its neighbours. The list
   therefore keeps the collapsed single-paragraph form and cuts it short; the
   detail page is where the full brief belongs. */
.contest-desc-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* ── Cards ─────────────────────────────────────────────────────── */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
}
.card-body { padding: 1.15rem; }
@media (min-width: 40rem) { .card-body { padding: 1.5rem; } }

.grid { display: grid; gap: 1rem; }
@media (min-width: 44rem) { .grid.cols-2 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 60rem) { .grid.cols-3 { grid-template-columns: repeat(3, 1fr); } }

/* Width and height are set on the element so the card does not reflow as the
   picture arrives — the biggest cause of a page feeling broken on a slow
   connection. */
.contest-image {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  background: var(--line-soft);
}

/* ── Buttons ───────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  font-weight: 700; font-size: 1rem; line-height: 1; letter-spacing: -.01em;
  padding: .85rem 1.35rem; border-radius: 999px;
  border: 1px solid transparent; cursor: pointer; text-decoration: none;
  min-height: 3rem;                    /* comfortable thumb target */
  transition: transform .12s ease, box-shadow .16s ease, background-color .16s ease;
}
.btn:active { transform: translateY(1px) scale(.99); }

.btn-primary {
  background: linear-gradient(140deg, var(--accent-hot), var(--accent));
  color: #FFFFFF;
  box-shadow: 0 6px 18px -8px var(--accent), inset 0 1px 0 rgba(255,255,255,.25);
}
.btn-primary:hover { box-shadow: 0 10px 26px -8px var(--accent); }

.btn-quiet { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-quiet:hover { background: var(--line-soft); border-color: var(--accent); }

.btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
.btn-block { width: 100%; }
.btn.is-done { border-color: var(--good); color: var(--good); }

/* ── Hero ──────────────────────────────────────────────────────── */
/* The picture is what people came for, so it runs edge to edge with the title
   sitting on it. The old version stacked a small image above a heading and read
   like a list item. */
.hero { margin-bottom: 1.5rem; box-shadow: var(--shadow-lg); border: 0; }
.hero-media { position: relative; }
.hero-media .contest-image { aspect-ratio: 16 / 10; }

/* A scrim, so white text is readable over any photo. */
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top,
              rgba(10,8,20,.86) 0%, rgba(10,8,20,.45) 34%, rgba(10,8,20,0) 68%);
  pointer-events: none;
}
.hero-title {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  margin: 0; padding: 1.1rem 1.15rem;
  color: #FFFFFF; text-shadow: 0 2px 18px rgba(0,0,0,.5);
  /* It is an h2 now - the page's h1 is the line above the card, which is
     the honest outline: the proposition is the page, the contest is what is
     on it today. The size is restated here so dropping a heading level
     changed the document and not the design. */
  font-size: clamp(2rem, 1.4rem + 2.8vw, 3.1rem);
  font-weight: 800; line-height: 1.02; letter-spacing: -.04em;
}
@media (min-width: 40rem) { .hero-title { padding: 1.5rem; } }

.ribbon {
  position: absolute; top: .9rem; left: .9rem; z-index: 2;
  background: #FFFFFF; color: var(--accent);
  font-family: var(--font-display);
  font-size: .72rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  padding: .4em .85em; border-radius: 999px;
  display: inline-flex; align-items: center; gap: .4rem;
  box-shadow: 0 4px 14px rgba(0,0,0,.3);
}
.ribbon::before {
  content: ""; width: .5rem; height: .5rem; border-radius: 50%;
  background: #E11D48; flex: none;
}
@media (prefers-reduced-motion: no-preference) {
  .ribbon::before { animation: livePulse 2s ease-in-out infinite; }
}

/* The prize, on the picture, opposite the Live ribbon. Amber because amber
   means winning everywhere else in this product.

   max-width and the wrap are deliberate: the text is whatever an operator
   typed into the prize - "500 rupees for the winner" is a real one - and
   half a prize with an ellipsis is worse than two short lines of it. Capped
   at 45% so it cannot meet the Live ribbon on a narrow phone. */
.ribbon-prize {
  position: absolute; top: .9rem; right: .9rem; z-index: 2;
  max-width: 45%; text-align: right;
  background: var(--prize-bright); color: #3D2200;
  font-family: var(--font-display);
  font-size: .72rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  line-height: 1.25;
  padding: .4em .85em; border-radius: 999px;
  box-shadow: 0 4px 14px rgba(0,0,0,.3);
}
@keyframes livePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(225,29,72,.6); }
  60%      { box-shadow: 0 0 0 .45rem rgba(225,29,72,0); }
}

/* ── Stat row ──────────────────────────────────────────────────── */
.stats { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: 1.25rem; }
.stat {
  flex: 1 1 auto; min-width: 6rem;
  background: var(--line-soft); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: .7rem .85rem;
}
.stat-value {
  display: block; font-weight: 800; font-size: 1.3rem;
  line-height: 1.1; letter-spacing: -.03em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.stat-label {
  display: block; font-size: .72rem; font-weight: 600; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .07em; margin-top: .25rem;
}
.stat-prize {
  background: var(--prize-soft); border-color: var(--prize-bright); flex: 2 1 13rem;
}
.stat-prize .stat-value { color: var(--prize); font-size: 1.05rem; }
.stat-prize .stat-label { color: var(--prize); opacity: .8; }

.stat.is-urgent { background: var(--warn-soft); border-color: var(--warn); }
.stat.is-urgent .stat-value, .stat.is-urgent .countdown { color: var(--warn); }

.countdown { font-variant-numeric: tabular-nums; font-weight: 800; color: var(--accent); }

/* ── How it works ──────────────────────────────────────────────── */
/* Three lines that fill the page when there is little else on it, and answer
   "what am I supposed to do" for somebody who has never seen this before. */
.steps {
  display: grid; gap: .75rem; margin-bottom: 1.75rem;
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) { .steps { grid-template-columns: repeat(3, 1fr); } }

.step {
  display: flex; align-items: flex-start; gap: .75rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: .9rem 1rem;
}
.step-num {
  font-family: var(--font-display); font-weight: 800; font-size: .9rem;
  width: 1.75rem; height: 1.75rem; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
}
.step p { margin: 0; font-size: .93rem; line-height: 1.45; }
.step strong { display: block; color: var(--ink); }

/* ── Composer ──────────────────────────────────────────────────── */
.composer { margin: 0; }
.composer textarea.input { min-height: 5rem; font-size: 1.05rem; }
.composer-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; margin-top: .65rem; flex-wrap: wrap;
}
.composer-foot .help { margin: 0; flex: 1 1 10rem; }
.help.is-close { color: var(--warn); font-weight: 700; }

/* ── Trust strip ───────────────────────────────────────────────── */
.trust {
  display: flex; flex-wrap: wrap; gap: .5rem 1.15rem; align-items: center;
  font-size: .88rem; font-weight: 600; color: var(--ink-soft); margin-bottom: 1.75rem;
}
.trust-item::before {
  content: "✓"; color: var(--good); font-weight: 800; margin-right: .35rem;
}

/* ── Proof strip ───────────────────────────────────────────────── */
.proof { margin-bottom: 1.75rem; }
.proof-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-bottom: .7rem;
}
.proof-head h2 { margin: 0; font-size: 1.2rem; }
.proof-head a { font-size: .88rem; }

.proof-strip {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(14rem, 1fr);
  gap: .75rem; overflow-x: auto; padding-bottom: .35rem;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
}
.proof-item {
  scroll-snap-align: start;
  background: var(--good-soft); border: 1px solid var(--good);
  border-radius: var(--radius-sm); padding: .85rem .95rem;
}
.proof-caption {
  font-weight: 700; margin: 0 0 .35rem; overflow-wrap: anywhere; color: var(--ink);
}

/* The winner's face, beside their name, on the home page. .winner-face is
   the same element the Winners page uses - this only lays the row out
   around it. A stranger deciding whether this contest is real is looking
   for evidence that somebody was actually paid, and a face beside a prize
   is that evidence in a way a name never is.

   The column keeps its shape when there is no photograph, which is most of
   the time: the row is a flex line and simply starts at the name. */
.proof-item { display: flex; flex-direction: column; }
.proof-who {
  display: flex; align-items: center; gap: .5rem; margin-top: auto;
  font-size: .88rem; color: var(--ink-soft); flex-wrap: wrap;
}
.proof-who .winner-face { width: 2rem; height: 2rem; }
.proof-who strong { color: var(--ink); font-weight: 700; }

/* ── The line above the hero ────────────────────────────────────────
   Deliberately smaller than a bare h1: the contest name sitting on the
   photograph is still meant to be the loudest thing on the page. */
.intro { margin-bottom: 1.1rem; }
.intro h1 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.2rem); margin-bottom: .25rem; }
.intro .lede { margin: 0; font-size: 1rem; }

/* ── Section heads ─────────────────────────────────────────────── */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.section-head h2 { margin-bottom: .6rem; }
.section-head a { font-size: .9rem; white-space: nowrap; }

/* ── Entries ───────────────────────────────────────────────────── */
.entry {
  position: relative;
  padding: 1.05rem 0; border-top: 1px solid var(--line-soft);
}
.entry:first-child { border-top: 0; }
.entry-text {
  font-size: 1.12rem; font-weight: 500; line-height: 1.4; margin: 0 0 .55rem;
  letter-spacing: -.01em;
  /* Captions are stranger-submitted and can be one long unbroken string. */
  overflow-wrap: anywhere;
}
.entry-meta {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  font-size: .86rem; color: var(--ink-faint); font-weight: 500;
}
.entry.is-mine {
  background: var(--accent-soft); border-radius: var(--radius-sm);
  padding: 1.05rem; border-top: 0;
  box-shadow: inset 3px 0 0 var(--accent);
}

/* The front-runner gets real weight: it tells a reader the contest has a
   shape, and tells whoever is second that they are close. */
.entry.is-leading { }
.lead-mark {
  display: inline-block; font-size: .68rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase;
  color: #FFFFFF; background: linear-gradient(140deg, var(--prize-bright), var(--prize));
  border-radius: 999px; padding: .25em .7em; margin-bottom: .45rem;
}
.entry.is-leading .entry-text { font-size: 1.22rem; font-weight: 600; }

/* ── The caption somebody was sent here to see ─────────────────────
   A visitor arriving from a shared link has no way to pick one caption out
   of twenty-five written by strangers. This is the signpost. It sits above
   the list and the same entry still appears in its own place below, so the
   card has to read as "here is the one" rather than as a separate contest. */
.shared-callout {
  border: 1px solid var(--accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .05);
  margin-bottom: 1rem;
}
.shared-lede {
  margin: 0 0 .35rem;
  font-size: .9rem; color: var(--ink-soft);
}
.shared-lede strong { color: var(--ink); }
/* The pinned copy is already inside its own bordered card, so it drops the
   list row's top rule and its own background tint - two nested highlights
   on one caption reads as a rendering fault rather than as emphasis. */
.shared-callout .entry { border-top: 0; padding-top: 0; }
.shared-callout .entry.is-mine {
  background: transparent; box-shadow: none; padding: 0;
}

/* ── The winner's own face ─────────────────────────────────────────
   Small and circular, sitting in the byline rather than above the card:
   this is "a real person won this", not a photo gallery. Most winners send
   nothing, so the row must read as complete without it — which is why it is
   inline and carries no reserved space of its own. */
.winner-face {
  width: 2.25rem; height: 2.25rem;
  border-radius: 50%; object-fit: cover;
  border: 2px solid var(--card);
  box-shadow: 0 0 0 1px var(--line-soft);
  flex: none;
}

/* ── Vote control — the most-tapped thing on the site ──────────── */
.vote {
  display: inline-flex; align-items: center; gap: .45rem;
  background: var(--card); color: var(--ink-soft);
  border: 1.5px solid var(--line); border-radius: 999px;
  padding: .5rem .95rem; font: inherit; font-size: .93rem; font-weight: 700;
  cursor: pointer; min-height: 2.5rem;
  transition: transform .12s ease, background-color .15s ease, border-color .15s ease, color .15s ease;
}
.vote:hover { border-color: var(--accent); color: var(--accent); }
.vote:active { transform: scale(.94); }
.vote[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #FFFFFF;
  box-shadow: 0 4px 14px -6px var(--accent);
}
/* Their one vote is gone — on another caption. Legible rather than hidden, so
   the count is still readable and it is obvious why the arrow is inert. */
.vote.is-spent { opacity: .45; border-style: dashed; cursor: default; }

.vote .arrow { transition: transform .18s ease; }
.vote[aria-pressed="true"] .arrow { transform: translateY(-1px); }
.vote.is-working { opacity: .6; }
.vote:disabled { cursor: default; }
.vote .count { font-variant-numeric: tabular-nums; }

.count.did-bump { animation: countBump .32s ease; display: inline-block; }
@keyframes countBump {
  0%   { transform: translateY(0) scale(1); }
  40%  { transform: translateY(-.3rem) scale(1.25); }
  100% { transform: translateY(0) scale(1); }
}

.entry.did-change { animation: entryGlow 1.6s ease; }
@keyframes entryGlow {
  0%   { background-color: var(--accent-soft); }
  100% { background-color: transparent; }
}

.vote-note {
  display: block; margin-top: .5rem; font-size: .85rem; font-weight: 600;
  padding: .45rem .7rem; border-radius: var(--radius-sm);
}
.vote-note.is-warn { background: var(--warn-soft); color: var(--warn); }
.vote-note.is-good { background: var(--good-soft); color: var(--good); }

/* ── Forms ─────────────────────────────────────────────────────── */
label { display: block; font-weight: 700; font-size: .92rem; margin-bottom: .35rem; }
.input, textarea.input {
  width: 100%; font: inherit; color: var(--ink);
  background: var(--card); border: 1.5px solid var(--line);
  border-radius: var(--radius-sm); padding: .8rem .95rem;
  /* 16px minimum, or iOS Safari zooms the page when the field is focused. */
  font-size: 1rem;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus, textarea.input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
  outline: none;
}
textarea.input { resize: vertical; min-height: 5.5rem; }
.field { margin-bottom: 1rem; }
.help { color: var(--ink-faint); font-size: .85rem; margin-top: .35rem; }

/* ── Badges and notes ──────────────────────────────────────────── */
.badge {
  display: inline-block; font-size: .72rem; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase;
  padding: .3em .7em; border-radius: 999px;
}
.badge-live    { background: var(--accent); color: #FFFFFF; }
.badge-pending { background: var(--warn-soft);  color: var(--warn); }
.badge-done    { background: var(--good-soft);  color: var(--good); }

.note {
  border-radius: var(--radius-sm); padding: .9rem 1.05rem;
  border: 1px solid var(--line); background: var(--line-soft);
  font-size: .94rem; margin-bottom: 1rem;
}
.note-good { border-color: var(--good); background: var(--good-soft); color: var(--good); }

/* ── The one-time name choice ───────────────────────────────────────
   A note, not a modal. It appears in the flash slot after a first caption
   is accepted, and both answers are one tap. Nothing is blocked: ignoring
   it keeps the handle, which is the same as pressing Keep - the flag was
   already claimed when the card was built. */
.name-choice { color: var(--ink); }
.name-choice p { margin: 0 0 .5rem; }
.name-choice-lede strong { color: var(--ink); }
.name-choice-ask { font-weight: 600; color: var(--ink-soft); }
.name-choice-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .85rem; }
/* Full width each on a phone, where two pills side by side would each be too
   narrow to read the handle inside one of them. */
.name-choice-actions .btn { flex: 1 1 12rem; }
.name-choice .btn-quiet { background: var(--card); }
.note-warn { border-color: var(--warn); background: var(--warn-soft); color: var(--warn); }
/* Waiting, not winning and not wrong. Matches .badge-pending, which is what
   the contest page already shows beside a caption under review - so the two
   surfaces describing the same state look like the same state. */
.note-pending { border-color: var(--warn); background: var(--warn-soft); color: var(--warn); }
.note-pending strong { color: inherit; }

.empty { text-align: center; padding: 3rem 1rem; color: var(--ink-soft); }
.empty p { margin-bottom: .4rem; }

/* ── Share ─────────────────────────────────────────────────────── */
.share-card {
  border: 0; border-radius: var(--radius); padding: 1.15rem;
  margin-bottom: 1.25rem; color: #FFFFFF;
  background: linear-gradient(140deg, var(--accent-hot), var(--accent));
  box-shadow: var(--shadow-lg);
}
.share-lead { margin: 0 0 .85rem; }
.share-card .btn-primary {
  background: #FFFFFF; color: var(--accent); box-shadow: none;
}
.share-card .btn-quiet { color: #FFFFFF; border-color: rgba(255,255,255,.5); }
.share-card .btn-quiet:hover { background: rgba(255,255,255,.14); }
.share-actions { display: flex; gap: .6rem; flex-wrap: wrap; }

@media (prefers-reduced-motion: no-preference) {
  .share-card, .note { animation: slideIn .28s ease; }
}
@keyframes slideIn {
  from { opacity: 0; transform: translateY(-.4rem); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Sticky call to action ─────────────────────────────────────── */
.sticky-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  padding: .7rem var(--gutter) calc(.7rem + env(safe-area-inset-bottom, 0px));
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 24px -14px rgba(20,19,28,.5);
}
@media (min-width: 44rem) { .sticky-bar { display: none; } }
@media (prefers-reduced-motion: no-preference) {
  .sticky-bar { animation: slideUp .22s ease; }
}
@keyframes slideUp {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
@media (max-width: 43.99rem) {
  /* The bar is fixed to the bottom of the VIEWPORT, so what it covers is the
     bottom of the page - and the bottom of the page is the footer, not main.
     Clearing main alone left the footer's last row underneath it, which is
     where the profile links went on a phone: rendered, reachable by keyboard,
     and invisible. The clearance belongs on whatever is last.

     main keeps a small pad so its final card does not butt against the footer
     rule; it no longer needs the full bar's height, because it is not the
     thing being covered. */
  body:has(.sticky-bar:not([hidden])) main { padding-bottom: 1rem; }
  body:has(.sticky-bar:not([hidden])) .site-footer { padding-bottom: 6rem; }
}

/* ── Winners ───────────────────────────────────────────────────── */
.winner-list { display: grid; gap: 1rem; }
@media (min-width: 48rem) { .winner-list { grid-template-columns: 1fr 1fr; } }
.winner-card { overflow: hidden; }
.winner-thumb {
  width: 100%; height: 9.5rem; object-fit: cover; background: var(--line-soft);
}
.winner-caption {
  font-size: 1.08rem; font-weight: 700; color: var(--ink);
  margin: 0 0 .6rem; overflow-wrap: anywhere;
}

/* ── Legal pages ───────────────────────────────────────────────── */
.legal { max-width: 38rem; }
.legal h2 { font-size: 1.12rem; margin-top: 2rem; }
.legal ul { padding-left: 1.1rem; }
.legal li { margin-bottom: .45rem; }
.legal p, .legal li { line-height: 1.65; }

.legal-table { width: 100%; border-collapse: collapse; margin: 1rem 0 1.25rem; font-size: .93rem; }
.legal-table th, .legal-table td {
  text-align: left; vertical-align: top;
  padding: .6rem .65rem; border-bottom: 1px solid var(--line);
}
.legal-table th { color: var(--ink-soft); font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; }
.legal-table td:first-child { font-weight: 700; width: 42%; }

.check-list { list-style: none; padding: 0; margin: 0 0 1.5rem; }
.check-list li {
  padding: .75rem 0 .75rem 1.7rem; position: relative;
  border-top: 1px solid var(--line-soft);
}
.check-list li:first-child { border-top: 0; }
.check-list li::before {
  content: "✓"; position: absolute; left: 0; top: .75rem;
  color: var(--good); font-weight: 800;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
