/* myPoleMagazine — the look: black, white, and hot pink. Sharp, not soft: hairline rules, small
   radii, firm weights (nothing past 700), and shadows used only where something genuinely floats.

   Light, dark and "follow this device" all read from the same set of colour tokens. Which set is in
   force is decided by, in order of strength:

     1. html[data-theme="light"|"dark"]  - what the dancer chose in Settings. app.js writes this
        attribute and remembers the choice in localStorage.
     2. the media query below              - when no choice has been made, the device decides.
     3. :root                              - light, the fallback for everything older than that.

   The cost of that order is that the light values are written twice and the dark values twice as
   well, because a media query cannot be reused for an attribute. It is load-bearing that the two
   copies of each set stay identical; when you change a colour, change it in both places. The
   `:not([data-theme="light"])` on the dark media block is what lets an explicit light choice win on
   a device that prefers dark.

   `color-scheme` is separate from the tokens and does a different job: it tells the browser which way
   round the native parts are - scrollbars, the number spinner, the date picker, the default focus
   ring. Without it, a dark page keeps a white scrollbar and white input spinners. */

:root {
  color-scheme: light dark;

  /* THE ONE RULE OF THIS PALETTE: --accent is for FILLS and --accent-strong is for INK. A pink that
     looks right behind a dark label is glare the moment it is used for words on white, so anything
     that is read as text - links, eyebrows, the count pill, the level badge, hovers - takes
     --accent-strong, and anything that is a shape - a button, an active pill, a meter, a border, an
     outline - takes --accent. Please do not undo this: it is what makes one hot pink feel like a
     palette instead of a highlighter. */
  --paper: #f7f6f8;          /* the page: a whisper of tint, never a bare white */
  --surface: #ffffff;        /* cards, panels, fields */
  --surface-2: #f0eff3;      /* fills that sit on a surface: tracks, chips, quiet chips */
  --ink: #2c2a33;            /* text. Softened off black on purpose: #0a0a0b on near-white is 19:1, and
                                that much glare is what made the first pass read harsh. This is
                                ~14:1 - still crisp - with a violet cast that sits with the pink,
                                and the greys around it now read as greys. */
  --muted: #6b6773;          /* second-rank text, labels, captions */
  --line: #e9e7ee;           /* every hairline */
  --accent: #ff2d85;         /* hot pink as a FILL: buttons, active pills, meters, borders */
  --accent-ink: #1c1b20;     /* the label that sits on --accent */
  --accent-soft: #ffe6f0;    /* the faintest pink: selected rows, quiet chips */
  --accent-strong: #a8004a;  /* hot pink as INK: deep enough to read on white (~7:1) where the fill
                                pink manages only ~4:1. It is also what draws the BORDER of a selected
                                row: a pink edge on a pink fill has to be this deep pink, because
                                --accent on --accent-soft is only 2.99:1 and the edge stops reading as
                                an edge. So the rule is by background, not by element: on a neutral
                                surface a shape takes --accent, on a pink one it takes --accent-strong. */
  --accent-hover: #ff5c9f;   /* what a pink button lightens to under the pointer: lighter, because its
                                label is dark, so brightening raises the contrast rather than eating
                                into it */
  --bar: #1c1b20;            /* the header and the phone tab bar: charcoal, not a black slab, so the top
                                of the page is not the hardest edge on it */
  --bar-ink: #f7f6f8;
  --focus: #ff2d85;
  --danger: #c3284d;
  --danger-ink: #fff4f8;     /* the label that sits ON a danger fill - the error toast, a danger
                                button under the pointer. Like --accent-ink, it is a separate token
                                because the fill has to be readable in one theme and its own label
                                in the other. */

  /* Move states and community difficulty both read as one ramp of the palette - quiet grey, pink,
     deep plum - so "how far along" and "how hard" are told the same way, and the top of the ramp is
     a colour rather than a black slab. */
  --goal-bg: #f0eff3;     --goal-fg: #55515e;
  --progress-bg: #ffe6f0; --progress-fg: #a8004a;
  --achieved-bg: #33101f; --achieved-fg: #ffc9de;
  --lvl1-bg: #f0eff3;     --lvl1-fg: #55515e;
  --lvl2-bg: #ffe6f0;     --lvl2-fg: #a8004a;
  --lvl3-bg: #33101f;     --lvl3-fg: #ffc9de;
  --lvl1-fill: #c8c5d0;   --lvl2-fill: #ff2d85;   --lvl3-fill: #33101f;

  --radius: 12px;
  --radius-sm: 8px;
  /* Every avatar on every screen is sized from this one number, and the circle and its inset
   * multiply together, so the shoe keeps the same share of the circle at any scale. */
  --avatar-scale: 1.25;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* The same palette read upside down, with three things chosen rather than inverted: a surface
       LIFTS as it comes forward (the page is the deepest thing on the screen, cards and tracks sit
       above it), the hairlines are brighter than the step between surfaces so a card still has an
       edge on a dim screen, and the top of the ramp - achieved, advanced - is the pink FILL with
       dark ink on it. That last one used to be a cream block, which was the light theme's deep plum
       slab reversed: a big pale rectangle in the middle of a dark page, louder than anything it was
       labelling. The pink costs nothing and matches the rest of the palette. */
    --paper: #111014;          /* deeper than before, so the cards have somewhere to lift from */
    --surface: #211f28;
    --surface-2: #2e2b36;
    --ink: #f2eef8;            /* ~14:1 on a card: crisp, without the glare of a pure white */
    --muted: #bab5c4;          /* second-rank text, over 7:1 on every surface it is drawn on */
    --line: #4c4757;           /* brighter than surface -> surface-2, so an edge still reads */
    --accent: #ff5b9d;
    --accent-ink: #17161a;
    --accent-soft: #4b1c33;
    --accent-strong: #ffa8cb;
    --accent-hover: #ff7bb2;
    --bar: #24222b;            /* a shade above the page: the header is a raised band, not a hole */
    --bar-ink: #efeef4;
    --focus: #ff5b9d;
    --danger: #ff8098;
    --danger-ink: #1a1116;
    --goal-bg: #2e2b36;     --goal-fg: #d3cfdb;
    --progress-bg: #4b1c33; --progress-fg: #ffa8cb;
    --achieved-bg: #ff5b9d; --achieved-fg: #17161a;
    --lvl1-bg: #2e2b36;     --lvl1-fg: #d3cfdb;
    --lvl2-bg: #4b1c33;     --lvl2-fg: #ffa8cb;
    --lvl3-bg: #ff5b9d;     --lvl3-fg: #17161a;
    --lvl1-fill: #6b6676;   --lvl2-fill: #ff5b9d;   --lvl3-fill: #ffa8cb;
  }
}

/* An explicit choice. Must stay identical to the tokens above - see the comment at the top. */
html[data-theme="light"] {
  color-scheme: light;
  --paper: #f7f6f8;
  --surface: #ffffff;
  --surface-2: #f0eff3;
  --ink: #2c2a33;
  --muted: #6b6773;
  --line: #e9e7ee;
  --accent: #ff2d85;
  --accent-ink: #1c1b20;
  --accent-soft: #ffe6f0;
  --accent-strong: #a8004a;
  --accent-hover: #ff5c9f;
  --bar: #1c1b20;
  --bar-ink: #f7f6f8;
  --focus: #ff2d85;
  --danger: #c3284d;
  --danger-ink: #fff4f8;
  --goal-bg: #f0eff3;     --goal-fg: #55515e;
  --progress-bg: #ffe6f0; --progress-fg: #a8004a;
  --achieved-bg: #33101f; --achieved-fg: #ffc9de;
  --lvl1-bg: #f0eff3;     --lvl1-fg: #55515e;
  --lvl2-bg: #ffe6f0;     --lvl2-fg: #a8004a;
  --lvl3-bg: #33101f;     --lvl3-fg: #ffc9de;
  --lvl1-fill: #c8c5d0;   --lvl2-fill: #ff2d85;   --lvl3-fill: #33101f;
}

html[data-theme="dark"] {
  color-scheme: dark;
  /* Identical to the media block above - see the comment there for why these values are these. */
  --paper: #111014;
  --surface: #211f28;
  --surface-2: #2e2b36;
  --ink: #f2eef8;
  --muted: #bab5c4;
  --line: #4c4757;
  --accent: #ff5b9d;
  --accent-ink: #17161a;
  --accent-soft: #4b1c33;
  --accent-strong: #ffa8cb;
  --accent-hover: #ff7bb2;
  --bar: #24222b;
  --bar-ink: #efeef4;
  --focus: #ff5b9d;
  --danger: #ff8098;
  --danger-ink: #1a1116;
  --goal-bg: #2e2b36;     --goal-fg: #d3cfdb;
  --progress-bg: #4b1c33; --progress-fg: #ffa8cb;
  --achieved-bg: #ff5b9d; --achieved-fg: #17161a;
  --lvl1-bg: #2e2b36;     --lvl1-fg: #d3cfdb;
  --lvl2-bg: #4b1c33;     --lvl2-fg: #ffa8cb;
  --lvl3-bg: #ff5b9d;     --lvl3-fg: #17161a;
  --lvl1-fill: #6b6676;   --lvl2-fill: #ff5b9d;   --lvl3-fill: #ffa8cb;
}

/* ---------- base ---------- */

* { box-sizing: border-box; }
html { scroll-padding-top: 72px; } /* keep focused fields clear of the sticky header */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.55 var(--sans);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent-strong); text-decoration-thickness: 1px; text-underline-offset: 2px; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
main:focus { outline: none; }

/* ---------- header & navigation ---------- */

.topbar {
  position: sticky; top: 0; z-index: 10;
  background: var(--bar); color: var(--bar-ink);
  padding-top: env(safe-area-inset-top);
}
.topbar-inner {
  max-width: 760px; margin: 0 auto; padding: 10px 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.brand {
  color: var(--bar-ink); text-decoration: none;
  font: 700 1.3rem/1 var(--serif); letter-spacing: -.02em;
}
.brand em { font-weight: 400; color: var(--accent); }

#nav { display: flex; gap: 2px; }

#goal-dialog-title{
  margin-bottom:1rem;
  border-bottom:1px solid var(--line);
}
#nav a {
  position: relative; color: inherit; text-decoration: none;
  /* The highlight is a rounded rectangle, not a capsule: on a phone each tab is a wide flex cell, and
   * a stadium at 999px turned the active one into a long oval that read as a shape of its own rather
   * than as the tab being lit. Sized like the app's other controls. */
  padding: 8px 12px; border-radius: var(--radius-sm);
  font-size: .9rem; font-weight: 600;
  /* The current tab changes colour, never geometry: same padding, same weight, same line box, so
   * selecting one does not move the label or shift the tabs beside it. Two earlier rules gave the
   * active tab a smaller padding of its own, which shrank it by two pixels on each side and made the
   * whole bar twitch on every tap. The pill is painted on the box the tab already had. */
  transition: background-color .15s ease, color .15s ease;
}

