/* WhatsApp's own dark palette, so the site and the sticker export page that
   sticker_creator.py generates read as one product. */
:root {
  --bg: #0b141a;
  --panel: #111b21;
  --panel-2: #202c33;
  --line: #2a3942;
  --text: #e9edef;
  --muted: #8696a0;
  --green: #25d366;
  --green-ink: #06251a;
  --red: #f15c6d;
  --radius: 18px;
}

* { box-sizing: border-box; }

/* The browser's own `[hidden] { display: none }` is a plain element rule, so
   any class here that sets `display` silently outranks it -- which is how the
   signed-out page ended up showing an empty avatar circle and a blank account
   row next to the sign-in button. `hidden` means hidden, whatever else a class
   asks for. */
[hidden] { display: none !important; }

/* The base colour goes on the root element, so it is painted across the whole
   canvas whatever the body happens to measure. Without it the body's background
   was propagated to the canvas and then *repeated* past the body box -- which
   drew a second copy of the glow starting again lower down, and the seam
   between them was a visible band across the foot of the page. It showed up
   most in landscape, where the viewport is short and wide and the body ends
   well inside it. */
html {
  background-color: var(--bg);
}

body {
  margin: 0;
  padding: 2.5rem 1.25rem 4rem;
  /* Never shorter than the screen, so the layers below always have the whole
     viewport to cover on a page with little on it. */
  min-height: 100vh;
  /* Two layers: the halftone dot grid a printed comic panel is made of, over
     the glow this page always had. The dots are what stop the background
     reading as a plain dark template -- they are barely visible up close and
     the eye still registers the texture.

     The dots tile, so they repeat. The glow does not: it is one band pinned to
     the top of the page at a fixed height rather than a share of the body's,
     so it looks the same on a short page and a long one, and everything below
     it is the flat colour the root element is already painting -- which is the
     same colour the glow fades into, so there is no edge to see. */
  background-image:
    radial-gradient(rgba(255, 255, 255, .045) 1px, transparent 1px),
    radial-gradient(circle at 50% -10%, #16323c 0%, var(--bg) 55%);
  background-size: 22px 22px, 100% 760px;
  background-repeat: repeat, no-repeat;
  background-position: 0 0, 0 0;
  color: var(--text);
  /* The emoji faces are last on purpose: they are only ever reached for
     characters the text faces do not cover, and without them those characters
     render as an empty box. */
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
               "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
  line-height: 1.5;
}

/* The padding is the stickers' band: they are absolutely positioned, and
   without a reserved strip above the title they land on top of it as soon as
   the title wraps to two lines on a narrow screen. */
header { text-align: center; margin-bottom: 2rem; position: relative; padding-top: 2.4rem; }

/* The meme caption look, which is Impact in white with a black outline. Drawn
   with a stroke where the browser has one and with four offset shadows where it
   does not, because a caption without the outline is just bold text and the
   whole joke of it is the outline. */
h1 {
  margin: 0 0 .5rem;
  font-family: Impact, Haettenschweiler, "Arial Narrow Bold", "Anton", sans-serif;
  font-size: clamp(2.1rem, 8vw, 3.4rem);
  font-weight: 400;              /* Impact is already heavy; bolding smears it */
  letter-spacing: .01em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 2px 2px 0 #000, -2px 2px 0 #000, 2px -2px 0 #000, -2px -2px 0 #000;
  transform: rotate(-1.2deg);
}
@supports (-webkit-text-stroke: 1px black) {
  h1 { -webkit-text-stroke: 2.5px #000; paint-order: stroke fill; text-shadow: none; }
}
h2 { margin: 0 0 .35rem; font-size: 1.15rem; }

/* --- the brandmark -------------------------------------------------------
   The title, once the page has this hour's logo meme: the wordmark as a
   cut-out image with somebody's face in its bubble, in place of the type
   above. Sized by height rather than width -- the cut-out is trimmed to its
   own artwork, so its width is whatever that hour's face made it, and pinning
   the height is what keeps the heading the same size all day. */

.brandmark img {
  display: block;
  margin: 0 auto .6rem;
  /* Sized by width, not by height. The wordmark is two lines of type inside the
     picture, so a height that matches the old single-line heading draws
     something half its width and the title stops being the loudest thing on
     the page -- which, on a page whose whole job is memes, it has to be. */
  width: clamp(17rem, 84vw, 32rem);
  height: auto;
  /* The artwork carries its own black outline, so the shadow here is only what
     lifts it off the page -- the same lift the die-cut stickers get. */
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .45));
}

/* The h1's own type styling is for the *words*: a stroke and a shadow applied
   to no visible text, over a line box a line and a half taller than the image
   standing in it. The tilt is the exception and stays -- it is the heading's
   character, and the cut-out wears it as well as the type did. */
.brandmark.has-icon {
  -webkit-text-stroke: 0;
  text-shadow: none;
  line-height: 0;
}

