/* Clean and readable over spooky-stylised, per the web suite spec.
   A little seasonal colour, but this is a form people have to read. */

:root {
  --bg: #14121a;
  --panel: #1e1b26;
  --panel-edge: #2e2a3a;
  --ink: #ece9f1;
  --ink-dim: #a49fb4;
  --accent: #ff8a3d;
  /* Visited links. Darker than the accent but still 5.3:1 on a card, so it
     reads as "been there" rather than as a disabled link. */
  --accent-visited: #d9772f;
  --accent-ink: #1a1119;
  --good: #4ec97b;
  --bad: #ff6b6b;
  --warn: #ffc860;
  --info: #7aa2ff;
  --radius: 10px;
}

* { box-sizing: border-box; }

/* The browser's own `[hidden] { display: none }` is an attribute selector, so
   any class that sets display beats it and the element stays on screen --
   .topbar, which is a flex row, would ignore the attribute entirely.
   Everything that hides itself here uses that attribute, so it has to win. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding: 0 1rem 4rem;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* One link treatment for the whole suite: orange, a darker orange once visited,
   white on hover. Kept in :link/:visited/:hover order, because :visited and
   :hover have equal specificity and whichever comes last wins.

   Anchors that are really buttons -- .tab, .bucket-link, .ghost, the masthead
   title -- opt out by being more specific, and set their own hover below. */