#nav .home-nav-icon svg {
  width: 18px;
  height: 18px;
}
/* The wrapper has to be a box, not a word in a line: as an inline span its line box was 22-23px tall
 * around an 18px glyph, so centring the wrapper left the star itself about 2.5px above the middle of
 * its tab - the one icon in the bar that was not where it looked like it should be. */
#nav .home-nav-icon { display: inline-flex; }
#nav a:hover { background: rgb(255 255 255 / .1); }
/* Pink pill, charcoal label: the bar is dark in both themes, so this reads the same either way
   without needing a token of its own for text on the accent. */
#nav a[aria-current="page"] {
  background: var(--accent);
  color: var(--bar);
}
#nav a[aria-current="page"]:hover { background: var(--accent); }
/* The account tab is drawn as an icon rather than a word, so the link centres it itself - a text
 * link gets that for free from its own line box. */
#nav a.icon-only { display: inline-flex; align-items: center; justify-content: center; }
/* The glyph sits inside a ring, the way an avatar placeholder does. The ring is the border and the
 * space between it and the glyph is the padding, so shrinking one without the other cannot make the
 * two collide. Sized so 22 + the link's 8px padding either side lands on the height the word tabs
 * already are, which keeps the whole bar on one line. */
#nav a.icon-only > svg {
  width: 22px; height: 22px; padding: 2.5px;
  border: 1.5px solid currentColor; border-radius: 50%;
}
/* The direct-child rule matters: the Home star is an icon in the bar too, but it is nested inside
 * .home-nav-icon for its own sizing, and a ring drawn around a star reads as an avatar nobody set. */
#nav .pill {
  display: inline-block; min-width: 18px; margin-left: 4px; padding: 0 5px;
  border-radius: 999px; background: var(--accent); color: var(--bar);
  font-size: .72rem; font-weight: 700; text-align: center;
}
/* The count that used to sit on the Review tab rides on the profile instead, so a queue of
 * suggestions is visible from every screen. Absolute, so a badge cannot widen the tab or nudge the
 * icon the way a trailing inline pill would. */
#nav a.icon-only .pill {
  position: absolute; top: 0; right: 0; margin: 0;
  min-width: 16px; padding: 0 4px; font-size: .62rem; line-height: 16px;
}

main { max-width: 760px; margin: 0 auto; padding: 22px 16px 56px; }

@media (max-width: 700px) {
  #nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
    justify-content: space-around; gap: 0;
    background: var(--bar); border-top: 1px solid rgb(255 255 255 / .14);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
  }
  /* Every tab the same box, and each one centres what it holds. Text on its own sits a couple of
   * pixels above the middle of a stretched cell, which is exactly the kind of "slightly off" that is
   * easy to see and hard to name; as a flex item it lands on the middle like the icons do. */
  #nav a {
    flex: 1; display: inline-flex; align-items: center; justify-content: center;
    text-align: center; font-size: .72rem; padding: 10px 2px;
  }
  /* Home moves to the middle of the bar on a phone. The two lists a dancer opens most often sit
   * either side of it and the star is the way back to the top, one thumb-width from the centre of
   * the screen. The markup keeps a single order and the two layouts differ only here, so they cannot
   * drift apart. Desktop is unchanged, with Home leading. */
  #nav a[data-nav="moves"] { order: 1; }
  #nav a[data-nav="dictionary"] { order: 2; }
  #nav a[data-nav="home"] { order: 3; }
  #nav a[data-nav="community"] { order: 4; }
  #nav a[data-nav="account"] { order: 5; }
  main { padding-bottom: 104px; }
}

/* ---------- type & page structure ---------- */

h1 {
  font: 700 clamp(2rem, 6vw, 2.7rem)/1.05 var(--serif);
  letter-spacing: -.02em; margin: 0; overflow-wrap: anywhere;
}
h3 { margin: 0; font: 700 1.06rem/1.25 var(--sans); letter-spacing: -.01em; overflow-wrap: anywhere; }
p { max-width: 68ch; }
.eyebrow {
  margin: 0 0 6px; color: var(--accent-strong); font-size: .7rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
}
/* A hairline under the head, not a rule: the page already reads as a page, and a bar of near-black
   across every screen was part of what made the first pass feel harsh. The one heavy rule left in
   the app is on a trick's own page (.entry-head), and it is pink. */
.page-head {
  position: relative;   /* what a follow bar's dropped list lines up against, and how wide it is */
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  padding-bottom: 14px; margin-bottom: 20px; border-bottom: 1px solid var(--line);
}
/* A move's own page: the name, where it is in your goals and how hard it is, read as one line. The
   two fields keep their labels - without them the pink pill reads as a status of the page rather than
   of this move, and five dashes read as decoration - and sit at the far end of the name's row. */
