/* My Shifts.
 *
 * The design's proportions, with two deliberate corrections: every control
 * clears the 48px tap floor (the design draws some at 44), and the territory
 * tint under a shift row takes its text colour from `--t-*-deep` rather than
 * the primary, which is 1.89:1 on its own tint in sun mode
 * (04_DESIGN_HANDOFF.md § Contrast). */

.me { display: block; }

/* --- where you stand ------------------------------------------------------ */

.me-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: flex-start;
  background: var(--card);
  border-radius: var(--radius-card);
  padding: 17px 19px;
  margin-bottom: var(--space-4);
}

.me-who { flex: 1 1 160px; min-width: 0; }

.me-name {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: var(--weight-normal);
  line-height: 1.2;
  margin: 0;
  overflow-wrap: anywhere;
}

.me-pod { font-size: var(--size-small); color: var(--soft); margin: 2px 0 0; }

.me-stat { display: flex; flex-direction: column; gap: 2px; flex: none; }

.me-stat-label {
  font-size: var(--size-kicker);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--soft);
}

.me-stat-value {
  font-family: var(--font-display);
  font-size: 24px;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.me-dues {
  font-size: 15px;
  font-weight: var(--weight-semibold);
  padding-top: 6px;
  line-height: 1.2;
}

.me-stat-note { font-size: var(--size-micro); font-weight: var(--weight-semibold); }
.me-stat-note.is-soft { font-weight: var(--weight-normal); color: var(--soft); }

/* Scoped, not global — this stylesheet is linked into the SPA alongside five
   others and a bare `.is-live` would repaint half of them. */
.me-head .is-live { color: var(--live); }
.me-head .is-soon { color: var(--soon); }
.me-head .is-gap { color: var(--gap); }

.me-signout { flex: none; align-self: center; }

/* --- how camp finds you --------------------------------------------------- */

.me-editor { margin-bottom: var(--space-5); }

/* Unsaved work should be visible from across a tent: the panel grows an amber
   edge, the badge appears, and the fields you touched are outlined. Amber and
   not red — nothing is wrong, something is simply not committed yet. */
.me-editor.is-dirty { box-shadow: inset 3px 0 0 var(--soon); }

.me-editor-head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
  justify-content: space-between;
}

.me-kicker {
  font-size: var(--size-kicker);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--soft);
  margin: 0 0 11px;
}

.me-unsaved {
  font-size: var(--size-kicker);
  font-weight: var(--weight-bold);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  background: var(--soon-bg);
  color: var(--soon);
  border-radius: var(--radius-pill);
  padding: 3px 9px;
}

.me-unsaved[hidden] { display: none; }

.me-field.is-changed .field { border-color: var(--soon); }
.me-field-flag { color: var(--soon); font-weight: var(--weight-semibold); }
.me-field-flag[hidden] { display: none; }

.me-editor-note { align-self: center; font-size: var(--size-micro); color: var(--soft); }

.me-fields { display: flex; flex-direction: column; gap: 11px; }

.me-fields--row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 11px;
}

.me-field { display: flex; flex-direction: column; gap: 5px; }
.me-field .field-label { margin-top: 0; font-size: var(--size-micro); }

/* "Show only my playa name" — Rule 8. A checkbox and two lines of why, laid
   out as a row rather than as a fourth column: it is a decision about the other
   three fields, not another one of them. The whole label is the hit area, which
   on a dusty phone in the dark is the only size worth having. */
.me-publish {
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  margin-top: 13px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}

.me-publish.is-changed { border-color: var(--soon); }

/* Unavailable until there is a playa name to show, and it has to *look* it —
   a tick box that silently does nothing reads as a broken app. It keeps full
   contrast on the note that says why, because that sentence is the whole
   point of the state. */
.me-publish.is-unavailable { cursor: not-allowed; opacity: 0.75; }

/* The real control, tinted rather than re-implemented — the same treatment the
   sign-in card's "none of these are me" tick gets, so the keyboard, the screen
   reader and the label click all keep working and the colour comes out of the
   token layer instead of the browser's default blue. */