a { color: var(--accent); }
a:visited { color: var(--accent-visited); }
a:hover { color: #fff; }

.wrap { max-width: 780px; margin: 0 auto; }
.wrap.narrow { max-width: 460px; }

/* --- masthead ----------------------------------------------------------- */

/* Mascot beside the sign-in button, title and tagline. Bottoms line up so the
   art sits on the same edge the first card starts from. */
.hero { display: flex; align-items: flex-end; gap: 1.75rem; padding-top: 1.5rem; }
.hero-text { flex: 1; min-width: 0; }

/* Drawn at its own pixel size -- no CSS width, so re-exporting the art at a
   different size is all it takes to resize it here (update the width/height
   attributes in apply.ejs to match, so the space is reserved before it loads). */
.hero-art {
  flex: none;
  margin-bottom: 1.25rem;
  image-rendering: pixelated;
}

/* Narrow screens give the header the whole width instead. */
@media (max-width: 620px) { .hero-art { display: none; } }

.masthead { padding: 2.5rem 0 1.25rem; }
.masthead h1 { margin: 0; font-size: 1.9rem; line-height: 1.2; }
.masthead h1 a { color: inherit; text-decoration: none; }
.tagline { margin: .4rem 0 0; color: var(--ink-dim); }

.admin-bar {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Way back to the admin dashboard and out to the public page, on every admin
   screen. Dim like the footer links -- it's navigation, not the thing anyone
   came to this page to do. */
.admin-nav { margin: .4rem 0 0; font-size: .9rem; color: var(--ink-dim); }

/* --- cards -------------------------------------------------------------- */

.card {
  background: var(--panel);
  border: 1px solid var(--panel-edge);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin-bottom: 1.25rem;
}

.card h2 { margin: 0 0 .75rem; font-size: 1.2rem; }

/* Decorative emoji in front of a card heading. aria-hidden in the markup, so
   screen readers read the heading text and nothing else. Sized off the heading
   and nudged down a hair, because emoji glyphs ride high on the text baseline. */
.card h2 .ico {
  margin-right: .75rem;
  font-size: 1.1em;
  line-height: 1;
  vertical-align: -.06em;
}
.card h3 { margin: 0 0 .4rem; font-size: 1rem; }
.card p:last-child, .card ul:last-child { margin-bottom: 0; }

.notice { border-color: var(--accent); }

/* --- the two frames ----------------------------------------------------- */

.frames { display: grid; gap: 1rem; grid-template-columns: repeat(3, 1fr); }
@media (max-width: 700px) { .frames { grid-template-columns: 1fr; } }

.frame {
  background: #17151e;
  border: 1px solid var(--panel-edge);
  border-radius: var(--radius);
  padding: 1rem;
}

/* The closed door: shown for context, but not something the artist draws. The
   dashed edge and dimmed tag are what keep it from reading as a third
   deliverable sitting beside the two that are. */
.frame-given { border-style: dashed; background: none; }
.frame-given .frame-tag { color: var(--ink-dim); }
.frame-given .frame-art { opacity: .85; }

.frame-tag {
  display: inline-block;
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: .35rem;
}

.frame ul { margin: .5rem 0 0; padding-left: 1.1rem; }
.frame li { margin-bottom: .35rem; }

/* The example drawings. Centred in the cell and capped rather than shown at
   their full 180px, so the two cards stay mostly text. max-width plus
   height:auto keeps the 1:2 ratio the examples are demonstrating, however
   narrow the cell gets.

   The edge is a box-shadow rather than a border on purpose: everything here is
   border-box, so a 1px border would come out of the image and render these at
   140x278 -- slightly off the very ratio they're meant to be showing. A shadow
   costs no layout. */
.frame-art {
  display: block;
  margin: .8rem auto;
  width: 100%;
  max-width: 140px;
  height: auto;
  border-radius: 6px;
  box-shadow: 0 0 0 1px var(--panel-edge);
}

.rules ul { margin: 0; padding-left: 1.1rem; }
.rules li { margin-bottom: .8rem; }
.rules li:last-child { margin-bottom: 0; }

/* The art specs on the profile page. Same shape as .rules, but the list sits
   under a paragraph rather than straight under the heading. */
.specs { margin: 0; padding-left: 1.1rem; }
.specs li { margin-bottom: .7rem; }
.specs li:last-child { margin-bottom: 0; }

/* --- forms -------------------------------------------------------------- */

.field { margin-bottom: 1.1rem; }
.field label { display: block; font-weight: 600; margin-bottom: .35rem; }

input[type="url"],
input[type="text"],
input[type="password"],
select,
textarea {
  width: 100%;
  padding: .6rem .7rem;
  background: #17151e;
  color: var(--ink);
  border: 1px solid var(--panel-edge);
  border-radius: 7px;
  font: inherit;
}

textarea { resize: vertical; }

input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.hint { margin: .35rem 0 0; font-size: .875rem; color: var(--ink-dim); }
.hint-inline { font-weight: 400; font-size: .85rem; color: var(--ink-dim); }
.under-button { margin-top: .8rem; }

.checks { margin: 1.25rem 0; display: grid; gap: .7rem; }

.check {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  cursor: pointer;
  font-weight: 500;
}

.check input { margin-top: .3rem; width: 1.05rem; height: 1.05rem; flex: none; accent-color: var(--accent); }

/* A .check used as a field's own control rather than inside a .checks group --
   the admin house-eligibility box. `.field label` is the more specific selector
   of the two, so without this it would win and lay the row out as a block of
   bold text with the box on its own line. */
.field label.check { display: flex; font-weight: 500; margin-bottom: 0; }

/* A house box the town's own numbers have already answered. Dimmed rather than
   hidden: what is about to be saved still matters to whoever is reading the
   card, even when there is nothing left for them to decide about it. */
.check.locked { cursor: default; color: var(--ink-dim); }
.check.locked input { accent-color: var(--ink-dim); }

button.primary, button.danger {
  padding: .7rem 1.3rem;
  border: 0;
  border-radius: 7px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

button.primary { background: var(--accent); color: var(--accent-ink); }
button.danger { background: var(--bad); color: #2a0f0f; }
button.small { padding: .45rem .9rem; font-size: .9rem; }

button.primary:disabled {
  background: #3a3646;
  color: #7b7590;
  cursor: not-allowed;
}

/* Sits above the masthead for returning applicants. Present but quiet: it
   mustn't compete with "Complete Application" for a first-time visitor. */
.topbar { display: flex; justify-content: flex-end; padding: 1.25rem 0 0; }

/* The header's third occupant: who you're signed in as, shown when nothing else
   on the page is going to say it. Sized down to sit beside the buttons it
   replaces rather than compete with the title below. */
.topbar-who { margin: 0; align-self: center; font-size: .9rem; color: var(--ink-dim); }
.topbar-who strong { color: var(--ink); }

/* Same control in two forms: a button when it starts a sign-in, a link when it
   goes to the profile page. */
button.ghost, a.ghost {
  display: inline-block;
  padding: .5rem 1rem;
  border-radius: 7px;
  border: 1px solid var(--panel-edge);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: .9rem;
  cursor: pointer;
  text-decoration: none;
}

button.ghost:hover, a.ghost:hover { border-color: var(--accent); color: var(--accent); }
button.ghost:disabled { color: var(--ink-dim); cursor: not-allowed; border-color: var(--panel-edge); }

button.linklike {
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
  padding: 0;
}

/* Matches a real link's hover. These sit inline beside actual links ("Not you?",
   Sign out), so the two behaving differently on hover would look like a bug.
   No visited state, because a button has never been anywhere. */
button.linklike:hover { color: #fff; }

.form-error {
  background: rgba(255, 107, 107, .12);
  border: 1px solid var(--bad);
  color: #ffc4c4;
  border-radius: 7px;
  padding: .6rem .75rem;
  margin: 0 0 1rem;
}

.flash {
  background: rgba(78, 201, 123, .12);
  border: 1px solid var(--good);
  color: #c2f0d3;
  border-radius: 7px;
  padding: .6rem .75rem;
}

/* Same banner, refused rather than saved. `pre-line` is load-bearing: the
   conflict message puts the other organiser's status and private notes on their
   own lines, because it is also read in a confirm() box where that is the only
   formatting there is. */
.flash.error {
  background: rgba(255, 107, 107, .12);
  border-color: var(--bad);
  color: #ffd0d0;
  white-space: pre-line;
}

.signed-in {
  background: #17151e;
  border: 1px solid var(--panel-edge);
  border-radius: 7px;
  padding: .6rem .75rem;
  margin-bottom: 1.1rem;
  font-size: .95rem;
}

/* --- applicant status --------------------------------------------------- */

.status-pill {
  display: inline-block;
  padding: .2rem .6rem;
  border-radius: 999px;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  background: #3a3646;
  color: var(--ink);
  white-space: nowrap;
}

.status-card .status-pill { margin-bottom: .75rem; }
.status-approved { border-color: var(--good); }
.status-approved .status-pill { background: var(--good); color: #0d2716; }
.status-rejected .status-pill { background: var(--bad); color: #2a0f0f; }
.status-pending .status-pill { background: var(--warn); color: #2a1f08; }

/* Admin-only: applicants never see a "Considered" pill. */
.status-pill.status-approved { background: var(--good); color: #0d2716; }
.status-pill.status-rejected { background: var(--bad); color: #2a0f0f; }
.status-pill.status-pending { background: var(--warn); color: #2a1f08; }
.status-pill.status-considered { background: var(--info); color: #0d1733; }

/* Admin-only, and deliberately quiet: an outline rather than a filled pill, so
   it reads as a footnote on the status beside it and can't be mistaken for a
   status of its own. Says the applicant hasn't been shown that pill yet. */
.held-pill {
  display: inline-block;
  padding: .2rem .55rem;
  border: 1px dashed var(--ink-dim);
  border-radius: 999px;
  font-size: .7rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-dim);
  white-space: nowrap;
}

/* --- deadline countdown -------------------------------------------------- */

/* The card carries the state class, so one variable drives the number, the bar
   and the border together and they can never disagree about how urgent it is. */
.countdown { --meter: var(--good); border-color: color-mix(in srgb, var(--meter) 45%, var(--panel-edge)); }
.countdown.state-amber { --meter: var(--warn); }
.countdown.state-red { --meter: var(--bad); }
.countdown.state-over { --meter: var(--ink-dim); }

.countdown-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.countdown-head h2 { margin: 0; }
.countdown-value { margin: 0; font-weight: 700; font-size: 1.15rem; color: var(--meter); }

.meter {
  margin-top: .9rem;
  height: .55rem;
  border-radius: 999px;
  background: #17151e;
  border: 1px solid var(--panel-edge);
  overflow: hidden;
}

/* Starts at 0 and is set inline by static/countdown.js. The transition is what
   makes a page-load fill look deliberate rather than like a layout glitch. */
.meter-fill {
  width: 0;
  height: 100%;
  background: var(--meter);
  transition: width .6s ease, background-color .4s ease;
}

@media (prefers-reduced-motion: reduce) { .meter-fill { transition: none; } }

.summary { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 0; }
.summary dt { color: var(--ink-dim); }
.summary dd { margin: 0; overflow-wrap: anywhere; }

/* The numbered-step rules that used to live here went with the shared-password
   login: organiser access is now one step, so there are no steps to number. */

/* The call to action inside the approved card, filled in that card's own green
   rather than the site accent. Scoped to .status-approved deliberately: the
   colour only reads as deliberate where the border beside it already carries
   it, and an orange button here would be a second accent competing with the
   status the card exists to announce.

   Colour and ink are the same pair the Approved pill uses a few rules down, so
   the two can't drift apart. The :visited rule is not optional: the global
   `a:visited` would otherwise repaint this label orange the moment someone has
   followed it once. */
.status-approved a.action {
  display: inline-block;
  padding: .7rem 1.3rem;
  border-radius: 7px;
  background: var(--good);
  color: #0d2716;
  font-weight: 700;
  text-decoration: none;
}

.status-approved a.action:visited { color: #0d2716; }
.status-approved a.action:hover { background: #6bd894; color: #0d2716; }

/* --- the character-image preview tool ------------------------------------ */

/* Columns are capped rather than sharing the card's full width: at 1:2 a
   350px-wide box would stand 700px tall and push everything else off the
   screen. 190px gives a box a little taller than the example art on the
   landing page, which is about as big as a thumbnail needs to be. */
.pickers {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(2, minmax(0, 190px));
  margin-bottom: .5rem;
}

@media (max-width: 560px) { .pickers { grid-template-columns: minmax(0, 190px); } }

/* Drawn at the 1:2 doorway ratio, so a frame that isn't 1:2 shows up as
   letterboxing in the box before the animation is even run. The image inside
   is `contain`, unlike the overlay's `fill`: this box is showing an artist
   their own file, so it must not be the thing that distorts it. The squashing
   belongs in the preview, where the game does it. */
.pickbox {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 2;
  padding: .75rem;
  border: 2px dashed var(--panel-edge);
  border-radius: var(--radius);
  background: #17151e;
  color: var(--ink-dim);
  text-align: center;
  cursor: pointer;
  overflow: hidden;
}

.pickbox:hover { border-color: var(--accent); color: var(--accent); }

/* Held while a file is over the box. Solid rather than dashed, and tinted, so
   the drop target is unmistakable even when the box already holds a thumbnail
   and the border is the only part of it still showing. */
.pickbox.dragover {
  border-style: solid;
  border-color: var(--accent);
  background: rgba(255, 138, 61, .12);
  color: var(--accent);
}

.pickbox-sub { display: block; margin-top: .3rem; font-size: .8rem; opacity: .8; }

/* The file input is .sr-only rather than hidden, so it still takes focus; this
   is what makes that focus visible, since the input itself has no box to ring. */
.pickbox:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Fills the box rather than sitting at its natural size in the middle of it:
   most submissions are far bigger than this thumbnail, but the examples people
   test with are often smaller, and a 180px drawing marooned in a 190px box
   reads as a broken layout. */
.pickbox-art { width: 100%; height: 100%; object-fit: contain; }

/* A note that has turned into a complaint about the file: still a hint in size
   and position, but the wrong-ratio and too-small cases are the ones people
   need to catch, so they don't stay grey. */
.note-warn { color: var(--warn); }

/* No border or padding of its own: this is a .field that happens to need a
   <fieldset> so the radio group has a real legend. */
.pick-type { border: 0; padding: 0; margin: 1.25rem 0 1.1rem; min-width: 0; }
.pick-type legend { padding: 0; font-weight: 600; }
.pick-type .checks { margin: .5rem 0 0; }

/* --- admin -------------------------------------------------------------- */

.bucket-links { display: grid; gap: .75rem; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 760px) { .bucket-links { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .bucket-links { grid-template-columns: 1fr; } }

.bucket-link {
  display: block;
  padding: 1.1rem;
  background: #17151e;
  border: 1px solid var(--panel-edge);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--ink);
  text-align: center;
}

/* Colour restated, not just the border: without it the global a:hover would
   reach in here and repaint the label. */
.bucket-link:hover { border-color: var(--accent); color: var(--ink); }
.bucket-count { display: block; font-size: 2rem; font-weight: 700; color: var(--accent); line-height: 1.1; }

/* Every line in a tile gets its own row -- the count, the label, and each half
   of the housing split. Inline, they run together into one wrapped paragraph
   and you can't tell the label from the numbers under it. */
.bucket-link span { display: block; }

/* The house/apartment split under a bucket's total. Small and grey: the number
   above it is the headline, and this is the breakdown you go looking for once
   the headline has your attention. Not on the rejected tile. */
.bucket-housing {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--panel-edge);
  font-size: .72rem;
  line-height: 1.5;
  /* A plainer grey than --ink-dim, which carries enough of the page's violet to
     read as quiet body text rather than as an aside. */
  color: #8b8695;
}

.tabs { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: 1.25rem; }

/* :visited named too: `a:visited` outranks a bare class, and would paint a tab
   orange the moment its page had been opened. */
.tab, .tab:visited {
  padding: .45rem .9rem;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--panel-edge);
  color: var(--ink-dim);
  text-decoration: none;
  font-size: .9rem;
}

/* A tab lifts from dim to full ink rather than going white-on-nothing, and the
   current one keeps its dark-on-orange whatever the pointer is doing. */
.tab:hover { color: var(--ink); border-color: var(--accent); }
.tab.current, .tab.current:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); font-weight: 700; }
/* Page links on long admin lists (partials/pager.ejs): Previous and Next greyed out at either end. */
.pager .tab.is-off { opacity: .4; cursor: default; }
.pager .tab.is-off:hover { color: var(--ink-dim); border-color: var(--panel-edge); }

/* The housing filter, sitting under the list tabs. Deliberately quieter than
   the row above it: two rows of identical orange pills would read as one set of
   choices, when they are really "which list" and then "which artists in it". So
   these are smaller, and the selected one is outlined rather than filled. */
.housing-tabs {
  margin-top: -.75rem;
  align-items: center;
}

.housing-tabs .tab { font-size: .82rem; padding: .3rem .7rem; }

.housing-tabs .tab.current,
.housing-tabs .tab.current:hover {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
}

.filter-note { font-size: .82rem; color: var(--ink-dim); }

/* Positioned so the off-screen expand checkbox anchors to the card. */
.application { position: relative; }

.app-head { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; margin-bottom: .5rem; }
.app-head h2 { margin: 0; }
.meta { margin: .2rem 0 0; font-size: .85rem; color: var(--ink-dim); }
.char-link { overflow-wrap: anywhere; font-size: .95rem; margin-bottom: 0; }
.housing { font-size: .95rem; margin: 0 0 .35rem; color: var(--ink-dim); }
.housing strong { color: var(--ink); }
/* An organiser's override of what the artist asked for. Coloured like .edited,
   because it's the same kind of thing: a deviation worth noticing at a glance
   rather than reading for. */
.held-back { color: var(--warn); }

.app-state { display: flex; align-items: center; gap: .5rem; flex: none; }

/* Deployment fingerprint in the admin bar. Quiet and monospaced: it is there to
   be read character by character against `npm run build-id`, not admired. */
.build-id {
  margin: .3rem 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .7rem;
  color: var(--ink-dim);
}

/* --- expand-to-edit ------------------------------------------------------
   A checkbox toggle rather than a scripted one, so the form still opens with
   JavaScript off. The box itself is off-screen but focusable, so the pencil is
   reachable by keyboard; :focus-visible on it lights up the pencil. */

.edit-toggle { position: absolute; opacity: 0; width: 0; height: 0; }

.pencil {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 7px;
  border: 1px solid var(--panel-edge);
  background: #17151e;
  cursor: pointer;
  font-size: .95rem;
  line-height: 1;
  user-select: none;
}

.pencil:hover { border-color: var(--accent); }
.edit-toggle:focus-visible + .app-head .pencil { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Tinted rather than filled: a solid accent background swallows the emoji. */
.edit-toggle:checked + .app-head .pencil {
  border-color: var(--accent);
  background: rgba(255, 138, 61, .18);
}

.edit-body { display: none; }
.edit-toggle:checked ~ .edit-body { display: block; }

/* --- saving in place -----------------------------------------------------
   What a save looks like when the page doesn't navigate. A record that still
   belongs on this list gets .card-flash and stays; one that has moved to another
   list gets .card-veil over it and then folds away. Both exist so that not
   navigating still feels like something happened -- see static/admin.js. */

.card-flash {
  display: inline-block;
  vertical-align: middle;
  background: rgba(78, 201, 123, .12);
  border: 1px solid var(--good);
  color: #c2f0d3;
  border-radius: 7px;
  padding: .4rem .6rem;
  margin-left: .75rem;
  font-size: .9rem;
  animation: flash-in .18s ease-out;
}

/* A refusal in the same spot. Its own line under the Save button rather than
   beside it: this is a sentence explaining what to do instead, not the word
   "Saved", and squeezed into the space left over on that row it would wrap into
   a narrow column against the right margin. */
.card-flash.error {
  display: block;
  background: rgba(255, 107, 107, .12);
  border-color: var(--bad);
  color: #ffd0d0;
  margin: .6rem 0 0;
  white-space: pre-line;
}

@keyframes flash-in { from { opacity: 0; } to { opacity: 1; } }

/* A white loading ring, for pictures on the way (decorate page, profile). */
.spinner {
  width: 48px; height: 48px; border-radius: 50%;
  border: 5px solid rgba(255, 255, 255, .25); border-top-color: #fff;
  animation: spin .9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 3s; } }

/* Covers the card it belongs to, which is why .application is positioned. */
.card-veil {
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  background: rgba(20, 18, 26, .94);
  color: #c2f0d3;
  font-weight: 700;
  opacity: 0;
  transition: opacity .18s ease-out;
}

/* Sticky rather than centred in the card. An expanded card is taller than the
   window, and its middle is wherever it happens to be -- often off-screen, which
   is no use for a message whose whole job is to be seen. Sticky keeps the text
   in view for as long as any part of the card is. */
.card-veil span {
  position: sticky;
  top: 40vh;
  display: block;
  padding: 1rem;
  text-align: center;
}

.dismissing .card-veil { opacity: 1; }

/* The height is pinned and then set to 0 from admin.js; everything else that
   takes up room has to go with it, or the card leaves a gap behind. */
.collapsing {
  overflow: hidden;
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: 0;
  border-top-width: 0;
  border-bottom-width: 0;
  opacity: 0;
  transition: height .3s ease, padding .3s ease, margin .3s ease, opacity .3s ease;
}

@media (prefers-reduced-motion: reduce) {
  .card-flash { animation: none; }
  .card-veil { transition: none; opacity: 1; }
  .collapsing { transition: none; }
}

.remove-row {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--panel-edge);
  margin-top: 1.25rem;
  padding-top: 1.25rem;
}

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

.warn {
  background: rgba(255, 200, 96, .12);
  border: 1px solid var(--warn);
  border-radius: 7px;
  padding: .6rem .75rem;
  font-size: .9rem;
}

.canned-row { display: flex; flex-wrap: wrap; gap: .4rem; }

.canned {
  padding: .35rem .7rem;
  border-radius: 999px;
  border: 1px dashed var(--panel-edge);
  background: #17151e;
  color: var(--ink-dim);
  font: inherit;
  font-size: .82rem;
  cursor: pointer;
}

.canned:hover { border-color: var(--accent); color: var(--accent); border-style: solid; }

/* Which status Save will write. Marked on the current status when the card is
   drawn, and moved by static/admin.js as the organiser clicks around. */
.canned.selected {
  border-style: solid;
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
}

.decide { border-top: 1px solid var(--panel-edge); margin-top: 1rem; padding-top: 1rem; }

.state-open { color: var(--good); }
.state-closed { color: var(--bad); }
.empty { color: var(--ink-dim); margin: 0; }

.foot { padding: 1rem 0 0; font-size: .9rem; color: var(--ink-dim); }
.edited { color: var(--warn); }

/* --- Dev mode (src/dev-ngio.js). Never rendered unless DEV_MODE is on. ----- */

.dev-badge {
  position: fixed;
  left: .75rem;
  bottom: .75rem;
  z-index: 1000;
  padding: .3rem .65rem;
  border-radius: 6px;
  background: var(--warn);
  color: #2a1f05;
  font-size: .8rem;
  font-weight: 700;
  pointer-events: none;
}

.dev-actions { display: flex; gap: .6rem; align-items: center; }
.card .dev-picks-title { margin: 1.5rem 0 .5rem; font-size: .95rem; color: var(--ink-dim); }
.dev-picks { display: flex; flex-wrap: wrap; gap: .4rem; }
.dev-prefill-options { margin: .6rem 0 0 1.65rem; }
.dev-prefill:not(:has(input[name="prefill"]:checked)) .dev-prefill-options { display: none; }

/* --- invites (/admin/invites, /invite/...) ------------------------------- */

.invite-facts { margin: 0 0 1.1rem; padding-left: 1.2rem; }
.invite-facts li { margin: .25rem 0; overflow-wrap: anywhere; }

.invite-capacity { margin: 0 0 1.1rem; padding-left: 1.2rem; }

/* The link and its Copy button on one line; the box takes what's left. */
.invite-link { display: flex; gap: .5rem; margin: .6rem 0 0; }
.invite-link input { flex: 1; min-width: 0; font-family: ui-monospace, monospace; font-size: .85rem; }

.invite-list { list-style: none; margin: 0; padding: 0; }
.invite-row { padding: .9rem 0; border-top: 1px solid var(--panel-edge); }
.invite-row:first-child { border-top: 0; padding-top: 0; }
.invite-row h3 { display: flex; align-items: center; gap: .5rem; }
.invite-row form { margin: .5rem 0 0; }

.invite-pill {
  display: inline-block;
  padding: .1rem .5rem;
  border-radius: 999px;
  font-size: .75rem;
  font-weight: 700;
  background: var(--warn);
  color: #2a1f08;
}
.invite-used .invite-pill { background: var(--good); color: #0d2716; }
.invite-expired .invite-pill { background: var(--panel-edge); color: var(--ink-dim); }
.invite-declined .invite-pill { background: var(--bad); color: #2a0f0f; }
.invite-expired h3, .invite-used h3, .invite-declined h3 { color: var(--ink-dim); }