.move-head { flex-wrap: wrap; row-gap: 12px; }
.head-fields { display: grid; gap: 10px; }
.head-field { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.head-field > .label { flex: none; font-size: .76rem; font-weight: 700; color: var(--muted); }
/* Beside its label the written level sits on the same line as the dashes, not under them - and drops
   to a line of its own when the row is too narrow to hold it, which is what a phone does. */
.head-field .diff-edit { flex-direction: row; align-items: center; flex-wrap: wrap; gap: 0; }
.head-field .diff-label { margin: 0 0 0 8px; font-size: .8rem; }
/* A phone cannot hold the label, the five dashes and the written level on one line. The level goes
   under the dashes rather than the whole control dropping below its label, so *Difficulty* keeps the
   thing it names beside it - the dashes - and the reading sits under them as it does in the form. */
@media (max-width: 700px) {
  .head-field .diff-edit { flex-direction: column; align-items: flex-start; gap: 0; }
  .head-field .diff-label { margin-left: 6px; }
}
/* The move's name is the page's title, and when the move is filed under a trick the title links to
   it, so the name that is written here is also the way to the dictionary's version of it. Nothing is
   drawn around the title, and nothing repeats it: the pencil beside it opens the one place the name
   and that link are changed. */
.move-ident { flex: 1 1 260px; min-width: 0; }
.move-title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.move-title { flex: 1 1 auto; min-width: 0; display: block; }
/* Big type needs a rule it can be seen under: the quiet link's hairline is doubled at this size and
   sits further from the baseline than it does in a line of body text. */
.move-title .quiet-link { text-decoration-thickness: 2px; text-underline-offset: 5px; }

/* The move's dictionary entry, in the details dialogue: the trick it is filed under as a link, and
   the select that changes it folded away behind Change until it is wanted. */
.dict-link { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.dict-hint { margin: 4px 0 0; font-size: .8rem; color: var(--muted); }
.dict-hint:empty { display: none; }

/* The Back button a screen shows when it is only ever reached from another one. Every screen uses the
   same one - a small button, the shape of every other button here - so going back is the same gesture
   and the same target wherever a dancer happens to be; only the word after the arrow changes. */
.page-back { margin-bottom: 14px; width: 40px; padding: 0; justify-content: center; font-size: 1.1rem; line-height: 1; }
.subhead {
  margin: 30px 0 10px; color: var(--muted); font-size: .72rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
}
/* A line under a subhead that explains it, without the gap .fineprint leaves above itself. */
.subnote { margin: -2px 0 10px; font-size: .85rem; }
.muted { color: var(--muted); }
.fineprint { font-size: .85rem; margin-top: 20px; }
.count {
  display: inline-block; min-width: 26px; padding: 2px 9px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong);
  font: 700 .78rem/1.6 var(--sans); text-align: center;
  font-variant-numeric: tabular-nums;
}
.lede { color: var(--muted); margin: 8px 0 22px; font-size: 1.02rem; }
.page-head .lede { margin: 8px 0 0; }

/* A collection's name is renamed where it is written: the pencil swaps the heading for an input of
 * the same size, with a check and a cross beside it, so the title never leaves the line it belongs
 * to. The head wraps as a whole on a phone, which keeps the three controls off the name rather than
 * squeezing it into a column. */
.collection-head { flex-wrap: wrap; row-gap: 12px; }
.collection-ident { flex: 1 1 240px; min-width: 0; }
.collection-title-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.collection-title-row h1 { min-width: 0; }
.collection-name-edit { display: flex; align-items: center; gap: 6px; flex: 0 1 auto; min-width: 0; max-width: 100%; }
.collection-head .collection-name-edit input {
  field-sizing: content;
  flex: 0 1 auto; width: auto; min-width: 12ch; max-width: calc(100% - 80px);
  min-height: 0; padding: 2px 8px;
  font: 700 clamp(2rem, 6vw, 2.7rem)/1.05 var(--serif); letter-spacing: -.02em;
}
.collection-name-edit .icon-btn.plain { width: 34px; height: 34px; }
.collection-name-edit .icon-btn.plain svg { width: 16px; height: 16px; }
.collection-head .head-actions { margin-left: auto; }
.icon-btn.plain.delete-btn:hover { color: var(--danger); }

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 0 18px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font: 700 .95rem/1 var(--sans); text-decoration: none; cursor: pointer;
}
.btn:hover { border-color: var(--muted); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.danger { color: var(--danger); border-color: var(--danger); }
.btn.danger:hover { background: var(--danger); border-color: var(--danger); color: var(--danger-ink); }
.btn.small { min-height: 36px; padding: 0 14px; font-size: .85rem; }
.btn.block { width: 100%; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn[disabled]:hover { border-color: var(--line); }
/* An icon-only button: the collection pencil, beside the move count in a page head. */
.icon-btn, .icon-btn-hover, .icon-btn-in-moves {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0; flex: none;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.icon-btn svg, .icon-btn-hover svg, .icon-btn-in-moves svg { width: 18px; height: 18px; }

.icon-btn-in-moves {
  background: var(--accent); color: var(--accent-ink);
}

.icon-btn-hover:hover { border-color: var(--accent); color: var(--accent-strong); }
.icon-btn[aria-expanded="true"] { border-color: var(--accent-strong); color: var(--accent-strong); background: var(--accent-soft); }
.head-actions { display: flex; align-items: center; gap: 10px; }

.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.actions.stacked { flex-direction: column; margin-top: 16px; }
.actions.stacked .btn { width: 100%; }

/* ---------- cards, badges, chips ---------- */

.stack { display: grid; gap: 10px; }

/* Collections at the top of the Moves page: square tiles, with a + tile first to make a new one. */
.coll-section { margin-bottom: 28px; }
.coll-grid {
  display: grid; gap: 10px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
}
.coll-tile {
  aspect-ratio: 1; display: flex; flex-direction: column; justify-content: space-between;
  padding: 12px; min-width: 0; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink); text-decoration: none; font: inherit; text-align: left; cursor: pointer;
}
.coll-tile:hover { border-color: var(--accent); }
.coll-tile .coll-name { font-weight: 600; overflow-wrap: anywhere; }
.coll-tile .count { align-self: flex-end; }
.coll-new { align-items: flex-start; border-style: dashed; color: var(--muted); }
.coll-new .coll-plus { font-size: 2rem; line-height: 1; color: var(--accent); }
/* An invitation is a tile in this same grid, because that is where a dancer looks for the collections
 * they can open - and it is the one tile that is a question, so it takes the whole row: the mark,
 * the name and who sent it on the left, the two answers on the right. The dashed edge is the same
 * "not a collection yet" the + tile wears, in the reading pink rather than the sheen: what it asks
 * for is an answer, and the answer turns it into one of the tiles below it. */
.coll-invite {
  grid-column: 1 / -1; aspect-ratio: auto;
  flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  border-style: dashed; border-color: var(--accent-strong);
}
/* What is being asked, together: the mark, the collection and who sent the invitation wrap among
 * themselves instead of being spread across the row, which is what `space-between` from the square
 * tiles would otherwise do to them. */
.coll-invite-main { flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; min-width: 0; }
/* On a phone the answers drop onto their own line rather than squeezing the name into a column of
 * three one-word rows; once there is room they sit beside it, level with the middle of the tile. */
@media (min-width: 480px) {
  .coll-invite-main { flex: 1 1 0; }
}
.coll-invite-main .coll-name { overflow-wrap: anywhere; }
.coll-invite:hover { border-color: var(--accent-strong); }   /* it is not a link; nothing to open */
.coll-invite-mark {
  padding: 2px 9px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong);
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.coll-invite-from { color: var(--muted); font-size: .85rem; }
.coll-invite-actions { display: flex; gap: 8px; margin-left: auto; }
/* A collection you joined wears "shared" where the others wear the count of your moves in them: the
 * count would read 0 for a class that is full of everybody else's work. */
.coll-shared {
  align-self: flex-end; font-size: .72rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
/* The share dialogue, opened by the people icon in a collection's head. It is the collection's whole
 * sharing surface - invitations, members and withdrawals - so the page itself stays a page of moves
 * rather than carrying a panel that repeats what the icon opens. */
.share-sheet .share-search { margin: 18px 0 10px; }
.share-sheet .share-section { margin-top: 18px; }
.share-sheet .share-section .subhead { margin: 0 0 6px; }
.share-sheet .chips { margin-top: 0; }
.share-sheet > .field-help { margin-top: 14px; }
/* A dancer's name is the link; the × beside it is the owner's way to take them out (or withdraw a
 * question nobody has answered), so the chip's ink stays quiet rather than turning link-pink. */
.chip a { color: inherit; text-decoration: none; }
.chip a:hover { color: var(--accent-strong); }
/* The username search the + opens, in the new-collection sheet and in the invite dialogue. */
.dancer-picked { display: flex; flex-wrap: wrap; gap: 6px; }
.dancer-picked:empty { display: none; }
.dancer-search { margin-top: 2px; }
.dancer-results { display: grid; margin-top: 8px; }
.dancer-result {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-top: 1px solid var(--line);
}
.dancer-result:first-child { border-top: 0; }
.dancer-name { font-weight: 600; overflow-wrap: anywhere; }
.dancer-note { margin: 8px 0 0; color: var(--muted); font-size: .85rem; }
.card {
  position: relative;            /* the container a preview's stretched link covers */
  display: block; padding: 16px 18px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
  color: inherit; text-decoration: none;
}
a.card:hover { border-color: var(--accent); }
/* The same pink edge on every card that previews a trick or a move, not only the ones that are links:
 * the Moves page's cards have had it since they were anchors, and the dictionary, the goals window and
 * the feed draw the same card - so hovering any of them says the same thing in the same way. Written
 * as :has(.trick-card) rather than on the container, because what makes a card a preview is the
 * preview inside it, and every one of these is built by trickPreview(). */
.card:has(.trick-card):hover { border-color: var(--accent); }

/* The whole card is the way into the move or the trick, not only the name inside it.
 *
 * The name stays the one real link and is stretched over the card by a pseudo-element - the same
 * trick the Community list already uses on `.member-card-link`, for the same reason. That is
 * deliberately not "wrap the card in an anchor": these cards hold a status dropdown, a Remove button
 * and a drag grip, and an anchor may not contain controls - the browser would take the first click on
 * any of them as a navigation. The link does the work, so it is still focusable, still carries the
 * href a status bar shows, and still supports middle-click and Copy Link. The controls are lifted
 * above the overlay, which is why they are the ones given a stacking order here and the card's own
 * padding, name and detail line are not. */
.card:has(.trick-name a) { cursor: pointer; }
.card .trick-name a::after {
  content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit;
}
.trick-actions > *,
.trick-tail > a,
.trick-tail > button { position: relative; z-index: 2; }
/* A keyboard tab lands on the name, and the ring is drawn round the card it opens: the outline on the
 * name alone would sit in the middle of a large clickable card and read as though only that part of it
 * were the link. Limited to cards that are actually links, so a form that happens to use .card does
 * not draw one around itself when a field is focused. */
.card:has(.trick-name a):focus-within { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
.card.disabled { opacity: .7; }
.card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.card-top h3 { min-width: 0; }   /* so a long name gives way before the level beside it does */
.card p { margin: 6px 0 0; }

/* ---------- one trick or move, previewed ----------
   The shape every list that previews a trick draws: the name, the five dashes at the far end of that
   line, and whatever is known about it underneath. The Moves page, the Current goals window, the
   Dictionary and the Community feed all put this inside their own container, so the preview reads the
   same wherever a dancer meets one - what differs between the screens is only what fills the slots:
   a status dropdown and a Remove button here, a "Pending" mark and an add button there.

   The card around it belongs to the container, not to the preview: on the Moves page the card *is* the
   link, and everywhere else the container decides. */
.trick-card { display: grid; gap: 6px; min-width: 0; }
.trick-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; }
/* The name takes the free space, which is what keeps the dashes against the right edge of the card
   instead of trailing a short name. It gives way first when the line runs out of room. */
.trick-name { flex: 1 1 auto; min-width: 0; }
.trick-name a {
  color: inherit;
  text-decoration: none;
  border-bottom: 0;
}
.card:hover .trick-name a { color: var(--accent-strong); }
/* `margin-left: auto` matters only when the line runs out and this wraps onto a line of its own - a
   long name beside an add button on a phone. The name takes the free space on a shared line, so
   nothing moves there; alone on its line, the tail is pushed to the right, which is where the dashes
   and the action live on every other card. Without it a wrapped action starts back at the left edge
   under the name, reading as a second thing on the card rather than the end of the first line. */
.trick-tail { flex: none; margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.trick-level { display: inline-flex; align-items: center; }
/* Written after `.card p`, which would otherwise space these two onto their own lines. */
.trick-card .trick-detail, .trick-card .trick-alias {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  margin: 0; color: var(--muted); font-size: .88rem;
}
.trick-card .trick-alias { font-style: italic; }
.trick-actions { display: flex; align-items: center; gap: 8px;}
/* The add and in-your-moves marks on a dictionary row are a small mark beside the name, not a big button. */
.trick-tail .icon-btn, .trick-tail .icon-btn-hover, .trick-tail .icon-btn-in-moves { width: 32px; height: 32px; }
.trick-tail .icon-btn svg, .trick-tail .icon-btn-hover svg, .trick-tail .icon-btn-in-moves svg { width: 15px; height: 15px; }
.trick-actions .btn { margin: 0; }
.trick-actions .remove-dash { margin-left: auto; }

/* Dragging a card in Current goals into a new place.
 *
 * The card itself is the drag surface except for its real controls, which still keep their own click
 * and tap behaviour. The card being moved rises out of the list (a transform, so nothing reflows under
 * it) and the card it would displace is marked with a pink edge rather than by moving it, because the
 * list shifting under a finger is how a drag starts to feel like it is fighting back. */
.goal-card { cursor: grab; }
.goal-card:active, .card.reordering { cursor: grabbing; }
.goal-grip { cursor: inherit; }
.card.reordering {
  position: relative; z-index: 3; transition: none;
  border-color: var(--accent-strong);
  box-shadow: 0 14px 30px rgb(0 0 0 / 18%);
  /* A card picked up by a finger would otherwise select its own words, and a long press on a link
   * would offer the browser's picture menu, halfway through the drag. */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.card.drop-before { box-shadow: inset 0 3px 0 var(--accent-strong); }

/* "Already in" in the add-a-goal panel is an answer, not a button that happens to be switched off. A
 * disabled control is drawn at 45% opacity so it reads as unavailable, which here is too faint to
 * read at all: it keeps its shape and its border, and says what it has to say in a muted colour. */
.goal-source-block .btn[disabled] {
  opacity: 1; cursor: default;
  border-color: var(--line); background: var(--surface-2); color: var(--muted);
}
.goal-source-block .btn[disabled]:hover { border-color: var(--line); }

/* The status of a move: a dropdown that is also the place the status is read, painted in the colour of
   the answer it holds. The three pairs below are the goal / progress / achieved tokens the palette
   check measures, so a status reads the same way here as it did as a badge. */
.status-pick {
  position: relative; display: inline-flex; border-radius: 999px; overflow: hidden;
  background: var(--surface-2); color: var(--ink);
}
.status-goal     { background: var(--goal-bg);     color: var(--goal-fg); }
.status-progress { background: var(--progress-bg); color: var(--progress-fg); }
.status-achieved { background: var(--achieved-bg); color: var(--achieved-fg); }
/* The native arrow is swapped for a small chevron drawn in the pill's own colour, so the control
   reads as one shape with its text rather than as a grey box with a system arrow. */
.status-pick select {
  appearance: none; -webkit-appearance: none; box-sizing: border-box;
  min-height: 32px; margin: 0; padding: 0 30px 0 14px; border: 0; border-radius: 999px; -webkit-border-radius: 999px;
  background: transparent; color: inherit; cursor: pointer;
  font: 600 .86rem/1 var(--sans); letter-spacing: .005em;
}
.status-pick select:focus-visible { outline: 2px solid currentColor; outline-offset: -3px; border-radius: 999px; }
.status-pick::after {
  content: ""; position: absolute; right: 13px; top: 50%; width: 6px; height: 6px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.status-pick option { color: var(--ink); background: var(--surface); font-weight: 600; }
.meta, .meta-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 8px; }

.badge {
  flex: none; padding: 3px 10px; border-radius: 999px;
  font-size: .75rem; font-weight: 700; white-space: nowrap;
}
/* A smaller badge for a list row, where the chip is a label on a name rather than a headline of its
   own, and a quiet one for the states that are an absence of an answer (Unrated, Pending) - they are
   true and worth saying, and they must not compete with a trick somebody bothered to mark. */
.badge.sm { padding: 2px 8px; font-size: .68rem; }
/* Smaller again, for the dictionary's own list: there the level is a mark beside the trick rather than the
   last thing on the line, and it has to stay quieter than the name it marks. */
.badge.xs { padding: 1px 7px; font-size: .62rem; }
.badge.is-unrated { background: var(--surface-2); color: var(--muted); }
/* A suggestion waiting for review, in a dictionary row: a pink asterisk instead of a word. The reason
   is in its tooltip, so the row itself stays quiet. */
.pending-mark { flex: none; color: var(--accent-strong); font: 800 1.15rem/1 var(--sans); margin-left: 6px; cursor: default; }

.badge-goal     { background: var(--goal-bg);     color: var(--goal-fg); }
.badge-progress { background: var(--progress-bg); color: var(--progress-fg); }
.badge-achieved { background: var(--achieved-bg); color: var(--achieved-fg); }

.tag {
  display: inline-block; padding: 2px 9px; border-radius: 6px; border: 1px solid var(--line);
  color: var(--muted); font-size: .75rem; font-weight: 600;
}

/* ---------- one dancer's own 1-5 difficulty ---------- */
.diff-badge, .diff-unrated {
  display: inline-flex; align-items: center; white-space: nowrap;
  padding: 1px 7px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface-2); color: var(--muted); font-size: .62rem; font-weight: 600;
}
.diff-badge {
  border-color: color-mix(in srgb, var(--accent) 28%, var(--line));
  background: color-mix(in srgb, var(--accent) 10%, var(--surface-2));
  color: var(--accent-strong);
}

/* The five pills in the add-move form: tighter than the default radio pills so all five fit a
   phone, and allowed to wrap when they do not. */
/* The editable dashes on a move's own page. Each step is a button with a generous hit area around a
   thin dash, so it is easy to hit on a phone. The written level sits underneath. */
.diff-edit { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.diff-steps { display: inline-flex; gap: 2px; }
.diff-step {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: 12px 6px; border-radius: 10px;
}
.diff-step::before {
  content: ""; display: block; width: 34px; height: 7px; border-radius: 3px;
  background: var(--surface-2); border: 1px solid var(--line);
}
.diff-step.on::before { background: var(--accent); border-color: var(--accent); }
.diff-step:hover::before { border-color: var(--accent); }
.diff-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.diff-label { margin: 0 0 0 6px; color: var(--muted); font-size: .85rem; font-weight: 600; }


.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.section .chips { margin-top: 0; }
.chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 11px;
  border-radius: 999px; background: var(--accent-soft); color: var(--ink);
  font-size: .85rem; text-decoration: none;
}
a.chip:hover { outline: 1px solid var(--accent); }

.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 18px; }
.stat {
  padding: 14px 8px; text-align: center; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.stat strong {
  display: block; font: 700 1.7rem/1.15 var(--sans);
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.stat span { color: var(--muted); font-size: .68rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }

.empty {
  padding: 34px 16px; text-align: center; color: var(--muted);
  border: 1px dashed var(--line); border-radius: var(--radius);
}
.empty h3 { color: var(--ink); margin-bottom: 6px; }
.empty p { margin-left: auto; margin-right: auto; }
.empty .btn { margin-top: 10px; }

/* ---------- lists ---------- */

.list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 13px 0; border-bottom: 1px solid var(--line);
}
/* `flex: 1` is load-bearing, not decoration. `.row` is space-between, so on a row with three
   children - avatar, text, button - the free space is split between BOTH gaps and the name ends up
   floating in the middle of the page with a dead stretch either side. Letting the text block take the
   free space instead keeps the name against its avatar and still pushes the button to the far right. */
.row-main { display: grid; gap: 2px; min-width: 0; flex: 1 1 auto; }
.row-main strong { overflow-wrap: anywhere; }
.row .actions { margin: 0; flex: none; }
/* The review queue is the one list that is a to-do rather than a record, so the rules between its
   rows are drawn in --accent instead of --line: a border is a shape, so the palette's rule at the
   top of this file says --accent and not --accent-strong. A trick waiting for a decision then reads
   as something with an answer owed on it, rather than as one more row of history. The line above
   the first one is left alone, so the queue starts on the page with the same --line every other
   list starts with instead of being framed in pink. */
.review-list > .row { border-bottom-color: var(--accent); }
.member-card-link {
  display: flex; align-items: center; gap: 12px;
  flex: 1 1 auto; min-width: 0; color: inherit; text-decoration: none;
}
.row:has(> .member-card-link) { position: relative; }
.member-card-link::after { content: ""; position: absolute; inset: 0; }
.row:has(> .member-card-link) > .actions { position: relative; z-index: 1; }
.member-card-link:hover .entry-link { border-color: var(--accent); color: var(--accent-strong); }

/* A post in the Community feed: whose it is on the left, and one of the shared preview cards on the
   right. The avatar is the feed's own mark - who posted it - which a preview of a trick has no
   business carrying, so the two live side by side and only the card is the preview.

   Every update is one of these. A move, a move marked achieved and a clip all wear the same card, in
   the same column beside the same avatar, because they are all the same thing to the person reading
   the tab: something somebody did, with a name on it. The date sits in the card's detail line with
   the name that goes with it, so there is no separate timestamp column to line up. */
.feed-post {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 0; border-bottom: 1px solid var(--line);
}
.feed-list > .feed-post:last-child { border-bottom: 0; }
.feed-post .card { flex: 1 1 auto; min-width: 0; }
.feed-post .card:has(.trick-card):hover { border-color: var(--line); }
/* One Instagram account on a studio page: the chip and the pencil that changes it, or the + that
 * starts one. The form is hidden until either is tapped, so the section is one line at rest. */
.studio-instagram { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.studio-instagram-form { margin-top: 12px; }

.studio-members { display: flex; flex-wrap: wrap; gap: 10px; }
.studio-members a { display: inline-flex; flex: none; border-radius: 50%; line-height: 0; }
.studio-members a:hover { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- filters ---------- */

.filters { display: grid; gap: 10px; margin-bottom: 16px; }
/* minmax(0, 1fr) rather than 1fr: a 1fr track is minmax(auto, 1fr), so its minimum is the select's
 * own min-content width, and two selects whose options are long words added up to more than the
 * page - which pushed a horizontal scrollbar onto every screen in the app on a phone. The first
 * track is allowed to go below its content width instead, and the selects share what there is. */
.filter-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
.filter-row select { width: auto; max-width: 48vw; }

/* A labelled filter on a line of its own: the word, then the control. Used where a select narrows a
   list - the members tab's studio picker - so it reads as part of the list it filters rather than as
   a control in the page head. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 18px; }
.filter-bar .set-label { flex: none; }
.filter-bar select, .filter-bar input { flex: 1 1 200px; width: auto; min-width: 0; }
/* Keep the magnifier beside the search box rather than inside its border. */
.search-field { flex: 0 1 240px; min-width: 0; display: flex; align-items: center; gap: 8px; }
.search-field input { flex: 1 1 auto; width: 100%; min-width: 0; }
.search-field svg { flex: none; color: var(--muted); pointer-events: none; }

/* A heading with its own control on the same line: what the panel holds, and how to narrow it. The
   heading drops its usual top margin because this row carries the gap instead. */
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin: 26px 0 12px;
}
.panel-head .subhead { margin: 0; }
.panel-head .seg { margin: 0; }

.seg { display: flex; flex-wrap: wrap; gap: 6px; }
.seg-btn {
  min-height: 40px; padding: 0 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font: 700 .88rem/1 var(--sans); cursor: pointer;
}
.seg-btn:hover { border-color: var(--muted); }
.seg-btn[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

/* The primary choice on a screen - which half of the Community page you are on - as ONE track with
   two equal halves, instead of two loose pills that have to be read as a pair. The pressed half is
   the pink fill and the seam between them is invisible, which is what makes it read as one control
   rather than two buttons that happen to sit together. Declared after .seg-btn on purpose: same
   specificity, so source order is what wins. */
.seg-tabs {
  display: flex; width: 100%; gap: 4px; padding: 4px; margin-bottom: 20px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2);
}
.seg-tabs .seg-btn {
  flex: 1; min-height: 38px; padding: 0 14px; border: 0; background: none;
  border-radius: 999px;
  /* The two labels must never wrap onto two lines and make the track jump height on a phone. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.seg-tabs .seg-btn:hover { background: var(--surface); }
.seg-tabs .seg-btn[aria-pressed="true"],
.seg-tabs .seg-btn[aria-pressed="true"]:hover {
  background: var(--accent); color: var(--accent-ink);
}

/* A secondary switch: the audience filter beside a heading. Quieter than the tabs on purpose - it
   narrows what is already on screen rather than choosing the screen. */
.seg-sm .seg-btn { min-height: 32px; padding: 0 12px; font-size: .8rem; font-weight: 600; }

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

.form { margin-top: 8px; }
.field { display: grid; gap: 6px; margin: 0 0 20px; padding: 0; border: 0; min-width: 0; }
.field > span, .field > legend, .field > .label, .field-head > .label { font-size: .9rem; font-weight: 700; padding: 0; }
.field small { color: var(--muted); }
.field-help { margin: 0; font-size: .85rem; color: var(--muted); }

/* A label and the pencil that opens its field for editing, on one line. */
.field-head { display: flex; align-items: center; gap: 4px; }

/* Entries, exits and combos are always editable: their chips keep their ×, and the + at the end of
 * the row is how another is added. Only Notes is opened and closed, so only Notes carries the state. */

/* Notes: the paragraph the move's own page draws, or the box to write in, never both. */
.field[data-editing="off"] .notes-input { display: none; }
.field[data-editing="on"] .notes-read { display: none; }
.notes-read {
  white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; padding: 12px 16px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: var(--surface);
  max-width: none;   /* the 68ch that prose paragraphs keep: this one stands in for a field, so it lines
                        up with the box it replaces rather than reading short of it on a wide screen */
}
/* An empty note has no box of its own: the + beside the label is the whole empty state. */
.notes-read.is-empty { display: none; }

input[type="text"], input[type="search"], input[type="password"], input[type="email"], input[type="number"], select, textarea {
  width: 100%; min-height: 44px; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font: inherit;
}
/* A ring on :focus rather than :focus-visible: a field you are typing in should show where the
   caret is, whether you got there by tab or by tap. */
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
textarea { resize: vertical; min-height: 110px; }
input::placeholder, textarea::placeholder { color: var(--muted); opacity: .75; }
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }

.inline-form { display: flex; gap: 8px; margin-bottom: 16px; }
.inline-form input { flex: 1; }
/* A <select> carries width: 100%, which fights the button beside it without this. */
.inline-form select { flex: 1; min-width: 0; }
.inline-form .btn { flex: none; white-space: nowrap; }

/* A one-word answer does not need a line of its own: label first, then its control and button beside
 * it. The control keeps the hairline border every other field has but drops the fill, so the line
 * reads as text with a button rather than as a box. */
.field-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.field-row .set-label { flex: none; }
.field-row input { flex: none; width: 7.5rem; background: none; }
.field-row input:focus { background: var(--surface); }

/* The support form's honeypot: a box no person can see or tab to, which a bot filling in every input
 * will happily complete. Moved off-screen rather than `display: none`, because the obvious cases
 * skip hidden fields and this is the cheap half of what keeps an open contact endpoint usable. */
/* `input.hp` rather than `.hp` on purpose: the input rules above it are `input[type="text"]`,
 * which outranks a bare class, so the 1px box would otherwise never apply. */
input.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* radio groups drawn as pills */
.seg label { position: relative; }
.seg label input { position: absolute; opacity: 0; pointer-events: none; }
.seg label span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
  font-weight: 700; cursor: pointer;
}
.seg label span:hover { border-color: var(--muted); }
.seg label input:checked + span {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.seg label input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: 2px; }

/* Collections are a list of ticks and names, nothing drawn around them. A bordered pill made the
 * list read as a row of buttons to press, and the pink fill on a ticked one competed with the move's
 * own accents; the tick itself is the answer, and the name is the label. */
.checks { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.check {
  display: flex; align-items: center; gap: 10px; max-width: 100%;
  min-height: 34px; padding: 2px 0; cursor: pointer;
}
.check input { width: 20px; height: 20px; margin: 0; flex: none; }
/* A shared collection says so quietly after its name: the name is the label, this is a note on it. */
.check-shared {
  font-size: .7rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}

/* Entries, exits and combos: the pills and the + that adds another, with nothing drawn around them.
 * A box here was a second card inside the move's own card, and it said "field" about a row that is
 * meant to read as the thing itself. The row is centred because the + is a 34px icon button while a
 * pill is shorter; centring is what stops the two sizes from reading as a mistake. */
.chip-field { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-height: 34px; }
/* The box the + opens takes the pace of a pill rather than a full-width field, so it grows out of
 * the row instead of replacing it. Width comes from the flex basis, not the global `width: 100%`. */
.chip-field .chip-text {
  flex: 0 1 190px; width: auto; min-width: 130px; min-height: 34px; padding: 3px 12px;
  border-radius: 999px;
}
.chip-x {
  min-width: 24px; min-height: 24px; padding: 0 2px; border: 0; background: none;
  color: inherit; font-size: 1.15rem; line-height: 1; cursor: pointer;
}
.chip-x:hover { color: var(--accent-strong); }

/* ---------- move detail ---------- */

.section { margin: 24px 0; }
.section h2 {
  margin: 0 0 8px; color: var(--muted); font-size: .72rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
}
.section p { margin: 0; }
.notes {
  white-space: pre-wrap; overflow-wrap: anywhere; padding: 12px 16px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: var(--surface);
}

/* A support message in the inbox: what somebody wrote is a paragraph, not a line, so the row lines up
   at the top and the body gets a little air above it. */
.support-row { align-items: flex-start; }
.support-row .notes { margin-top: 8px; }

/* ---------- account & auth ---------- */

.check-row { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; }
.check-row input { margin-top: 4px; width: 20px; height: 20px; flex: none; }
.check-row span { display: grid; gap: 2px; }

/* The username and its pencil share one row, so the pencil sits right of the name instead of at the
 * far edge of the page head. Editing swaps the name for an input in that same row, so nothing below
 * the head moves. The follow bars ride in the same row; wrapping is what puts them on their own line
 * when a name and three bars do not fit side by side. */
.name-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 6px; min-width: 0; }
.name-form { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.name-form input {
  flex: 1 1 220px; width: auto; min-width: 0; min-height: 40px;
  font: 700 clamp(1.25rem, 4.5vw, 1.6rem)/1.2 var(--serif);
}
/* A pencil reads as part of what it edits rather than a boxed control: no panel behind it. Every
   edit pencil in the app is drawn this way - the field labels, the profile corner, and the
   "Request edit" on a trick or a studio - so an edit is never the loudest thing on the page. */
.icon-btn.plain { width: 34px; height: 34px; border-color: transparent; background: none; color: var(--muted); }
.icon-btn.plain:hover { border-color: transparent; background: none; color: var(--accent-strong); }
/* The open state still says so, in pink rather than in a white box. */
.icon-btn.plain[aria-expanded="true"] {
  border-color: transparent; background: var(--accent-soft); color: var(--accent-strong);
}
/* A corner pencil keeps the 40px target the other icon buttons have. */
.icon-btn.plain.profile-edit, .icon-btn.plain.request-edit { width: 40px; height: 40px; }
.profile-edit svg, .request-edit svg { width: 18px; height: 18px; }
/* On a phone the avatar leaves no room for a name, an input and two buttons on one line, so the
 * input takes the width and the buttons take the next line on purpose instead of by accident. */
@media (max-width: 460px) {
  .name-form input { flex: 1 1 100%; }
}

/* One shape for every setting on the account page: a small label, the control, a line of help, and
 * a rule between one setting and the next. */
.set-block { display: grid; gap: 10px; }
.set-block + .set-block { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.set-label { font-size: .9rem; font-weight: 700; }
/* The Settings entry on the profile page: a gear and its name. A row sized for a thumb rather than
 * a pill that asks to be noticed. */
.set-link {
  display: inline-flex; align-items: center; gap: 10px; min-height: 44px;
  font-size: .9rem; font-weight: 700; color: var(--ink); text-decoration: none;
}
.set-link:hover { color: var(--accent-strong); }

/* A whole row that is a link: the Support and Settings entries at the foot of a profile. The icon
 * keeps a column of its own so two rows line up, and a label over its one-line explanation is the
 * same shape as every other titled row in the app. */
.link-row {
  display: flex; align-items: center; gap: 14px; min-height: 56px;
  color: var(--ink); text-decoration: none;
}
.link-row svg { flex: none; color: var(--muted); }
.link-row strong { display: block; font-weight: 700; }
.link-row small { display: block; color: var(--muted); font-size: .85rem; }
.link-row:hover { color: var(--accent-strong); }
.link-row:hover svg { color: var(--accent-strong); }
/* The chevron is a sibling of the text in the same flex row, so it has to be told to go to the far
 * edge; without this it would sit right after the last word of the explanation. */
.link-row > svg:last-child { margin-left: auto; }

/* Logging out sits apart from the rows above it - a rule between them - so it is a deliberate step
 * rather than the next line of the settings list. */
.account-actions { margin-top: 28px; padding-top: 22px; border-top: 1px solid var(--line); }
/* A card with no subhead over it. The subhead is what normally spaces a section from the one above,
 * so a card that carries its own label has to bring that gap with it. */
.card.standalone { margin-top: 30px; }
.set-block .inline-form { margin-bottom: 0; }
.set-block .avatar-picker { margin: 0; }
/* Named so it beats `.card p`, which would otherwise space these out. */
.set-block .field-help { margin: 0; font-size: .85rem; }

/* ---------- settings: equal groupings ----------
   Every group is the same card, the same padding and the same internal rhythm, and they sit in one
   equal row where there is room: the track is narrow enough that three fit side by side at the
   page's own max width, so the delete group is never a half-width orphan and nothing competes for
   attention with it. Each card is a column so its control row can be pushed to the bottom with
   margin-top: auto - that is what lines the controls of neighbouring cards up when one of them has
   more to say than the other. */
.set-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px; align-items: stretch;
}
.set-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 18px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.set-title { margin: 0; font: 700 1rem/1.25 var(--sans); letter-spacing: -.01em; }
.set-help { margin: 0; color: var(--muted); font-size: .85rem; max-width: none; }
.set-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; padding-top: 4px; }
/* The one control that is a field rather than a button: the type-your-username box in the delete
   group. It takes what room there is and leaves the button its own width, so the pair lines up with
   the button rows of the other cards. */
.set-actions input { flex: 1 1 160px; min-width: 0; }
/* The theme switcher is the Appearance card's control, so it sits on the same bottom line as every
   other card's control row. Three pills only just fit the narrowest column (230px, so ~194px of
   content): left to themselves they wrap onto a second line, which gave the Appearance card the only
   ragged bottom edge in the row - exactly what "equal groupings" was meant to stop. So they share
   the row evenly, at a slightly smaller size that fits without ellipsis, and never wrap. */
.set-card .seg { margin-top: auto; padding-top: 4px; flex-wrap: nowrap; }
.set-card .seg-btn {
  flex: 1 1 0; min-width: 0; padding: 0 8px; font-size: .8rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.set-card .btn { min-width: 0; }

.auth { max-width: 440px; margin: 4vh auto 0; }
.brand-lg { font-size: clamp(2.4rem, 9vw, 3.4rem); }
.brand-lg em { font-weight: 400; color: var(--accent); }
.auth .seg { margin-bottom: 18px; }

/* ---------- avatars ---------- */

.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: calc(32px * var(--avatar-scale)); height: calc(32px * var(--avatar-scale));
  flex: none; border-radius: 50%; overflow: hidden;
  /* The rim scales with everything else on purpose. A fixed 1px rim shrinks the inside of a big
   * circle and grows it in a small one, so the circle, its rim and its inset all carry the scale and
   * the picture keeps exactly its share at any size. It also has to: the photo is a square inside a
   * round clip, so once it is wider than the circle's inscribed square its corners get shaved off. */
  background: var(--surface); border: calc(2px * var(--avatar-scale)) solid var(--accent);
  object-fit: cover;
}
/* A photo comes with its own white background, so the disc is white too and the picture is inset a
 * little: the shoe then sits in white instead of touching the rim. The inset is the element's own
 * padding, which object-fit respects, so the photo scales down rather than being cropped. In px, not
 * %, because a percentage padding resolves against the parent's width instead of the circle's. */
img.avatar { background: #fff; padding: calc(5px * var(--avatar-scale)); }
/* The pleaser artwork is dark line work, so its disc stays light in both themes - a dark disc in
 * dark mode would leave nothing to see. A deliberate exception to the palette, not an oversight. */
img.avatar-art { background: #f2f2f5; }
/* No picture: the initial sits on the same light disc and pink rim as the heel photos, with the
   letter in the brand pink. The rim comes from .avatar, so the only thing this rule changes is the
   fill and the letter colour - every circle in the app wears the one rim, whether it holds a photo,
   the artwork or a letter. */
.avatar-letter { background: var(--surface); color: var(--accent-strong); font: 700 calc(1rem * var(--avatar-scale))/1 var(--sans); }
.avatar-xs { width: calc(20px * var(--avatar-scale)); height: calc(20px * var(--avatar-scale)); }
.avatar-sm { width: calc(34px * var(--avatar-scale)); height: calc(34px * var(--avatar-scale)); }
.avatar-md { width: calc(52px * var(--avatar-scale)); height: calc(52px * var(--avatar-scale)); }
.avatar-lg { width: calc(72px * var(--avatar-scale)); height: calc(72px * var(--avatar-scale)); }
/* One inset per size, so every circle keeps roughly the same share of white. Written as img.avatar-*
 * because a plain .avatar-lg would lose to img.avatar above on specificity; equal specificity here
 * means source order decides, which is what these rely on. */
img.avatar-xs { padding: calc(3px * var(--avatar-scale)); }
img.avatar-sm { padding: calc(5px * var(--avatar-scale)); }
img.avatar-md { padding: calc(8px * var(--avatar-scale)); }
img.avatar-lg { padding: calc(10px * var(--avatar-scale)); }
.avatar-md.avatar-letter { font-size: calc(1.35rem * var(--avatar-scale)); }
.avatar-lg.avatar-letter { font-size: calc(1.9rem * var(--avatar-scale)); }

/* avatar beside a name in a page head. min-width: 0 lets the block beside the avatar give way when
 * there is not room, so the follow bars below it wrap instead of pushing the page sideways. */
/* The block beside the avatar takes the rest of the head's width rather than shrink-wrapping its
   content, so a control that belongs at the far end of one of its lines has somewhere to go. */
.who { display: flex; flex: 1 1 auto; align-items: center; gap: 14px; min-width: 0; }

/* A profile head: who it is on the left, and the follow lists beside the name. flex-start so a taller
 * open bar lines up with the top of the block instead of stretching its closed neighbours, and it
 * wraps instead of squeezing the name. */
.profile-head { align-items: flex-start; flex-wrap: wrap; gap: 14px 18px; }
.who > div { flex: 1 1 auto; min-width: 0; }
.who p { margin: 0; }
/* Somebody else's profile keeps its follow control on the same line as the count bars, at the far
   end of it: the bars answer "who", and the one thing you can do about it sits at the end of that
   sentence rather than halfway down the page. It brings its own room because the lines it sits
   between carry no margin of their own. The bars grow and the button does not, so the button stays
   pinned right even when the bars wrap onto a second line on a phone. */
.who .btn { margin: 8px 0; }
/* The bars are pills, so their labels begin one padding inside the row's left edge while the lines
   under them - Poler since, where they train - start at that edge. Pulling the row out by exactly
   that padding puts "Followers" and "Poler since" on one vertical line, which is the line the eye
   follows down the block. The pill's own border hangs just outside it, which is how a control that
   holds a word should sit against a column of text. */
.follow-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin: 8px 0 8px -10px; }
.follow-row .follow-bars { flex: 1 1 auto; }
/* The follow control is the same height as the pills it stands beside, and round like them: one more
   mark on that line rather than a 40px button that happened to land next to a caption. flex-start
   keeps it against the first line when the bars wrap onto a second one on a phone. */
.follow-toggle {
  width: 26px; height: 26px; min-height: 26px; padding: 0;
  border-radius: 999px;
}
.follow-toggle svg { width: 15px; height: 15px; }
.follow-toggle:hover { border-color: var(--accent); color: var(--accent-strong); }
.follow-toggle[aria-pressed="true"] { border-color: var(--accent); color: var(--accent-strong); }
.who .eyebrow { margin-bottom: 6px; }

/* ---------- the follow bars in a page head ---------- */

/* Followers, Friends and Following as a row of bars beside the name: a label, a count, and the
 * list itself once the bar is opened. The opening is a native <details>, so it works from the
 * keyboard with no script, and the panel drops below its own bar instead of covering the page.
 * flex-start matters: without it a taller open bar would stretch its closed neighbours. */
/* width: max-content is load-bearing: a wrapping flex container reports only its widest single
 * item as its intrinsic width, which collapsed the name block to one bar per line. Asking for
 * max-content states the row's real width, and max-width lets it wrap on a narrow screen. */
.follow-bars {
  display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px;
  width: max-content; max-width: 100%;
}
.follow-bar { min-width: 0; }
/* The summary is the whole bar, so it is aligned to the top of its own line box rather than on the
 * text baseline: otherwise the row reserves a couple of pixels of leading above the pill, and the
 * Follow button beside it - aligned to the row, not to the baseline - sits that much higher. */
.follow-bar > summary { vertical-align: top; }
/* Label-sized on purpose: the bars sit just under the dancer's name, so they read as a caption on
 * that name rather than as three controls that happened to land there. The
 * padding is asymmetric because the count on the right carries its own pink pill. */
.follow-bar > summary {
  display: inline-flex; align-items: center; gap: 6px; min-height: 26px; padding: 0 4px 0 9px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
  color: var(--ink); font: 700 .74rem/1 var(--sans); cursor: pointer; list-style: none;
}
.follow-bar .count { min-width: 19px; padding: 1px 6px; font-size: .68rem; }
/* Safari draws its own triangle in the marker pseudo-element rather than as a list marker. */
.follow-bar > summary::-webkit-details-marker { display: none; }
.follow-bar > summary::marker { content: ''; }
.follow-bar > summary:hover { border-color: var(--accent); }
.follow-bar[open] > summary { border-color: var(--accent-strong); background: var(--accent-soft); color: var(--accent-strong); }
/* The count would otherwise disappear into the open bar's own tint. */
.follow-bar[open] .count { background: var(--surface); }
/* Out of the flow on purpose: the list drops over what is below the header instead of shoving the
 * page down, which is what makes a bar a dropdown rather than an accordion. It measures against the
 * page head - left/right: 0 is that head's own width - so a row inside always has room for a name
 * and a button, and it can never stick out past the page the way a content-sized box would. */
.follow-panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 10px); z-index: 5;
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
  box-shadow: 0 14px 34px rgb(0 0 0 / 12%);
}
.follow-panel > :first-child { margin-top: 0; }
.follow-panel .subnote { margin: 0 0 10px; }

/* ---------- journal ----------
 * A native <dialog> docked to the right edge, so Escape and the focus trap come from the browser, and
 * a floating notepad button that opens it. Where the button sits in pixels is decided in js/app.js
 * (which reads back where it was last dropped); how it looks is here.
 *
 * touch-action: none is what makes a drag the button's rather than the page's scroll. The price is
 * that a swipe starting on the button scrolls nothing, which is why it is a 52px disc in a corner. */
.journal-fab {
  position: fixed; z-index: 12;
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; padding: 0;
  border: 1px solid rgb(255 255 255 / .16); border-radius: 50%;
  background: var(--bar); color: var(--bar-ink);
  cursor: grab; touch-action: none;
  box-shadow: 0 6px 18px rgb(0 0 0 / 30%);
  transition: transform .15s ease, box-shadow .15s ease;
}
.journal-fab[hidden] { display: none; }
.journal-fab:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgb(0 0 0 / 34%); }
/* While it is being moved: no transition, so it tracks the finger instead of lagging behind it, and a
 * cursor that says the drag is the point. */
.journal-fab.dragging { cursor: grabbing; transform: scale(1.05); transition: none; }
@media (prefers-reduced-motion: reduce) { .journal-fab { transition: none; } }
.journal-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; left: auto; margin: 0; height: 100dvh; max-height: 100dvh;
  width: min(420px, 100vw); max-width: 100vw;
  padding: 18px 20px; border: 0; border-left: 1px solid var(--line);
  background: var(--surface); color: var(--ink); overflow-y: auto;
  box-shadow: -24px 0 60px rgb(0 0 0 / 22%);
}
.journal-drawer:focus { outline: none; }
.journal-drawer::backdrop { background: rgb(0 0 0 / .35); }
.journal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.journal-head h2 { margin: 0; }
.journal-day { margin: 0 0 8px; color: var(--muted); font-weight: 600; }
.journal-list { list-style: none; margin: 20px 0 0; padding: 0; }
.journal-entry { padding: 12px 0; border-top: 1px solid var(--line); }
.journal-entry-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.journal-entry-head time { color: var(--muted); font-size: .9rem; font-weight: 600; }
.journal-entry-body { margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.journal-media { margin-top: 10px; }
.journal-media .media-item img, .journal-media .media-item video { aspect-ratio: 4 / 3; }

/* ---------- a dialogue ----------
   The browser owns the backdrop, the Escape key and the focus trap; this is only the box. Used by
   the add-a-trick form, which asks three questions that belong together. */
.sheet {
  width: min(560px, calc(100% - 24px)); max-height: calc(100dvh - 48px); overflow: auto;
  padding: 20px 22px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  box-shadow: 0 24px 60px rgb(0 0 0 / 28%);
}
.sheet::backdrop { background: rgb(0 0 0 / .45); }
.sheet h2 { margin: 0 0 6px; }
/* A question about one thing is a sentence and two buttons, so it is narrower than a form's sheet.
   There is no positioning here on purpose: a modal <dialog> is centred in the top layer by the
   browser (margin: auto), so the box sits in the middle of the page at any window size - which is
   the one thing window.confirm() could never be told to do. */
.confirm-sheet { width: min(420px, calc(100% - 24px)); }
.confirm-sheet p { margin: 0 0 16px; }
/* The question is the one thing being read, so the sentence under it is the ink, not the muted grey
   a caption would use, and it wraps at the box rather than at a measure. */
.confirm-sheet #confirm-body { color: var(--ink); overflow-wrap: anywhere; }
/* The form's own fields carry a bottom margin everywhere else, but the last thing in a dialogue is
   its button row and that margin would leave the box looking unfinished. */
.sheet .actions { margin-bottom: 0; }
/* Four choices - Unrated and the three levels - as two columns: the widest that still fits a phone,
   and all four the same size. */
.seg-2col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.seg-2col label { display: flex; }
.seg-2col label span { flex: 1; justify-content: center; text-align: center; }

/* The profile editor's two sheets: details behind the pencil, the picture behind "Change avatar". Each
 * is a dialogue with its own Save. */
#avatar-dialog .avatar-picker { margin: 12px 0 0; }

/* The "Add new goal" panel: the two ways in as tabs across the top - one of my moves, a trick from
 * the dictionary - and the chosen one's options underneath. A column down the left made the box as
 * tall as the longest way in and left the list squeezed into a narrow strip beside it; along the top
 * the box reads in one direction: which way in, then the way in. */
.goal-add {
  position: relative;
  margin-top: 18px; padding: 14px 16px 16px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.goal-add .inline-form { margin-bottom: 0; }
/* The cross hangs partly off the top and right edges of the box it closes: pink on hover like every
 * other quiet control, and a circle so it reads as the same family as the icon buttons in a page head. */
.panel-x {
  position: absolute; top: -12px; right: -12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.panel-x:hover { border-color: var(--accent); color: var(--accent-strong); }
/* The ways in in one row above the panel, one column of content under them. The top padding is the
 * band the cross sits in, so nothing is ever drawn under it. `flex: 1 1 0` with `min-width: 0` gives
 * the tabs equal widths rather than widths sized to their words: a row that sized to its text would
 * make one way in look more important than the other, and these are a choice between equals. */
.goal-add-grid {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 12px; align-items: start; padding-top: 30px;
}
.goal-sources { display: flex; gap: 6px; min-width: 0; }
.goal-source-btn {
  flex: 1 1 0; min-width: 0;
  min-height: 40px; padding: 8px 10px; text-align: center; overflow-wrap: anywhere;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink);
  font: 700 .85rem/1.25 var(--sans); cursor: pointer;
}
.goal-source-btn:hover { border-color: var(--muted); }
/* The chosen source: the pink text and edge the palette check measures against --accent-soft, so the
 * selected row reads as selected in both themes without a tick beside it. */
.goal-source-btn[aria-pressed="true"] {
  border-color: var(--accent-strong); background: var(--accent-soft); color: var(--accent-strong);
}
/* On a narrow phone the dialogue leaves each tab less than a word wide at the normal size, and a tab
 * label must never break down the middle of a word. A slightly smaller label and tighter side
 * padding below 380px keeps both on one unbroken line; `overflow-wrap` on the button above stays as
 * the last resort for anything narrower than that. */
@media (max-width: 380px) {
  .goal-source-btn { padding: 8px 6px; font-size: .8rem; }
}
.goal-source-block { display: grid; gap: 10px; align-content: start; min-width: 0; }
/* In the overlay the panel is the dialogue itself: no box inside a box. The cross is placed in the
 * dialogue's own corner, so the panel is not positioned against the box it used to sit in. */
.goal-sheet .goal-add { position: static; margin-top: 0; padding: 0; border: 0; background: transparent; }
.goal-sheet .goal-add-grid { padding-top: 0; }
.goal-sheet .panel-x { top: 12px; right: 12px; }
.goal-sheet h2 { padding-right: 44px; }

/* The camera on a profile picture: a small disc on the bottom edge of the avatar, the same family as
 * the icon buttons. The avatar is wrapped so the disc has something to sit on. */
.avatar-wrap { position: relative; display: inline-flex; flex: none; }
.avatar-cam {
  position: absolute; left: 50%; bottom: -4px; transform: translateX(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border-radius: 999px;
  border: 2px solid var(--surface); background: var(--surface); color: var(--ink); cursor: pointer;
}
.avatar-cam:hover { border-color: var(--accent); color: var(--accent-strong); }

/* The profile's edit pencil sits in the top right corner of the head, out of the name's way. */
.profile-head { position: relative; padding-right: 56px; }
.profile-edit { position: absolute; top: 0; right: 0; }
/* A member page has no corner pencil, so the room it reserves would leave its follow control short
   of the edge every other row on the page ends at. */
.profile-head.no-pencil { padding-right: 0; }
/* The same corner pencil asks an admin for a correction on studio and trick pages. Like every other
   pencil it is drawn without a panel behind it, so it is the icon that changes colour, not a box. */
.request-edit { position: absolute; top: 0; right: 0; }

.avatar-upload { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0 0; }
.avatar-picker { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 0; }
.avatar-opt {
  padding: 3px; border: 2px solid transparent; border-radius: 50%;
  background: none; cursor: pointer; line-height: 0;
}
.avatar-opt:hover { border-color: var(--line); }
.avatar-opt[aria-pressed="true"] { border-color: var(--accent); }

/* ---------- community ---------- */

/* the avatar that opens a feed row, so a name is not just text */
.feed-who { flex: none; line-height: 0; }
/* The picture on an upload in the feed, so a photo reads differently from a move at a glance. It is
 * part of that update's card, under the line that says what it is: full width and 16:9, like a media
 * card anywhere else, rather than a 72px square at the end of a row that no longer exists. */
.feed-media {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; max-height: 220px; margin-top: 2px;
  border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--line); line-height: 0;
}
.feed-media img, .feed-media video { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-owner {
  display: flex; align-items: center; gap: 6px; padding: 8px;
  font-size: .78rem; color: var(--muted); overflow-wrap: anywhere;
}
.media-owner a { color: inherit; }

/* ---------- photos & videos ---------- */

.media-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px; margin-top: 10px;
}
.media-item {
  position: relative; margin: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
}
.media-item img, .media-item video {
  display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; background: #000;
}
.media-remove {
  position: absolute; top: 6px; right: 6px; width: 28px; height: 28px; padding: 0;
  border: 0; border-radius: 50%; background: rgb(28 27 32 / .55); color: #fff;
  font: 700 1.05rem/1 var(--sans); cursor: pointer;
}
.media-remove:hover { background: var(--danger); color: var(--danger-ink); }
.upload-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 12px; }
.upload-note { font-size: .8rem; }

/* ---------- files picked in the add-move form ----------
   Chosen but not yet uploaded, so each row is a name and a way to take it back off before saving.
   The name truncates rather than wrapping: a long filename should not push the remove button off. */
.file-list { list-style: none; display: grid; gap: 8px; margin: 12px 0 0; padding: 0; }
/* Nothing picked yet: the list is not a row of its own, so it must not leave the gap of one under
   the label - the + beside the label is the whole empty state. */
.file-list:empty { display: none; }
.file-chip {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
  font-size: .85rem;
}
.file-chip-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; }
.file-chip-size { color: var(--muted); font-variant-numeric: tabular-nums; }
.file-chip-x {
  flex: none; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--muted); font: 700 1.05rem/1 var(--sans); cursor: pointer;
}
.file-chip-x:hover { color: var(--danger); background: var(--surface); }