/* Taken out of the picture, not thrown away: this is still the accessible name
   of the heading, and still what is read aloud. */
.brandmark.has-icon .brandmark-text {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* --- stickers ------------------------------------------------------------
   The one decorative idea on this page: a small tilted label with a thick
   white border and a shadow, the way a die-cut sticker sits on a surface. Used
   sparingly -- two either side of the title, one on the results card, one on
   the biggest plan -- because the point is that they look stuck on, and a page
   covered in them just looks busy. */

.tag {
  position: absolute; z-index: 1;
  display: inline-block; padding: .2rem .6rem;
  background: var(--green); color: var(--green-ink);
  border: 3px solid #fff; border-radius: 8px;
  font-family: Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif;
  font-size: .85rem; letter-spacing: .06em; text-transform: uppercase;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .45);
  pointer-events: none;
}
/* Real templates strewn across the background, each one the same die-cut
   sticker as the captions -- white border, shadow -- but scattered wide instead
   of fanned in a row: far apart, at wildly different sizes, tilted every which
   way, as though a handful had been thrown at the page and left where it fell.

   The layer sits at `z-index: 0`, above the body's background and below every
   piece of content, and takes no pointer events -- so however big a sticker
   gets it can never cover a button or eat a click. It is emphatically *not*
   `z-index: -1`: a negative index paints behind the body's own background, and
   the body paints an opaque glow across the top 760px of itself, so seven of
   the eight stickers were hidden under it and only the one that happened to
   fall past the band ever showed. That is what made the memes look like they
   only existed at the bottom of the page.

   Fixed rather than absolute: the page is several screens tall and stickers
   pinned to the document would leave whole stretches of it bare. */
.decor-field {
  position: fixed; inset: 0; z-index: 0;
  overflow: hidden;              /* the ones off the edges are clipped, not scrolled to */
  pointer-events: none;
}

/* And the other half of that arrangement: content has to be given a layer of
   its own, or it shares layer 0 with the stickers and a big one drawn later in
   the document would sit on top of a card. */
nav.account-bar, header, main { position: relative; z-index: 1; }

/* Each sticker's own four numbers. The defaults are the middle of the range, so
   a ninth template -- should the server ever send one -- lands somewhere
   sensible instead of stacking at the origin. */
.decor {
  --x: 50vw; --y: 50vh; --size: 90px; --rot: 0deg; --fade: .5; --far: 0px;
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--size); height: var(--size); padding: 3px;
  background: #fff; border-radius: 10px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
  /* Centred on its point, so a sticker placed past the edge hangs off it by
     half its width whatever size it happens to be. */
  transform: translate(-50%, -50%) rotate(var(--rot));
  opacity: var(--fade);
  filter: blur(var(--far));
}
.decor img { width: 100%; height: 100%; object-fit: cover; border-radius: 7px; display: block; }

/* All eight in one band across the top, rather than trailing down the whole
   page. Read left to right they arc from big and sharp at the edges to small,
   dim and slightly out of focus toward the middle -- which is both the depth
   trick and the reason the title stays legible: whatever falls behind the
   headline is the faintest of them. Nothing is placed below the band, so the
   memes are one part of the page and the rest of it is clean. */
.decor:nth-child(1) { --x:  5vw; --y: 13vh; --size: 124px; --rot: -13deg; --fade: .55; }
.decor:nth-child(2) { --x: 17vw; --y: 25vh; --size:  86px; --rot:   9deg; --fade: .42; }
.decor:nth-child(3) { --x: 28vw; --y:  8vh; --size:  62px; --rot:  -6deg; --fade: .30; --far: .6px; }
.decor:nth-child(4) { --x: 40vw; --y: 22vh; --size:  50px; --rot:  15deg; --fade: .20; --far: 1px; }
.decor:nth-child(5) { --x: 61vw; --y:  9vh; --size:  54px; --rot: -11deg; --fade: .22; --far: 1px; }
.decor:nth-child(6) { --x: 73vw; --y: 24vh; --size:  70px; --rot:   6deg; --fade: .34; --far: .6px; }
.decor:nth-child(7) { --x: 85vw; --y: 10vh; --size:  94px; --rot:  -9deg; --fade: .46; }
.decor:nth-child(8) { --x: 95vw; --y: 26vh; --size: 118px; --rot:  12deg; --fade: .55; }

/* Below about 1100px the 46rem of content has eaten the gutters the band lives
   in. It stays one band at the top, but pressed out to the edges and shrunk,
   and the four that would land squarely behind the headline go entirely --
   a sticker mostly hidden behind a card is not decoration, it is a sliver. */