.me-publish-check {
  margin-top: 2px;
  width: 20px;
  height: 20px;
  flex: none;
  accent-color: var(--soon);
  cursor: inherit;
}
.me-publish-body { display: flex; flex-direction: column; gap: 3px; }
.me-publish-label { font-size: var(--size-small); font-weight: var(--weight-semibold); }
.me-publish-note { font-size: var(--size-micro); line-height: 1.45; }

.me-editor-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 13px; }

.me-confirm {
  background: var(--soon-bg);
  color: var(--ink);
  border-radius: var(--radius-control);
  padding: 13px 15px;
  margin-top: 13px;
}

.me-confirm-copy { font-size: var(--size-small); line-height: 1.5; margin: 0; }
.me-confirm .me-editor-actions { margin-top: 11px; }

.me .btn--ink {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--bg);
}

.me-error {
  color: var(--gap);
  font-size: var(--size-small);
  font-weight: var(--weight-semibold);
  margin: var(--space-2) 0 0;
}

/* --- the seats you hold --------------------------------------------------- */

.me-shifts { margin-top: var(--space-4); }

.me-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }

.me-row {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  /* The territory tint is painted inline by me.js, so the token layer stays the
     only place a colour is named. This is the fallback for a row without one. */
  background: var(--panel);
  color: var(--ink);
  border-radius: var(--radius-control);
  padding: 13px 15px;
  min-height: 56px;
}

.me-row-emoji { font-size: 17px; flex: none; line-height: 1; }
.me-row-body { flex: 1; min-width: 0; }

.me-row-title {
  display: block;
  font-size: var(--size-body);
  font-weight: var(--weight-semibold);
  color: var(--ink);
}

.me-row-sub {
  display: block;
  font-size: var(--size-small);
  color: var(--soft);
  font-variant-numeric: tabular-nums;
}

.me-release { flex: none; white-space: nowrap; background: transparent; }

.me-empty {
  background: var(--panel);
  border: 1px dashed var(--line);
  border-radius: var(--radius-card);
  padding: var(--space-5) var(--space-4);
  margin: 0;
  color: var(--soft);
  font-size: var(--size-body);
  text-align: center;
}

/* The wrong-pod catch: an empty page while a namesake holds shifts. Amber, not
   red — it is a signpost out of a quiet identity fork, not an error. */
.me-hint {
  background: var(--soon-bg);
  color: var(--soon);
  border-radius: var(--radius-card);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-2) 0 0;
  font-size: var(--size-body);
}

.me-hint strong { color: inherit; }

/* --- elsewhere ------------------------------------------------------------ */

.me-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.me-links .btn { text-decoration: none; }

/* --- the 401 -------------------------------------------------------------- */

.me-signin { max-width: 30rem; }
.me-signin-panel { margin-top: var(--space-4); }
.me-signin .me-fields { margin-bottom: var(--space-4); }
.me-signin-go { margin-top: var(--space-4); }
.me-signin .me-error[hidden] { display: none; }

@media (max-width: 480px) {
  .me-head { gap: var(--space-4); padding: var(--space-4); }
  .me-signout { align-self: flex-start; }
}

/* --- your portrait --------------------------------------------------------
 *
 * Sits directly under "How camp finds you" and is the same kind of thing: a
 * portrait is the other half of being findable on playa. It is only ever drawn
 * for somebody the server offered it to (`GET /api/photo/me`), so there is no
 * disabled state here and no "not for you" copy taking up a phone screen.
 *
 * The crop window is a circle over a square canvas, because a circle is what
 * every screen in this app actually draws (`.dir-photo`, `.person-photo`) — a
 * square preview would be a promise the Directory does not keep. The canvas
 * underneath is square and full-bleed so a finger dragging near the edge is
 * still dragging the picture. */

.portrait { margin-bottom: var(--space-4); }

.portrait-head { margin-bottom: var(--space-3); }
.portrait-head .me-kicker { margin-bottom: var(--space-1); }

.portrait-note {
  display: block;
  font-size: var(--size-small);
  line-height: 1.45;
  max-width: 34rem;
}

.portrait-note.is-soft,
.portrait-hint.is-soft { color: var(--soft); }

/* --- nothing loaded: what camp has, and the way to change it -------------- */

.portrait-resting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

.portrait-current,
.portrait-empty {
  width: 96px;
  height: 96px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--card);
  display: block;
}