.entry-link { color: inherit; text-decoration: none; border-bottom: 1px solid var(--line); }
.entry-link:hover { border-color: var(--accent); color: var(--accent-strong); }

/* The rest of a dancer's studios, behind a "+N" that opens where it sits. A profile leads with one
   place and folds the others away, so the summoning control is a small round button the size of the
   line it sits on rather than a bar; the panel is the same box a follow bar drops. */
.studio-more { position: relative; display: inline-block; margin-left: 6px; vertical-align: baseline; }
.studio-more-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; min-height: 22px; padding: 0 7px; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface);
  color: var(--accent-strong); font: 700 .74rem/1 var(--sans); cursor: default;
}
.studio-more:hover .studio-more-count,
.studio-more:focus-visible .studio-more-count { border-color: var(--accent); }
.studio-more:focus-visible { outline: none; }
/* The panel only shows while the count is hovered or focused. Its transparent top padding closes
   the gap to the count, so the pointer can travel down into the panel without losing the hover. */
.studio-more-panel {
  display: none; position: absolute; left: 0; top: 100%; z-index: 5; width: max-content; max-width: 60vw;
  padding-top: 8px;
}
.studio-more:hover .studio-more-panel,
.studio-more:focus-visible .studio-more-panel { display: block; }
.studio-more-box {
  display: block; padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); box-shadow: 0 14px 34px rgb(0 0 0 / 12%);
}
.studio-more-item { display: block; }
.studio-more-item + .studio-more-item { margin-top: 6px; }