@media (max-width: 1100px) {
  .decor:nth-child(1) { --x:  4vw; --y: 10vh; --size:  84px; --fade: .45; }
  .decor:nth-child(2) { --x: 16vw; --y: 24vh; --size:  60px; --fade: .34; }
  .decor:nth-child(7) { --x: 84vw; --y: 11vh; --size:  66px; --fade: .38; }
  .decor:nth-child(8) { --x: 96vw; --y: 23vh; --size:  86px; --fade: .45; }
  .decor:nth-child(3), .decor:nth-child(4),
  .decor:nth-child(5), .decor:nth-child(6) { display: none; }
}

.tag-corner { top: -.7rem; right: -.5rem; transform: rotate(6deg); background: #ffd34d; }
.tag-plan   { top: -.7rem; right: -.4rem; transform: rotate(5deg); }

/* A card that carries a corner sticker has to be the sticker's frame of
   reference, and must not clip it. */
.stickered, .plan { position: relative; overflow: visible; }

@media (prefers-reduced-motion: no-preference) {
  .tag { transition: transform .15s; }
  .stickered:hover .tag-corner { transform: rotate(2deg) scale(1.05); }
}
.sub { margin: 0; color: var(--muted); font-size: .95rem; }
.hint { margin: .75rem 0 0; color: var(--muted); font-size: .84rem; }
.center { text-align: center; }
code { background: var(--panel-2); padding: .1em .35em; border-radius: 5px; font-size: .9em; }

main { max-width: 46rem; margin: 0 auto; display: flex; flex-direction: column; gap: 1.25rem; }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}

/* --- the boot skeleton --------------------------------------------------
   Shown only while the page has no data yet, and taken down by the boot code
   the moment there is something real to draw. It is deliberately the shape of
   the card that follows it, so nothing jumps when the real one arrives. */

.skeleton { display: none; }
body.booting .skeleton { display: block; }
body.booting main > .card, body.booting main > section { display: none !important; }

.skel {
  border-radius: 10px;
  background: linear-gradient(100deg, var(--panel-2) 30%, #2c3d47 50%, var(--panel-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
.skel-title { height: 1.4rem; width: 45%; margin-bottom: 1.1rem; }
.skel-box { height: 8.5rem; margin-bottom: 1.1rem; border-radius: var(--radius); }
.skel-line { height: .85rem; margin-bottom: .6rem; }
.skel-line.short { width: 60%; }

@keyframes shimmer { to { background-position: -220% 0; } }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }

/* --- dropzone ---------------------------------------------------------- */

#dropzone {
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  padding: 2.25rem 1.25rem;
  text-align: center;
  cursor: pointer;
  transition: border-color .15s, background .15s, transform .15s;
}
#dropzone:hover, #dropzone:focus-visible { border-color: var(--green); background: rgba(37, 211, 102, .04); outline: none; }
#dropzone.over { border-color: var(--green); background: rgba(37, 211, 102, .09); transform: scale(1.01); }
#dropzone.full { opacity: .5; cursor: not-allowed; }

.dz-icon { display: block; width: 2.4rem; height: 2.4rem; margin: 0 auto .6rem; color: var(--muted); }
#dropzone:hover .dz-icon, #dropzone:focus-visible .dz-icon, #dropzone.over .dz-icon { color: var(--green); }
.dz-title { margin: 0 0 .35rem; font-size: 1.05rem; }
.dz-hint { margin: 0; color: var(--muted); font-size: .85rem; }

/* --- thumbnails -------------------------------------------------------- */

.thumbs { list-style: none; display: flex; gap: .85rem; padding: 0; margin: 1.1rem 0 0; flex-wrap: wrap; }
.thumbs:empty { margin: 0; }
/* Staged photos get the same sticker treatment as the finished memes, so the
   before and the after look like the same kind of object. */