.portrait-current {
  object-fit: cover;
  border: 1px solid var(--line);
}

.portrait-empty {
  border: 1px dashed var(--line);
  color: var(--soft);
  font-size: 28px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  display: flex;
  align-items: center;
  justify-content: center;
}

.portrait-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 15rem;
}

/* The file input is the control; the label is the button. Hiding the input with
   `display: none` would take it out of the tab order and off a screen reader,
   so it is clipped to a pixel instead — the label's `for` still focuses it and
   the keyboard still reaches it. */
.portrait-file {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A label acting as a button still has to look and measure like one. */
.portrait-choose {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  cursor: pointer;
}

.portrait-file:focus-visible + .portrait-choose,
.portrait-choose:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }

.portrait-hint {
  font-size: var(--size-small);
  line-height: 1.45;
  margin: 0;
}

/* Only inside the button row, where it is a third item that has to take its own
   line. As a *column* flex item — which is what it is inside `.portrait-cropper`
   — `flex-basis: 100%` means 100% of the **height**, and it opened a hand's
   width of empty panel between the hint and the buttons on desktop. Caught in a
   screenshot; no test would have seen it. */
.portrait-actions .portrait-hint { flex: 1 1 100%; }

/* --- framing -------------------------------------------------------------- */

.portrait-cropper {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
  max-width: 22rem;
}

/* Nothing in this column may stretch to fill the panel. A flex column hands
   spare height to any item that will take it, and every item here wants its
   own height and no more. */
.portrait-cropper > * { flex: 0 0 auto; }

/* The canvas is square; the mask over it is the circle camp will actually see.
   Painted as a ring of the panel colour rather than as a translucent overlay so
   that what is inside the circle is the *exact* colour it will be on the
   Directory — a dimmed preview of a face in blinding sun is a face nobody can
   judge. */
.portrait-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--card);
}

.portrait-canvas {
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  cursor: grab;
}

.portrait-canvas:active { cursor: grabbing; }

.portrait-mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--panel);
  opacity: 0.78;
  /* Paint everything *except* the circle. `transparent` out to the radius means
     the picture inside the circle is drawn at its true colour, which is the
     whole point — judging a face through a 22% veil in blinding sun is not
     judging it.

     `closest-side` is load-bearing and was missing until 2026-08-05. A bare
     `radial-gradient(circle …)` sizes itself `farthest-corner`, so on this
     square stage the gradient ray is the half-*diagonal* — W·√2/2 — and a `50%`
     stop landed at 0.354·W. The clear circle was 70.7% of the canvas while the
     saved crop is the whole of it, so every portrait came out at 1/√2 the
     diameter somebody aimed for: **twice the area they framed**. Faces landed
     small and far away, and whatever was pushed just outside the circle came
     back in. `closest-side` pins the ray to W/2, so `100%` is the inscribed
     circle — exactly the disc the Directory draws, and exactly the ring
     `.portrait-stage::after` puts on top. Keep the two in step. */
  -webkit-mask-image: radial-gradient(circle closest-side at 50% 50%,
    transparent 0 calc(100% - 1px), var(--ink) 100%);
  mask-image: radial-gradient(circle closest-side at 50% 50%,
    transparent 0 calc(100% - 1px), var(--ink) 100%);
  transition: opacity 120ms ease;
}

/* The circle itself: a hard ring on top, so the eye has an edge to frame
   against rather than a soft vignette. */
.portrait-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: var(--radius-pill);
  border: 2px solid var(--line);
}

.portrait-zoom-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.portrait-zoom-row .field-label { margin: 0; flex: none; }

/* A slider a dusty thumb can actually catch: the track is thin, the thumb is
   not, and the whole row clears the tap floor. */
.portrait-zoom {
  flex: 1 1 auto;
  min-height: var(--tap-min);
  accent-color: var(--ink);
  cursor: pointer;
}

/* `prefers-reduced-motion`: the one transition on the panel goes. There is no
   other animation here — a crop that eased into place would be a crop nobody
   could aim. */
.portrait-cropper.is-still .portrait-mask { transition: none; }

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

@media (max-width: 480px) {
  .portrait-resting { gap: var(--space-3); }
  .portrait-cropper { max-width: none; }
  .portrait-actions .btn { flex: 1 1 10rem; }
}