/* The list of studios in the profile editor: one row per place, in the dancer's own order, with the
   top row marked because that is the one a profile shows. Rows rather than chips because a row has
   room for the remove control and the count of rows is what the +N counts. */
/* min-width: 0 is what makes the ellipsis work. The list is a grid item in .set-block, and an auto
   grid track sizes itself to its widest row: without this the one long name would widen the whole
   sheet instead of being cut, and every other row would stretch with it. */
.studio-draft { list-style: none; margin: 0; padding: 0; min-width: 0; }
.studio-draft-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px; min-width: 0;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.studio-draft-row:first-child { border-top: 1px solid var(--line); }
/* One line, shortened with an ellipsis. A studio's name is not what this editor is for, and a name
   long enough to wrap would push the arrows and Remove onto a line of their own and make the editor
   taller than the fields around it. The whole name is the row's title, and the studio's own page
   carries it in full. */
.studio-draft-name {
  flex: 1 1 10rem; min-width: 9rem; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The arrows and Remove stay together at the far end of their row. */
.studio-draft-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.studio-draft-actions .icon-btn.plain { width: 30px; height: 30px; }
.studio-draft-actions .icon-btn.plain svg { width: 15px; height: 15px; }


/* The + under the list opens the studios to choose from, as a panel over the form rather than a
   dropdown: the list is what is being read, so it carries a search box, then the studios, then the
   way to a studio nobody has added yet. Short and scrollable, and over the fields rather than
   between them, so opening it never moves the list it belongs to. */
.studio-pick-wrap { position: relative; margin-top: 10px; }
.studio-picker {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 6;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px;
  width: min(420px, 100%); max-height: 320px; overflow: auto;
  padding: 10px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); box-shadow: 0 18px 40px rgb(0 0 0 / 18%);
}
/* The + sits near the bottom of a sheet that fills a phone, so the panel opens upwards when there
   is more room above it. It is flipped only when both sides fit, so it never leaves the screen. */