.thumbs li {
  position: relative; width: 108px;
  background: #fff; border-radius: 12px; padding: 6px; text-align: center;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .35);
  transform: rotate(-2deg); transition: transform .18s;
}
.thumbs li:nth-child(even) { transform: rotate(2.5deg); }
.thumbs li:hover, .thumbs li:nth-child(even):hover { transform: rotate(0deg); }
.thumbs img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 10px; display: block; }
.thumbs .who { display: block; font-size: .72rem; color: #55636b; font-weight: 600; margin-top: .3rem; }
.thumbs button {
  position: absolute; top: -7px; right: -7px; width: 24px; height: 24px;
  border: 0; border-radius: 50%; background: var(--red); color: #fff;
  font-size: 15px; line-height: 22px; cursor: pointer; padding: 0;
}

/* --- buttons ----------------------------------------------------------- */

/* inline-flex rather than inline-block: every button here is an icon next to a
   label, and on a baseline the icon sat low and the pair drifted apart at
   different font sizes. Centring them in a flex line makes the icon sit right
   at any size and drops the per-glyph vertical-align tweaks. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  border: 0; cursor: pointer; text-decoration: none;
  font-family: inherit; font-size: 1rem; font-weight: 700; line-height: 1.2;
  padding: .95rem 1.6rem; border-radius: 999px;
  transition: transform .1s, box-shadow .15s, opacity .15s;
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .35; cursor: not-allowed; box-shadow: none; }

#create { display: flex; width: 100%; margin-top: 1.25rem; }

.btn-primary { background: var(--green); color: var(--green-ink); box-shadow: 0 6px 20px rgba(37,211,102,.25); }
.btn-whatsapp {
  background: var(--green); color: var(--green-ink); font-size: 1.1rem;
  padding: 1.1rem 2rem; box-shadow: 0 8px 26px rgba(37,211,102,.3);
}
.btn-ghost { background: var(--panel-2); color: var(--text); font-weight: 600; }
/* The front page's one control. Full width and a size up from the rest,
   because it is the only thing on that card a visitor is being asked to do,
   and on a phone a centred pill button reads as one option among several. */
.btn-hero { display: flex; width: 100%; font-size: 1.1rem; padding: 1.1rem 2rem;
            margin: .25rem 0 .35rem; }
.wa-glyph { flex: none; width: 1.25em; height: 1.25em; }

.linkish { background: none; border: 0; color: var(--muted); font: inherit; font-size: .85rem;
           text-decoration: underline; cursor: pointer; }
a.linkish:hover { color: var(--text); }
.restart { text-align: center; margin: 0; }

/* --- progress ---------------------------------------------------------- */

#step-progress { text-align: center; }

/* Three meme-shaped cards riffling past each other. Sized in one place: the
   card width drives the deck width, so the three stay centred under the label
   whatever the screen. */
.riffle {
  position: relative; width: 132px; height: 96px; margin: .4rem auto 1.4rem;
}
.riffle-card {
  position: absolute; top: 0; left: 50%; width: 68px; height: 92px;
  margin-left: -34px; border-radius: 12px;
  background: linear-gradient(150deg, var(--panel-2), #16323c);
  border: 1px solid var(--line);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .35);
  animation: riffle 2.4s cubic-bezier(.45, .05, .3, 1) infinite;
}
/* The deck reads as a deck only because the three are out of phase; one third
   of the cycle each is what keeps a card always arriving as another leaves. */
.riffle-card:nth-child(2) { animation-delay: -.8s; }
.riffle-card:nth-child(3) { animation-delay: -1.6s; }

@keyframes riffle {
  0%   { transform: translateX(-46px) rotate(-13deg) scale(.9); opacity: .25; }
  35%  { transform: translateX(0) rotate(0deg) scale(1.06); opacity: 1;
         border-color: var(--green); }
  70%  { transform: translateX(46px) rotate(13deg) scale(.9); opacity: .25; }
  71%, 100% { transform: translateX(-46px) rotate(-13deg) scale(.9); opacity: 0; }
}

/* Motion is the whole point of this element, so reduced motion gets a still
   deck rather than a slower one -- a slow riffle is more distracting, not less. */
@media (prefers-reduced-motion: reduce) {
  .riffle-card { animation: none; opacity: .5; }
  .riffle-card:nth-child(1) { transform: translateX(-40px) rotate(-11deg); }
  .riffle-card:nth-child(3) { transform: translateX(40px) rotate(11deg); }
  .riffle-card:nth-child(2) { opacity: 1; border-color: var(--green); }
}

.progress-label { margin: 0 0 .9rem; font-weight: 600; }
.eta { margin: .8rem 0 0; color: var(--green); font-size: .9rem; font-weight: 600;
       font-variant-numeric: tabular-nums; }
#progress-tip { min-height: 2.4em; }
.bar { height: 8px; background: var(--panel-2); border-radius: 999px; overflow: hidden; }
.bar-fill { height: 100%; width: 0; background: var(--green); border-radius: 999px; transition: width .4s ease; }

/* --- previews ---------------------------------------------------------- */

.previews { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
            gap: 1.25rem; margin-top: 1.3rem; }
/* Framed and tilted like a sticker on a page rather than a thumbnail in a
   grid. Alternate tilts so a row of them reads as scattered, not as a wonky
   grid, and straighten on hover so the meme itself is easy to look at. */
.preview {
  background: #fff; border-radius: 12px; overflow: hidden;
  padding: 6px 6px 0; box-shadow: 0 8px 22px rgba(0, 0, 0, .4);
  transform: rotate(-1.5deg); transition: transform .18s;
}
.preview:nth-child(even) { transform: rotate(1.8deg); }
.preview:hover, .preview:nth-child(even):hover { transform: rotate(0deg) scale(1.02); }
.preview img.meme { width: 100%; display: block; background: #000; border-radius: 8px; }
/* The caption strip under a framed meme sits on the white of the frame, so it
   takes dark ink -- the muted grey the rest of the page uses is invisible here. */
.preview .who-row { display: flex; align-items: center; gap: .55rem; padding: .55rem .35rem .5rem; }
.preview .who-row img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.preview .who-row span { font-size: .82rem; color: #55636b; font-weight: 600; }

/* --- the finished stickers ---------------------------------------------- */

/* Once a run has finished, the previews stop being a grid of full-width cards
   and become a row of small stickers, one per person, centred. Small because
   the last screen is about the set as a whole; laid out plainly, with real
   gaps rather than overlap, because each sticker is there to be looked at. */
.previews.small {
  display: flex; flex-wrap: wrap; gap: .9rem;
  align-items: flex-start; justify-content: center;
  margin-top: 1.6rem; padding-bottom: .6rem;
}
.previews.small .preview {
  flex: none; width: 8.5rem;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .45);
}
/* Straightened and lifted, because at this size a tilted sticker is hard to
   read and the caption under it harder still. */
.previews.small .preview:hover,
.previews.small .preview:focus-within {
  transform: rotate(0deg) scale(1.12) translateY(-4px); z-index: 20;
}
.previews.small .preview .who-row { padding: .4rem .3rem .35rem; gap: .4rem; }
.previews.small .preview .who-row img { width: 20px; height: 20px; }
.previews.small .preview .who-row span { font-size: .72rem; }

@media (max-width: 560px) {
  /* Two per row on a phone, rather than one wide one. */
  .previews.small { gap: .6rem; }
  .previews.small .preview { width: 7.25rem; }
}

/* --- pack -------------------------------------------------------------- */

#pack-ready { margin-top: 1.4rem; padding-top: 1.4rem; border-top: 1px solid var(--line); }
.tray { width: 84px; height: 84px; border-radius: 20px; background: var(--panel-2); }
.pack-title { margin: .7rem 0 .3rem; font-size: 1.1rem; font-weight: 700; }
.pack-actions { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: center; }

/* --- naming a pack ------------------------------------------------------ */

.naming { text-align: left; margin-bottom: 1.3rem; display: flex;
          flex-direction: column; gap: 1rem; }
.field { display: block; }
.field-label { display: block; font-size: .8rem; color: var(--muted);
               text-transform: uppercase; letter-spacing: .06em; margin-bottom: .4rem; }
/* Every text box on the site: the pack's name, a creator's code, a receipt's
   transaction ID. Written against the wrapper rather than listed by id, so a
   field added to a dialog tomorrow is dressed like the rest of them instead of
   arriving as a raw browser input -- which is exactly what the receipt box was
   before this rule stopped naming names. */
.field input[type="text"] {
  width: 100%; padding: .8rem 1rem; border-radius: 12px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
  font: inherit; font-size: 1rem;
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}
.field input[type="text"]::placeholder { color: var(--muted); opacity: .65; }
/* A ring rather than only a border: on this background a one-pixel colour
   change is easy to miss, and the field being focused is the whole answer to
   "am I typing into the right box". */
.field input[type="text"]:focus {
  outline: none; border-color: var(--green); background: var(--panel);
  box-shadow: 0 0 0 3px rgba(37, 211, 102, .18);
}
.field input[type="text"]:disabled { opacity: .55; cursor: not-allowed; }

.cover-row { display: flex; align-items: center; gap: .9rem; }
.cover-preview {
  position: relative; flex: none; width: 72px; height: 72px; border-radius: 18px;
  overflow: hidden; background: var(--panel-2); border: 1px solid var(--line);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: .7rem; text-align: center; padding: .3rem;
}
.cover-preview img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cover-actions { display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; }

/* The locked state of the naming block on a free pack: greyed, but still shown
   with its controls, because "you could have named this" is the message. */
.naming.locked #pack-title, .naming.locked .cover-row { opacity: .5; pointer-events: none; }
.lock {
  margin: 0; padding: .8rem 1rem; border-radius: 12px; font-size: .85rem;
  background: rgba(255, 183, 77, .1); border: 1px solid rgba(255, 183, 77, .35);
  color: #ffe6bd;
}
.brandname { color: var(--green); }


.error {
  margin: 0; padding: .95rem 1.15rem; border-radius: 12px;
  background: rgba(241, 92, 109, .12); border: 1px solid rgba(241, 92, 109, .4);
  color: #ffd7dc; font-size: .9rem;
}

/* Service status, not an error: the site is fine, it just cannot sell or
   generate at this moment. Amber rather than red, and it stays put. */
.offline {
  margin: 0; padding: .95rem 1.15rem; border-radius: 12px;
  background: rgba(255, 183, 77, .12); border: 1px solid rgba(255, 183, 77, .45);
  color: #ffe6bd; font-size: .9rem; font-weight: 600;
}

.notice {
  margin: 0; padding: .95rem 1.15rem; border-radius: 12px;
  background: rgba(37, 211, 102, .1); border: 1px solid rgba(37, 211, 102, .35);
  color: #d6ffe6; font-size: .9rem;
}

/* --- account bar ------------------------------------------------------- */

.account-bar {
  max-width: 46rem; margin: -1rem auto 1.75rem;
  display: flex; justify-content: space-between; align-items: center; gap: .75rem;
  min-height: 2.4rem; flex-wrap: wrap;
}
.nav-links { display: flex; align-items: center; gap: .35rem; }
.navlink {
  display: inline-flex; align-items: center; gap: .4rem;
  background: none; border: 0; padding: .4rem .6rem; border-radius: 999px;
  color: var(--muted); font: inherit; font-size: .85rem; cursor: pointer;
  /* A label never breaks across two lines. "Sticker packs" splitting under its
     own glyph made the row look broken rather than tight; when the row runs
     out of width a whole button moves down instead (see `.nav-links`). */
  white-space: nowrap;
}
.navlink:hover, .navlink:focus-visible { background: var(--panel-2); color: var(--text); outline: none; }
.nav-glyph { flex: none; width: 1.1em; height: 1.1em; }
.pill {
  background: var(--green); color: var(--green-ink);
  padding: .05rem .45rem; border-radius: 999px; font-size: .72rem; font-weight: 700;
}
.account { display: flex; align-items: center; gap: .6rem; font-size: .85rem; }
/* The avatar is a filled circle first and a photo second. A src-less <img>
   draws a broken-image box, and Google's CDN declines to serve some profile
   pictures to third-party origins, so the initial underneath is what actually
   shows most of the time -- the photo covers it only once it has loaded. */
.avatar {
  position: relative; flex: none; width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; overflow: hidden;
  background: var(--panel-2); color: var(--muted);
  font-size: .8rem; font-weight: 700; line-height: 1;
}
.avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.avatar img[hidden] { display: none; }
.btn-small { padding: .5rem 1rem; font-size: .85rem; }
/* Google's sign-in branding: white field, their own grey hairline, Roboto if
   the machine has it, and the label at medium weight rather than the bold the
   rest of the site uses. The button was inheriting all three of those from
   .btn, which is what made it look like a knock-off. */
.btn-google {
  background: #fff; color: #1f1f1f;
  border: 1px solid #747775; font-weight: 500;
  font-family: Roboto, -apple-system, "Segoe UI", Arial, sans-serif;
}
.btn-google:hover { background: #f7f8f8; }
/* Google's own mark, drawn as SVG rather than approximated: their guidelines
   require the real logo on a sign-in button. No background of its own --
   .btn-google supplies the white field. `flex: none` keeps the square square;
   without it the flex line squashed the G into an oval on a narrow screen. */
.g-glyph { flex: none; width: 1.15em; height: 1.15em; }

/* The only control on the page when nobody is signed in, so it is sized like a
   primary action rather than like the small one up in the corner. */
#signin-hero { margin-top: 1.35rem; font-size: 1.05rem; padding: 1rem 1.9rem; }

/* --- plan cards -------------------------------------------------------- */

/* A bulleted fact list. The marker is a drawn tick rather than a disc: these
   lists say what you get, and a disc reads as prose that happens to be indented.
   Drawn as a background image so it needs no extra element per line and no
   emoji font. */
.ticks { list-style: none; margin: 0; padding: 0; display: flex;
         flex-direction: column; gap: .3rem; }
.ticks li {
  position: relative; padding-left: 1.5rem; font-size: .86rem; line-height: 1.45;
  background-repeat: no-repeat; background-position: 0 .32em; background-size: 1rem 1rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2325d366' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12.5l5.5 5.5L20 6.5'/%3E%3C/svg%3E");
}

.pack-facts { margin: .5rem 0 1rem; text-align: left; display: inline-flex; color: var(--muted); }

/* Plain bullets, not ticks: these are things to know, not things you get. */
.notes { list-style: disc; margin: .75rem 0 0; padding-left: 1.15rem;
         text-align: left; display: flex; flex-direction: column; gap: .35rem; }

.plans {
  display: grid; gap: .9rem; margin-top: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
}
.plan {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 14px;
  padding: 1.1rem; display: flex; flex-direction: column; gap: .35rem;
}
.plan h3 { margin: 0; font-size: 1rem; }
.plan-price { margin: 0; font-size: 1.7rem; font-weight: 700; letter-spacing: -.02em; }
/* flex:1 so every card's button sits on the same line however many features
   the plan above it lists. */
.plan-features { margin: .3rem 0 .8rem; flex: 1; color: var(--muted); }
.plan-features li { font-size: .82rem; }
.plan-note { margin: 0; font-size: .8rem; color: var(--muted); font-style: italic; }
/* What this account already holds of *this* pack, stuck on the card it is
   about. It replaces the one count that used to sit in the account bar, which
   said how many packs were ready and never which ones. Left-hand corner, since
   the "Most memes" flash owns the right. */
/* How long the free pack is still there for. Amber, the colour this page keeps
   for "pay attention", and on the right where the "Most memes" flash sits on
   the card next door -- the two never appear on the same card, so they can
   share a corner. `tag-owned` has the left. */
.tag-offer { top: -.7rem; right: -.5rem; transform: rotate(6deg); background: #ffd34d; }
/* The same countdown said again under the sign-in button, where it is a clause
   in a sentence rather than a sticker. */
.offer-countdown { color: var(--green); }

.tag-owned {
  top: -.7rem; left: -.4rem; transform: rotate(-5deg);
  background: var(--panel); color: var(--green);
  border: 1px solid var(--green);
}
/* The pack the next job will spend. A hairline rather than a fill: it marks one
   of three cards that are otherwise identical in weight, and a filled card
   would read as the one being sold hardest. */
.plan-next { border-color: var(--green); box-shadow: 0 0 0 1px rgba(37, 211, 102, .35); }
.plan .btn { width: 100%; text-align: center; }

/* --- dialogs ----------------------------------------------------------- */

dialog {
  width: min(32rem, calc(100vw - 2rem));
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; font: inherit;
}
dialog::backdrop { background: rgba(0, 0, 0, .6); }
.dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.dialog-close {
  background: none; border: 0; color: var(--muted); cursor: pointer;
  font-size: 1.5rem; line-height: 1; padding: 0 .25rem;
}
.dialog-close:hover { color: var(--text); }
.brand-line { margin: .8rem 0 .2rem; font-size: 1.3rem; }
.about-story { margin: .9rem 0 0; font-size: .92rem; line-height: 1.6; color: var(--text); }
/* Paragraphs, spaced by a gap rather than by their own margins, so the block
   keeps the same distance from the tagline above it however many there are. */
.about-story p { margin: 0; }
.about-story p + p { margin-top: .7rem; }
/* Wraps rather than scrolls: a fourth account one day should drop onto a second
   line, not push the dialog wider than the phone it is open on. */
.socials { list-style: none; margin: 1rem 0 0; padding: 0;
           display: flex; flex-wrap: wrap; gap: .5rem; }
/* Picture, then network, then handle -- tighter than a plain button's spacing,
   because these three read as one label rather than as an icon and a word. */
.social { gap: .45rem; }
.social .nav-glyph { color: var(--muted); }
/* The profile picture on an account link. Smaller than the account bar's, and
   pulled in at the leading edge so it reads as part of the button rather than
   as an image sitting next to one. */
.avatar-sm { width: 22px; height: 22px; font-size: .7rem; margin-left: -.25rem; }

/* --- creator codes ------------------------------------------------------ */

/* The two dialog forms -- a code, a receipt -- are the same shape: label, box,
   one button under it. */
#code-form, #claim-form { margin: 1.1rem 0 0; display: flex; flex-direction: column; gap: .8rem; }
/* Codes are printed and said in upper case, so they are typed back in it
   whatever the keyboard did. A PayPal transaction ID is upper-case too, and
   long enough that the eye needs the letters apart to check it against a
   receipt -- so it is set in the tabular figures the rest of the site does not
   need. The server does not care either way; both are normalised there. */
#code-input, #claim-input { text-transform: uppercase; letter-spacing: .08em; }
#claim-input { font-variant-numeric: tabular-nums; }
#code-form .btn, #claim-form .btn { align-self: flex-start; }
#code-active { margin-top: 1.1rem; }
/* A held code is a success, not a warning: green with a tick, where the amber
   `.lock` block used to say the same thing in the colour the page uses for
   "careful". It is the first thing in the dialog after a code is accepted, so
   it is also the answer to "did that work?". */
.code-live {
  display: flex; align-items: flex-start; gap: .6rem;
  margin: 0; padding: .8rem 1rem; border-radius: 12px; font-size: .85rem;
  background: rgba(37, 211, 102, .1); border: 1px solid rgba(37, 211, 102, .35);
  color: #d6ffe6;
}
.code-live-tick { color: var(--green); font-weight: 700; line-height: 1.35; }
.code-active-links { display: flex; flex-wrap: wrap; gap: 1rem; margin: .6rem 0 0; }
/* Green and bold, like the links in the pack library: the row holds a link out
   to the creator and a button that undoes the code, and the two have to be
   told apart before they are pressed. */
.code-active-links a { color: var(--green); font-weight: 600; text-decoration: none; }
.code-active-links a:hover { text-decoration: underline; }
/* Said inside the dialog, under the form that caused it. */
.code-msg { margin-top: 1rem; }

/* --- the pack library --------------------------------------------------- */

.pack-list { list-style: none; margin: 1.1rem 0 0; padding: 0;
             display: flex; flex-direction: column; gap: .6rem;
             max-height: 60vh; overflow-y: auto; }
.pack-list li {
  display: flex; align-items: center; gap: .85rem;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 14px; padding: .7rem .85rem;
}
.pack-list img { flex: none; width: 48px; height: 48px; border-radius: 12px;
                 object-fit: cover; background: var(--bg); }
.pack-meta { flex: 1; min-width: 0; }
.pack-meta b { display: block; font-size: .95rem;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pack-meta span { display: block; font-size: .78rem; color: var(--muted); }
/* Whether the pack's share link is answering. Smaller than the line above it --
   it is a status, not a fact about the pack -- and green while it is open, so
   the two states are told apart at a glance rather than by reading. */
.pack-link { font-size: .72rem; }
.pack-link.live { color: var(--green); }
.pack-open { display: flex; flex-direction: column; gap: .3rem; align-items: flex-end; }
.pack-open a { font-size: .8rem; color: var(--green); text-decoration: none; font-weight: 600; }
.pack-open a:hover { text-decoration: underline; }

/* --- phones ------------------------------------------------------------- */

/* The layout above is one column on a narrow screen already, which is not the
   same as fitting one: three plan cards, each a tall stack of name, a 1.7rem
   price, five bullets and a button, is most of a phone screen per card and
   several screens of scrolling to compare them. Everything below shortens that
   without dropping a single fact. */
@media (max-width: 560px) {
  body { padding: 1.5rem .9rem 3rem; }
  main { gap: .9rem; }
  .card { padding: 1.1rem; border-radius: 14px; }

  /* The account row and the nav sat on one line and wrapped into a ragged
     two-line block. Two deliberate rows instead, nav first. */
  .account-bar { margin: -.5rem auto 1.25rem; gap: .5rem; }
  /* Three pills across a phone. Tighter padding and a slightly smaller label
     is what makes them fit on one line at 360px; on anything narrower the row
     wraps between buttons, which is why the row gap is set as well. */
  .nav-links { flex: 1 1 100%; flex-wrap: wrap; justify-content: center;
               gap: .15rem .2rem; }
  .navlink { padding: .4rem .45rem; gap: .3rem; font-size: .78rem; }
  #account-out, #account-in { flex: 1 1 100%; justify-content: center; }
  .account { justify-content: center; flex-wrap: wrap; row-gap: .3rem; }

  /* Name and price share the top line, and the button spans the full width
     under them, so a card costs roughly half the height it did. */
  .plan {
    display: grid; grid-template-columns: 1fr auto;
    align-items: baseline; column-gap: .75rem; row-gap: .25rem;
    padding: .9rem 1rem;
  }
  .plan h3 { grid-column: 1; font-size: 1.05rem; }
  .plan-price { grid-column: 2; grid-row: 1; font-size: 1.35rem; }
  .plan-features { grid-column: 1 / -1; margin: .35rem 0 .6rem; gap: .2rem; }
  .plan-features li { font-size: .8rem; padding-left: 1.35rem; background-size: .9rem .9rem; }
  .plan-note, .plan .btn { grid-column: 1 / -1; }

  /* Every action on a phone is a thumb target: full width, and the two pack
     buttons stack rather than squeezing onto one line. */
  .pack-actions { flex-direction: column; }
  .pack-actions .btn { width: 100%; }
  .btn { padding: .85rem 1.2rem; }
  .btn-whatsapp { width: 100%; font-size: 1rem; }
  #code-form .btn, #claim-form .btn { align-self: stretch; text-align: center; }

  header { margin-bottom: 1.25rem; padding-top: 2.2rem; }
  /* A phone has no gutters at all: four stickers, half off the edges, well
     faded. Enough that the background is clearly made of memes, little enough
     that it never fights the one column of content for attention. */
  .decor:nth-child(1) { --x: -1vw; --y: 12vh; --size: 74px; --fade: .3; }
  .decor:nth-child(2) { --x: 101vw; --y: 30vh; --size: 62px; --fade: .26; }
  .decor:nth-child(3) { --x: -2vw; --y: 62vh; --size: 84px; --fade: .28; }
  .decor:nth-child(4) { --x: 102vw; --y: 84vh; --size: 70px; --fade: .24; }
  .decor:nth-child(n + 5) { display: none; }
  .riffle { transform: scale(.85); margin-bottom: .9rem; }
  #dropzone { padding: 1.6rem 1rem; }
  .thumbs li { width: calc(50% - .45rem); }

  /* A dialog with a fixed 1.5rem gutter and a 60vh list runs off the bottom of
     a short phone screen; this keeps the whole thing on screen and scrolls the
     list inside it instead. */
  dialog { width: calc(100vw - 1.2rem); padding: 1.1rem; max-height: 88vh; overflow-y: auto; }
  .pack-list { max-height: none; }
  .pack-list li { flex-wrap: wrap; }
  .pack-open { flex-direction: row; gap: .9rem; width: 100%; justify-content: flex-end; }
}