.studio-picker.opens-up { top: auto; bottom: calc(100% + 6px); }
.studio-picker[hidden] { display: none; }
.studio-picker input[type="search"] { width: 100%; }
.studio-picker-list { list-style: none; margin: 0; padding: 0; min-width: 0; }
.studio-picker-row {
  display: flex; align-items: center; gap: 8px; width: 100%; min-width: 0; box-sizing: border-box;
  padding: 9px 8px; border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--ink); text-align: left; cursor: pointer;
  font: 700 .9rem/1.2 var(--sans);
}
.studio-picker-row:hover { background: var(--surface-2); }
/* The same one line as the list above: a long name is shortened rather than allowed to widen the
   panel, and the town gives way before the name does. */
.studio-picker-name {
  flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.studio-picker-city {
  flex: 0 1 auto; min-width: 0; color: var(--muted); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.studio-picker-added {
  flex: none; padding: 3px 8px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong); font: 700 .72rem/1 var(--sans);
}
/* A studio already in the list answers "can I add this?" by saying so, and is not a control. */
.studio-picker-row.is-added { color: var(--muted); cursor: default; }
.studio-picker-row.is-added:hover { background: none; }
.studio-picker .btn { justify-self: start; }
.studio-picker .field-row input { flex: 1 1 7rem; width: auto; }

/* A link inside a line of text that must still read as text - the studio on a profile, which is worth
   following to its page and should not shout about it. */
.quiet-link {
  color: inherit; text-decoration: underline; text-decoration-thickness: 1px;
  text-decoration-color: var(--line); text-underline-offset: 2px;
}
.quiet-link:hover { color: var(--accent-strong); text-decoration-color: var(--accent); }

/* Who logged a trick, as mini avatars on the dictionary screens. Nothing is drawn when nobody has, so an
 * unused trick gets no empty row. */
.loggers { display: inline-flex; align-items: center; gap: 4px; }
.logger { display: inline-flex; line-height: 0; border-radius: 50%; }
.logger:hover { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Counts the members past the third, so the row never grows past three faces. Tracks the avatar
 * scale so it stays the same size as the circles beside it. */
.logger-more {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: calc(20px * var(--avatar-scale)); height: calc(20px * var(--avatar-scale));
  padding: 0 5px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong);
  font-size: .72rem; font-weight: 700; line-height: 1;
}
/* The avatars and their caption on one line, on a trick page. */
.loggers-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.tag a { color: inherit; }

/* ---------- one dictionary entry ----------
   Read as a short article rather than a stack of cards: a title block on the one strong rule in the
   app - pink rather than black, so the trick is what stands out and not the line under it - then one
   quiet strip of facts, then clearly headed sections. */
/* Two gaps, not one. Everything that belongs to the trick itself - its name, the facts, the button
   that adds it to your moves - sits in one group at the smaller gap, and the groups are separated by
   the larger one. Reading down the page is then a matter of seeing three blocks rather than seven
   equally spaced things, which is what "space it out" actually asks for: the old single 20px gap
   gave the name and the page's last paragraph the same weight. */
.entry { display: grid; gap: 30px; }
.entry > *, .entry-hero > * { margin-top: 0; margin-bottom: 0; }
.entry-hero { display: grid; gap: 18px; position: relative; }
.entry-head {
  display: grid; gap: 8px;
  padding: 0 52px 16px 0; border-bottom: 2px solid var(--accent);
}
.entry-title { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px; }
.entry-title h1 { margin: 0; }
.entry-head .lede { margin: 0; max-width: 60ch; }
/* Three quiet tiles sharing hairlines, not three loud cards: the facts are context for the trick,
   and the trick is what should be loud.
   On a phone each fact is a line - the label at one end, its answer at the other - because three
   tiles across a phone cannot hold a word like "Intermediate": the old auto-fit row fitted two and
   pushed the third onto a line of its own, leaving a hole in the corner that read as a value that
   had gone missing. Rows cannot fail that way, and every answer stays at the same left/right edge. */
.entry-facts {
  display: grid; gap: 1px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.fact {
  display: flex; flex-direction: row-reverse; justify-content: space-between;
  align-items: baseline; gap: 12px; padding: 13px 16px; background: var(--surface);
}
.fact-value {
  font: 700 1.2rem/1.2 var(--sans); letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
.fact-label {
  color: var(--muted); font-size: .66rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
}
/* The third fact is a button: the answer is what the tile says, and the line under it is what pressing
   it does. Hovering or tabbing to it swaps that line - it is not added, so the strip never changes
   height under the pointer and the other two facts stay where they were - and the tile takes the soft
   pink so the whole block reads as the target rather than the two words in the corner. */
.fact-do { color: inherit; text-decoration: none; cursor: pointer; }
.fact-do .fact-say { display: none; color: var(--accent-strong); }
.fact-do:hover, .fact-do:focus-visible { background: var(--accent-soft); }
.fact-do:hover .fact-quiet, .fact-do:focus-visible .fact-quiet { display: none; }
.fact-do:hover .fact-say, .fact-do:focus-visible .fact-say { display: inline; }
.fact-do:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Once there is room, the same three facts are the tiles they were designed as. */
@media (min-width: 560px) {
  .entry-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .fact { flex-direction: column; align-items: flex-start; gap: 3px; }
}

/* ---------- toast ---------- */

#toast {
  position: fixed; left: 50%; bottom: 96px; z-index: 20;
  /* As wide as the auth column so a full sentence like "check your email" reads in a couple of
     lines instead of a tall egg. Short confirmations simply get a roomy box. */
  width: min(440px, calc(100% - 32px)); padding: 14px 18px; border-radius: var(--radius-sm);
  background: var(--bar); color: var(--bar-ink); font-weight: 700; text-align: center;
  box-shadow: 0 12px 30px rgb(0 0 0 / 18%);
  opacity: 0; transform: translate(-50%, 16px); pointer-events: none;
  transition: opacity .2s, transform .2s;
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }
#toast.error { background: var(--danger); color: var(--danger-ink); }
@media (min-width: 701px) { #toast { bottom: 32px; } }

/* ---------- community difficulty (levels) ---------- */

/* The badge in a page head or a dictionary row: what the members who answered said, and how many of them
   agree. The three levels run quiet grey -> hot pink -> deep plum, the same ramp as the move states
   above, so a level reads the same in light and dark and in the same direction a dancer expects.
   The top of the ramp is a colour now rather than a black slab, which is what keeps the hardest
   thing on a dictionary row from being a chip about a trick nobody found hard. */
.level-badge { background: var(--accent-soft); color: var(--accent-strong); }
.badge.level-beginner     { background: var(--lvl1-bg); color: var(--lvl1-fg); }
.badge.level-intermediate { background: var(--lvl2-bg); color: var(--lvl2-fg); }
.badge.level-advanced     { background: var(--lvl3-bg); color: var(--lvl3-fg); }

/* Voting: the pills are the same .seg radio group the move form uses, so they need nothing here
   beyond a little breathing room. */
.level-pick .seg { margin-bottom: 4px; }
/* The three answers are one track of three equal parts, not three pills left to wrap. Left alone
   they wrapped to two rows - Beginner and Intermediate on the first, Advanced alone under them -
   so the question "how hard is this?" arrived looking like a broken row. Equal parts is complete at
   every width: a long word wraps inside its own pill, and because the row is a grid all three stay
   the same height when it does. */
.level-pick .seg { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.level-pick .seg label { display: flex; }
.level-pick .seg label span { flex: 1; justify-content: center; padding: 0 8px; text-align: center; }
.level-pick small { color: var(--muted); }

/* A section drawn as its own panel, so it is visibly a separate thing from the section above it:
   the community's answer, and the clips on a trick. `.panel` is the general form and the trick
   page's level card is one of them - the two must stay the same box, or the page grows a section
   that does not line up with its neighbour. */
.level-card, .alias-card, .parts-card, .section.panel {
  padding: 18px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.level-card h2 { margin-bottom: 10px; }

/* Also known as: the nicknames members have filed for one trick, as the same chips an entry or exit
   is drawn as - they are the same kind of thing, a short name a dancer typed - with a × on the ones
   this reader may take off. The lead on a nickname is the name itself, so the × is inside the chip
   rather than beside it. */
.alias-card .chips { margin: 12px 0 0; }
.alias-chip { padding-right: 5px; }
/* The form under the chips is the last thing in the card, and the card's own padding is the gap. */
.alias-card .inline-form,
.parts-card .inline-form { margin: 16px 0 0; }

/* Entries & exits, gathered from every move of this trick: two short lists of the same chips, each
   with its own small heading, because "what do people get into this from" and "what do they get out
   into" are different questions with different answers. */
.parts-title {
  margin: 16px 0 0; color: var(--muted); font: 700 .72rem/1.2 var(--sans);
  letter-spacing: .12em; text-transform: uppercase;
}
.parts-title + .chips { margin-top: 8px; }
/* The number of members who said it, on the chip: quieter than the name, so a common entry does not
   shout over the trick it belongs to. */
.chip-count {
  padding: 0 6px; border-radius: 999px;
  background: var(--surface); color: var(--muted); font-size: .72rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.level-card .level-pick { margin-bottom: 6px; }

/* One row per level: name, bar, share, number of votes. The bar is a track with a fill, so the
   same markup reads as "nothing yet" at 0% without a special case. The bars are only drawn once
   somebody has answered at all; see levelSection() in app.js. */
.level-bars { display: grid; gap: 10px; margin: 16px 0 4px; }
.level-row {
  display: grid; grid-template-columns: 6.5rem 1fr 2.75rem 4.5rem;
  align-items: center; gap: 10px; font-size: .85rem;
}
.level-name { font-weight: 700; }
.level-track {
  display: block; height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden;
}
.level-fill { display: block; height: 100%; border-radius: 999px; background: var(--muted); }
.level-fill.level-beginner     { background: var(--lvl1-fill); }
.level-fill.level-intermediate { background: var(--lvl2-fill); }
.level-fill.level-advanced     { background: var(--lvl3-fill); }
.level-pct { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.level-count { white-space: nowrap; text-align: right; color: var(--muted); }
/* No mark on the leading row. A tick after the level most members chose read as a tick against the
 * level *you* chose - the two are usually the same, and the one person it misleads is the one who
 * voted differently and now thinks the app has agreed with the room. The numbers on the row say
 * which level is ahead, and they say it without being read as a verdict on your own answer. */

@media (max-width: 420px) {
  .level-row { grid-template-columns: 5.5rem 1fr 2.5rem; }
  .level-count { display: none; }   /* the share is the answer; the count is the detail */
}

/* ---------- misc ---------- */

/* For a hint that would otherwise be another row of visual clutter. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.file-warning { display: none; padding: 12px 16px; background: var(--accent-soft); color: var(--accent-strong); font-size: .9rem; }
.no-server .file-warning { display: block; }

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