/* ============================================================
   ONE OE Toolshed — page shell (server and prototype)
   Sits on top of the Object Edge design system (Product layer).
   Tokens only: no colour or size literals. Sizes read a system token
   (`--row`, `--prose-unit`, …) or a `--ts-*` property below; only the
   media query conditions keep their pixel values, since CSS cannot
   read a custom property there. Prototype-only rules live in
   assets/toolshed/prototype.css, which production does not serve.
   ============================================================ */

/* ——— Toolshed tokens (story 12.1; values from DESIGN.md `spacing.ts-*`) ———
   Two page widths only. The page, the top bar and the footer read `--ts-page` and `--ts-pad-x`,
   so the wordmark, crumbs, h1 and footer text share one edge on either width.
   Breakpoints (three): ≤ 900 two-column galleries; ≤ 640 the phone gutter, the avatar-only
   account menu, one-column galleries and cards, wrapping manage tabs; ≤ 480 the phone h1 and
   the pictures-only week grid. */
.oe-product {
  --ts-page-max: 1120px;        /* standard: galleries, tables, home, manage pages */
  --ts-page-narrow: 880px;      /* narrow: reading pages, sign-in, errors */
  --ts-gutter-x: 32px;          /* page, bar and footer side padding */
  --ts-gutter-x-phone: 16px;    /* at 640px and below */
  --ts-dot: 6px;                /* the eyebrow dot's size (.oec-eyebrow__dot), reused by the crumb dot */
  --ts-page-top: 48px;
  --ts-page-bottom: 72px;
  --ts-page-top-phone: 32px;
  --ts-page-bottom-phone: 56px;
  --ts-crumbs-gap: var(--row);
  --ts-head-gap: 48px;
  --ts-head-gap-phone: 36px;
  --ts-section-gap: 56px;
  --ts-headtext-max: 640px;
  --ts-card-min: 300px;
  --ts-grid-gap: 24px;
  --ts-measure: 720px;          /* one measure: the add form's typed text, moment text, list rows */
  /* Story 12.5 (fix 21): the literals DESIGN.md "Layout & Spacing" lists, as tokens. */
  --ts-hairline: 1px;           /* every rule and control border drawn locally */
  --ts-photo-band: var(--ink);  /* the year card's reaction band (story 20.3): solid ink where color-mix is unsupported … */
  --ts-label-offset: calc(var(--fs-app-label-md) * 1.4 + 7px); /* a field's label and its gap to the input (measured in Chrome): a button beside a field drops by this to meet its input (story 34.1) */
  --ts-w-bold: 700;             /* the moment page head (story 43.1); the system's own tokens stop at --w-semi 600 */
  --ts-tab-rule: 2px;           /* the rule over each manage tab (story 19.1), the design system tab underline's weight */
  --ts-bar-pad-y: 14px;         /* top bar and footer */
  --ts-bar-pad-y-phone: 12px;
  --ts-lh-head: 1.08;           /* the page h1 */
  --ts-tr-year: 0.02em;         /* the mono year on a year card */
  --ts-tr-brand: 0.02em;        /* the wordmark's alt text, if the image does not load */
  --ts-cat-max: 50%;            /* a gallery item's category: a long one truncates here, the name gives way first */
  --ts-vh-size: 1px;            /* the visually-hidden box; never tied to the hairline */
  --ts-card-min-h: 200px;       /* link cards */
  --ts-thumb: 64px;             /* Approvals and Removals thumbnails */
  --ts-podium-thumb: 96px;      /* the voting podium's thumbnails */
  --ts-photo-filter: saturate(0.75) sepia(0.12);   /* the one warm look on every photo (AD-2) */
  --ts-photo-max-h: 72vh;       /* the moment page photo */
  --ts-focus-width: 2px;        /* the ink outline of the focus ring */
  --ts-focus-halo: 4px;         /* the amber halo around it */
  --ts-focus-halo-wide: 6px;    /* gallery items, whose outline sits further out */
  --ts-notice-rule: 2px;        /* the notice's left rule */
  --ts-sort-bar: 2px;           /* the amber bar under the column a table is ranked by (Lets Move, story 317) */
  --ts-login-max: 460px;        /* the sign-in panel */
  --ts-textarea-min-h: 160px;   /* the add form's description */
  --ts-table-min: 640px;        /* tables scroll inside their wrapper below this */
  --ts-table-min-thumb: 740px;  /* … and below this with the picture column */
  /* The light tile under Google's mark on the ink sign-in button (story 12.3 review). */
  --ts-google-tile: var(--paper);
  --ts-google-tile-radius: 0;   /* square, as every system surface */
  --ts-google-tile-pad: calc(var(--prose-unit) / 2);
  /* The Save The Date page's banner only (story 231 / 99.2): the owner's exception to the square corners. */
  --ts-std-banner-radius: 15px;
  /* The current page's width and gutter; the layout sets data-width="narrow" on narrow pages. */
  --ts-page: var(--ts-page-max);
  --ts-pad-x: var(--ts-gutter-x);
}
.oe-product[data-width="narrow"] { --ts-page: var(--ts-page-narrow); }

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--fg-1);
  font-family: var(--font-sans);
  font-size: var(--fs-app-text-md);
  line-height: 1.5;
}

a { color: inherit; }

/* ——— The focus ring (DESIGN.md components.focus-ring): ink outline, amber halo. One rule for the
   local controls; system components draw their own. A control may move the ring out by setting
   --ts-focus-offset and --ts-focus-spread (gallery items do). ——— */
.shell__footlink:focus-visible,
.shell__acctbtn:focus-visible,
.addphoto__drop:focus-visible,
.pdrop:focus-visible,
.editframe:focus-visible,
.pcard__link:focus-visible,
.stdpage__piclink:focus-visible,
.pviewer__piclink:focus-visible,
.ppage__piclink:focus-visible,
.mview__piclink:focus-visible,
.rrow__link:focus-visible,
.lmtip__list:focus-visible,
.lmdist__sort:focus-visible,
.mitem:focus-visible {
  outline: var(--ts-focus-width) solid var(--ink);
  outline-offset: var(--ts-focus-offset, var(--ts-focus-width));
  box-shadow: 0 0 0 var(--ts-focus-spread, var(--ts-focus-halo)) var(--amber);
}

/* ——— Chrome bands ——— */
.shell__bar,
.shell__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--prose-unit) * 2);
  /* Hairlines run the full window width; the contents sit in the page's container and gutter. */
  padding: var(--ts-bar-pad-y) max(var(--ts-pad-x), calc((100% - var(--ts-page)) / 2 + var(--ts-pad-x)));
}
.shell__bar { border-bottom: var(--ts-hairline) solid var(--rule); }
/* While an engagement team member views as an employee (story 21.1): a tinted band under the top
   bar, its text and "Back to admin view" in the page's container, as the bar's contents. */
.shell__viewas {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--prose-unit) calc(var(--prose-unit) * 2);
  padding: var(--prose-unit) max(var(--ts-pad-x), calc((100% - var(--ts-page)) / 2 + var(--ts-pad-x)));
  background: var(--paper-2);
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.shell__viewastext { margin: 0; font-size: var(--fs-app-text-sm); color: var(--fg-1); }
.shell__viewas form { margin: 0; }
/* The label is short and fixed; the design system's 26ch button cap would cut it with the button's tracking. */
.shell__viewas .oec-btn { max-width: none; }
.shell__foot { border-top: var(--ts-hairline) solid var(--rule); margin-top: auto; }
/* Story 163 / 76.5: a stored footer message may be long; it wraps, and the link drops below it when needed. */
.shell__foot { flex-wrap: wrap; row-gap: var(--row); }
.shell__foot > .oe-mono-label:first-child { min-width: 0; overflow-wrap: anywhere; }
/* The company link in the footer (story 10.1): mono label look, external mark after the text. */
.shell__footlink { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) * 0.5); text-decoration: none; text-transform: none; }
.shell__footlink .oec-icon { width: var(--icon-xs); height: var(--icon-xs); }
.shell__footlink:hover { color: var(--ink); text-decoration: underline; text-underline-offset: calc(var(--prose-unit) * 0.375); }
.shell__right { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) * 2.5); }

/* ——— Account menu (story 6.1): the design system's Menu with an Avatar trigger ——— */
.shell__acctbtn { padding: 0 0 0 calc(var(--prose-unit) * 0.5); gap: calc(var(--prose-unit) * 1.25); color: var(--fg-1); }
/* The Avatar is the containing block for the picture: keep `position: relative`, or the picture escapes the round Avatar's clip (story 7.4). */
.shell__avatar { position: relative; }
.shell__acctimg { position: absolute; inset: 0; }
.shell__acctopt {
  width: 100%;
  margin: 0;
  border: 0;
  background: none;
  text-align: left;
  text-decoration: none;
}
.shell__acctopt:focus { outline: none; }
.shell__acctopt:focus-visible { outline: var(--ts-focus-width) solid var(--ink); outline-offset: calc(-1 * var(--ts-focus-width)); }
.shell__acctopt[aria-current="page"] { font-weight: var(--w-semi); box-shadow: inset calc(var(--prose-unit) * 0.25) 0 0 var(--amber); }
.shell__acct form { margin: 0; }
/* The account menu shows whole (owner, 2026-11-02): the design system caps a Menu list at 288px and
   scrolls it, which its groups and dividers outgrew. */
.shell__acctpanel .oec-menu__list { max-height: none; overflow-y: visible; }
/* A divider between two groups of the account menu (story 173 / 78.1): a hairline, not an item. */
.shell__acctsep {
  height: 0;
  margin: calc(var(--prose-unit) * 0.625) 0;
  border-top: var(--ts-hairline) solid var(--rule);
}
/* Manage ONE Employee's "Not in the last sync" (story 275 / 121.2): muted. */
.fields__none { color: var(--fg-3); }
/* The running build's version (story 129 / 59.2): the last line of the menu for every signed-in
   person (story 173 / 78.1), not an item. Mono, muted, after a divider; the case is kept so the
   commit hash reads as git prints it. */
.shell__acctversion {
  padding: calc(var(--prose-unit) * 0.5) calc(var(--prose-unit) * 1.625);
  color: var(--fg-3);
  text-transform: none;
  white-space: nowrap;
  cursor: default;
  user-select: text;
}
/* A long display name never widens the bar; the full name stays in the button's aria-label. */
.shell__acctname { display: block; max-width: calc(var(--row) * 8); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 640px) {
  .shell__acctname, .shell__acctcaret { display: none; }
  /* The avatar is the only way to Sign out here: a 44 px touch target, avatar centred. */
  .shell__acctbtn { padding: 0; min-width: var(--target-min); min-height: var(--target-min); justify-content: center; border-radius: 50%; }
}

.brand {
  display: inline-flex;
  align-items: center;
  /* The alt text keeps the wordmark's look when the image does not load. */
  font-weight: var(--w-semi);
  font-size: var(--fs-app-text-md);
  letter-spacing: var(--ts-tr-brand);
  color: inherit;
  text-decoration: none;
}
/* The ONE OE wordmark (assets/toolshed/oneoe-wordmark.png, 243×80, shown at control height). */
.brand__logo { display: block; height: var(--ctl-h-compact); width: auto; }

/* ——— Page ——— */
.page {
  width: 100%;
  max-width: var(--ts-page);
  margin: 0 auto;
  padding: var(--ts-page-top) var(--ts-pad-x) var(--ts-page-bottom);
  animation: page-in var(--dur-slow) var(--ease-standard);
}

.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--prose-unit);
  margin: 0 0 var(--ts-crumbs-gap);
  padding: 0;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--fs-app-label-lg);
  letter-spacing: var(--tr-mono-wide);
  text-transform: uppercase;
  color: var(--fg-2);
  overflow-wrap: anywhere;
}
.crumbs li { min-width: 0; }
/* The dot and the first label stay together, centred like an eyebrow's dot. */
.crumbs li:first-child { display: flex; align-items: center; }
/* The amber dot that opens the trail (story 15.4): the page eyebrow's dot, moved here. */
.crumbs__dot { display: inline-block; width: var(--ts-dot); height: var(--ts-dot); margin-right: var(--prose-unit); border-radius: 50%; background: var(--amber); flex: none; }
.crumbs li + li::before { content: "/"; margin-right: var(--prose-unit); color: var(--fg-3); }
.crumbs a { text-decoration: none; }
.crumbs a:hover { color: var(--fg-1); }
.crumbs [aria-current="page"] { color: var(--fg-1); }

.page__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--row);
  flex-wrap: wrap;
  margin-bottom: var(--ts-head-gap);
}
.page__headtext { max-width: var(--ts-headtext-max); min-width: 0; }
/* The home's head text spans the page (story 40.1); every other page keeps the cap. */
[data-page="home"] .page__headtext { max-width: none; flex: 1 1 auto; }
/* Balanced lines would stop the home headline short of the page width (story 40.1). */
[data-page="home"] .page__head h1 { text-wrap: pretty; }
.page__head h1 {
  /* No eyebrow above it since story 15.4: the crumbs gap alone sets the space above. */
  margin: 0 0 calc(var(--prose-unit) * 1.5);
  font-size: var(--fs-app-display-2xl);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-app-display-2xl);
  line-height: var(--ts-lh-head);
  text-wrap: balance;
  /* A long unbroken name (up to 80 characters) wraps instead of pushing the page sideways (story 12.2). */
  overflow-wrap: anywhere;
}
/* A stored tagline (story 47.2): a mono label between the h1 and the lede. */
.page__tagline { margin: 0 0 calc(var(--prose-unit) * 1.5); }
/* Story 230 / 99.1: a Save The Date's lines, one per place (story 246 / 106.2: per linked holiday), close together. */
.page__tagline:has(+ .page__tagline) { margin-bottom: calc(var(--prose-unit) * 0.5); }
.page__lede {
  margin: 0;
  font-size: var(--fs-app-display-sm);
  letter-spacing: var(--tr-app-display-sm);
  line-height: 1.4;
  color: var(--fg-2);
  text-wrap: pretty;
}

/* ——— Sections ——— */
.section { margin-top: var(--ts-section-gap); }
.page__head + .section { margin-top: 0; }
[tabindex="-1"]:focus { outline: none; }
.section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--prose-unit) * 2);
  padding-bottom: calc(var(--prose-unit) * 1.75);
  margin-bottom: var(--row);
  border-bottom: var(--ts-hairline) solid var(--rule);
}

/* The initiative page's detail sections (story 47.2): each stored field a block, its mono label over
   its text at the reading measure, paragraphs a paragraph gap apart. */
.detail + .detail { margin-top: var(--prose-gap-block); }
.detail__label { margin: 0 0 var(--prose-unit); }
.detail__text { max-width: var(--ts-measure); text-wrap: pretty; overflow-wrap: anywhere; }
.detail__text p { margin: 0; }
.detail__text p + p { margin-top: var(--prose-gap-p); }
.detail__text .oec-icon { vertical-align: -0.15em; margin-left: calc(var(--prose-unit) / 4); }

/* The default initiative page in two halves (story 114 / 50.4): the head and every detail section a
   grid of two equal columns, the left half first in source order. A half with nothing stays empty, so
   the halves line up from section to section; under the phone breakpoint they stack, left first. */
.halves { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ts-grid-gap); align-items: start; }
.halves__half { min-width: 0; }
/* The head: the h1, tagline and lede on the left, the actions on the right half, at its bottom right
   (story 123 / 56.1), in source order (the primary first). Every two-halves head: the default page and
   both landings. On a phone they sit under the text, on the left. */
/* The head is two thirds text, one third buttons (owner, 2026-10-28); the sections below stay 50/50. On a phone the
   `.halves` rule in the phone block (later in this file) stacks it in one column. */
.page__head--halves { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }
.page__head--halves .page__headtext { max-width: none; }
.page__head--halves .actions { align-self: end; justify-content: flex-end; }
/* A detail block's action below its text (the Share your feedback section's "Share feedback"). */
.detail__action { margin-top: var(--prose-gap-p); }
/* Its whole label, not cut at the design system's 26ch button cap (story 124 / 56.2); never past the half. */
.detail__action .oec-btn { max-width: 100%; }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--ts-card-min), 100%), 1fr));
  gap: var(--ts-grid-gap);
}

/* ——— Link cards (initiatives, tools, years) ——— */
.linkcard { min-height: var(--ts-card-min-h); }
.linkcard .oec-card__title {
  font-size: var(--fs-app-display-md);
  letter-spacing: var(--tr-app-display-md);
  font-weight: var(--w-medium);
}
.linkcard .oec-card__body { flex: 1; }
/* An initiative name's last word, on home cards and initiative page heads (story 13.3). Colour
   only: the AA-safe amber for amber that carries words; same style and weight as the title. */
.name-accent { color: var(--amber-text); }
/* Story 241 / 104.1 (owner, 2026-11-02): the link line, its text then its arrow, at the card's right edge. */
.linkcard .oec-card__foot { justify-content: flex-end; text-align: right; }
.linkcard__meta { font-size: var(--fs-app-text-sm); color: var(--fg-1); min-width: 0; overflow-wrap: anywhere; text-align: right; }
/* Lucide arrow-right, or arrow-up-right when external, in .oec-icon (fix 19, story 12.3). */
.linkcard__arr {
  display: inline-flex;
  color: var(--fg-2);
  transition: transform var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.linkcard:hover .linkcard__arr { color: var(--amber-text); transform: translateX(calc(var(--prose-unit) * 0.5)); }

.yearcard .oec-card__title {
  font-family: var(--font-mono);
  font-size: var(--fs-app-display-lg);
  letter-spacing: var(--ts-tr-year);
  font-feature-settings: "tnum" 1;
}

/* ——— Picture IDs ——— */
.fieldcount { display: flex; justify-content: space-between; gap: calc(var(--prose-unit) * 2); }
.desc-cell { display: block; max-width: 36ch; font-size: var(--fs-app-text-xs); color: var(--fg-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mono-id {
  font-family: var(--font-mono);
  font-size: var(--fs-app-label-lg);
  letter-spacing: var(--tr-app-num);
  font-feature-settings: "tnum" 1;
  color: var(--fg-2);
}

/* ——— Forms ——— */
.stack { display: flex; flex-direction: column; gap: var(--row); }
/* ——— Notice (DESIGN.md Components; fix 24, story 12.5): the one notice. Paper-2 ground and an
   amber rule by default; `data-tone` info, warn or danger takes the status tokens; `data-size`
   compact is the sign-in panel's tighter padding. It carries no outer margin: its place does. ——— */
.notice {
  margin: 0;
  padding: calc(var(--prose-unit) * 1.75) calc(var(--prose-unit) * 2);
  border-left: var(--ts-notice-rule) solid var(--amber);
  background: var(--paper-2);
  font-size: var(--fs-app-text-sm);
  color: var(--fg-1);
}
.notice[data-size="compact"] { padding: calc(var(--prose-unit) * 1.5) calc(var(--prose-unit) * 1.75); }
.notice[data-tone="info"] { background: var(--status-info-soft); color: var(--status-info-text); border-color: var(--status-info-text); }
.notice[data-tone="warn"] { background: var(--status-warn-soft); color: var(--status-warn-text); border-color: var(--status-warn-text); }
.notice[data-tone="danger"] { background: var(--status-danger-soft); color: var(--status-danger-text); border-color: var(--status-danger-text); }
.notice strong { font-weight: var(--w-semi); }
/* A notice that opens a manage panel or one of its parts (Add in bulk, Add one moment, story 22.2) sits one row above what follows. */
.manage__panel > .notice,
.admin__addone > .notice { margin-bottom: var(--row); }
.drop {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--prose-unit) * 1.25);
  padding: calc(var(--prose-unit) * 2.5);
  border: var(--ts-hairline) dashed var(--control-border);
  background: var(--paper);
}
.drop[data-drag="true"] { border-color: var(--ink); background: var(--paper-2); }
.drop__name { font-size: var(--fs-app-text-sm); color: var(--fg-2); word-break: break-all; }
.actions { display: flex; gap: calc(var(--prose-unit) * 1.5); flex-wrap: wrap; align-items: center; }

.facts { display: grid; grid-template-columns: max-content 1fr; gap: calc(var(--prose-unit) * 1.25) var(--row); margin: 0; }
.facts dt { font-family: var(--font-mono); font-size: var(--fs-app-label-lg); letter-spacing: var(--tr-mono-wide); text-transform: uppercase; color: var(--fg-2); padding-top: calc(var(--prose-unit) * 0.25); }
.facts dd { margin: 0; }

.rowline { display: flex; align-items: center; justify-content: space-between; gap: var(--row); flex-wrap: wrap; padding: calc(var(--prose-unit) * 2.5) 0; border-bottom: var(--ts-hairline) solid var(--rule); }
.rowline__text { max-width: var(--ts-measure); }
/* A row holding a field and its button (Add member, Look up): the button lines up with the input,
   not with the label and input together, so the row aligns to the top and the button drops by the
   label's height (story 34.1). A hint or error under the input does not move it. */
.rowline:has(> .oec-field) { align-items: flex-start; }
.rowline > .oec-field .oec-field__label { line-height: 1.4; }
.rowline:has(> .oec-field) > .oec-btn { margin-top: var(--ts-label-offset); }
.rowline__title { margin: 0 0 calc(var(--prose-unit) * 0.5); font-size: var(--fs-app-text-lg); font-weight: var(--w-semi); }
.rowline__desc { margin: 0; font-size: var(--fs-app-text-sm); color: var(--fg-2); }

[hidden] { display: none !important; }

@keyframes page-in {
  from { opacity: 0; transform: translateY(var(--prose-unit)); }
  to   { opacity: 1; transform: none; }
}

/* Phone: the gutter, the account menu (above) and the one-column grids change together. */
@media (max-width: 640px) {
  .oe-product { --ts-pad-x: var(--ts-gutter-x-phone); }
  .shell__bar, .shell__foot { padding-top: var(--ts-bar-pad-y-phone); padding-bottom: var(--ts-bar-pad-y-phone); }
  .page { padding-top: var(--ts-page-top-phone); padding-bottom: var(--ts-page-bottom-phone); }
  .page__head { margin-bottom: var(--ts-head-gap-phone); }
  .grid { grid-template-columns: 1fr; }
  .halves { grid-template-columns: 1fr; }
  .halves__half:empty { display: none; }
  .page__head--halves .actions { justify-content: flex-start; }
}
@media (max-width: 480px) {
  .page__head h1 { font-size: var(--fs-app-display-xl); letter-spacing: var(--tr-app-display-xl); }
}

/* Wide content scrolls inside its own box, never the page. */
.tablewrap { overflow-x: auto; }
.tablewrap .oec-table { min-width: var(--ts-table-min); }
/* The Approvals and Removals tables gain a 64 px picture column (story 11.1). */
.tablewrap .oec-table:has(.thumbcell) { min-width: var(--ts-table-min-thumb); }
.oec-tabs { overflow-x: auto; scrollbar-width: none; }
.oec-tab { flex: none; }
.tablewrap td[data-mono="true"] { white-space: nowrap; }
.actions > .oec-btn { flex: none; }
/* Whole button labels everywhere (story 134 / 64.1, owner 2026-10-28): the design system caps a button
   at --ui-button-max (26ch) and cuts its label with an ellipsis; here a button is as wide as its label
   allows, never wider than its container, and a label longer than its container wraps instead of being
   cut ("Open {name}" with a long stored name, a 40-character call to action). */
.oe-product .oec-btn { max-width: 100%; }
.oe-product .oec-btn__lab { white-space: normal; overflow: visible; text-overflow: clip; text-align: center; overflow-wrap: anywhere; }
/* Head actions keep their whole label ("Open Moments That Matter", fix 9, story 12.3), never past the page edge. */
#head-actions .oec-btn { max-width: 100%; }

/* ============================================================
   MTM year page and moment items
   Layout from design/claude-design/Moments That Matter 2026.dc.html,
   scaled from its 1620px canvas to the 1120px page. The design's
   colour literals are mapped to tokens.
   ============================================================ */
/* The year tabs sit between the page head and the grid (story 12.2), the category Select on the
   right (story 17.3); on a narrow screen the Select wraps below the tabs. */
.mtm-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: calc(var(--prose-unit) * 2);
  margin-bottom: calc(var(--prose-unit) * 4);
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.mtm-strip .oec-tabs { border-bottom: 0; min-width: 0; }
/* Year tabs are page links: the current year carries aria-current, styled as the design system's selected tab. */
.oec-tab[aria-current="page"] { color: var(--fg-1); border-bottom-color: var(--amber); }
.oec-tab[aria-current="page"] .oec-tab__count { color: var(--amber-text); }
.mtm-strip + .section { margin-top: 0; }
.yearfilter { display: flex; align-items: center; gap: var(--prose-unit); margin-left: auto; padding-bottom: var(--prose-unit); }
.yearfilter .oec-field__label { white-space: nowrap; }
.yearfilter .oec-select { width: auto; max-width: 100%; }
/* Owner request 2026-11-06: My badges' badge filter sits at the right, at every width. */
[data-page="my-badges"] .yearfilter { justify-content: flex-end; margin-left: 0; }
/* On a phone the Select takes its own line, left-aligned under the tabs. */
@media (max-width: 640px) {
  .yearfilter { margin-left: 0; width: 100%; }
  .yearfilter .oec-select { flex: 1 1 auto; }
}

.mgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: calc(var(--prose-unit) * 5) var(--row); }

.mitem {
  display: flex;
  flex-direction: column;
  gap: calc(var(--prose-unit) * 1.5);
  min-width: 0;
  color: inherit;
  text-decoration: none;
  /* Its focus ring sits further out than a control's. */
  --ts-focus-offset: calc(2 * var(--ts-focus-width));
  --ts-focus-spread: var(--ts-focus-halo-wide);
}
.mitem__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--prose-unit) * 2);
  padding-bottom: calc(var(--prose-unit) * 1.25);
  border-bottom: var(--ts-hairline) solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--fs-app-label-lg);
  letter-spacing: var(--tr-app-label-lg);
  text-transform: uppercase;
}
.mitem__name { color: var(--amber-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The name gives way first; a long category truncates at --ts-cat-max (story 12.5). */
.mitem__cat { flex-shrink: 0; max-width: var(--ts-cat-max); color: var(--fg-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* My moments (story 178 / 79.1): a pending card's warn badge, on its own line under the head. */
.mitem__status { margin: 0; }
.mitem__photo { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--paper-2); }
.mitem__photo > :not(.framed, .mitem__reactions) {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--ts-photo-filter);
}
.mitem__photo > :not(.mitem__reactions) { transition: scale var(--dur-base) var(--ease-standard); }
.mitem:hover .mitem__photo > :not(.mitem__reactions) { scale: 1.02; }
.mitem__desc {
  margin: 0;
  font-size: var(--fs-app-text-md);
  line-height: 1.45;
  letter-spacing: var(--tr-app-text-md);
  color: var(--fg-2);
  text-wrap: pretty;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* … and 64% ink where it is: paper-on-dark counts stay ≥ 4.5:1 over a white photo (story 20.3). */
/* On `.oe-product`, where the token lives: on `:root` it would be shadowed (fixed in story 25.1). */
@supports (color: color-mix(in srgb, red 50%, transparent)) {
  .oe-product { --ts-photo-band: color-mix(in srgb, var(--ink) 64%, transparent); }
}
/* A year card's reactions above zero (story 17.3): emoji and count, read as "Fire, 3". They sit on a
   flat translucent ink band across the bottom of the picture, light counts on it (story 20.3; no
   gradient, per the design system); a card with no reactions shows no band. */
.mitem__reactions {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; flex-wrap: wrap; gap: calc(var(--prose-unit) * 1.5);
  margin: 0; padding: var(--prose-unit) calc(var(--prose-unit) * 1.5);
  background: var(--ts-photo-band);
}
.mitem__reaction { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) * 0.5); font-size: var(--fs-app-text-md); line-height: 1; }
.mitem__comments { margin-left: auto; } /* the comment count, on the band's right (story 131 / 61.1) */
.mitem__reactcount { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--fs-app-label-lg); color: var(--paper-on-dark); }

@media (max-width: 900px) {
  .mgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .mgrid { grid-template-columns: 1fr; }
}

/* ——— Shared in the last week (story 11.2): 3 per line on every width, at most 3 lines.
   Below 480 px the items show the picture only, as a centred square crop of the 4:3 framed view. ——— */
.mgrid.mgrid--week { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* The frame wraps the framed photo in the week grid only. `.mgrid--week .mitem__photo > .mitem__frame` (0,3,0)
   outranks the generic `.mitem__photo > :not(.framed)` (0,2,0), which would otherwise filter the frame and
   the photo twice; it does not rely on source order. */
.mgrid--week .mitem__photo > .mitem__frame { position: absolute; inset: 0; display: block; width: 100%; height: 100%; filter: none; }
/* Owner request 2026-11-06: the "Show more" of every "Shared in the last week" (Moments, ONE Vibes, Object Badges) sits at the right. */
.shared__more { display: flex; justify-content: flex-end; margin-top: calc(var(--prose-unit) * 4); }
/* The list's ghost Button ("Show more", story 16.2) shows its whole label, never cut at the 26ch Button ceiling. */
.shared__more .oec-btn { max-width: none; }
/* The week cards show name, category and picture only (story 16.2). Below 900 px three columns are
   narrow: stack name over category so each gets the whole column. */
@media (max-width: 900px) {
  .mgrid.mgrid--week { gap: calc(var(--prose-unit) * 4) calc(var(--prose-unit) * 2); }
  .mgrid--week .mitem__head { flex-direction: column; align-items: flex-start; gap: calc(var(--prose-unit) * 0.5); }
  .mgrid--week .mitem__name,
  .mgrid--week .mitem__cat { max-width: 100%; }
}
@media (max-width: 480px) {
  .mgrid.mgrid--week { gap: var(--prose-unit); }
  .mgrid--week .mitem { gap: 0; }
  .mgrid--week .mitem__head { display: none; }
  .mgrid--week .mitem__photo { aspect-ratio: 1 / 1; }
  .mgrid--week .mitem__photo > .mitem__frame { left: 50%; right: auto; width: calc(100% * 4 / 3); translate: -50% 0; }
  .shared__more { margin-top: var(--row); }
}

/* ——— Add page (design/claude-design/Add Moment.dc.html) ——— */
.addgrid {
  /* One column: Shared by, Category, photo, Description (owner change from the design's two columns).
     It fills the standard page (story 17.1), and since story 26.1 so does every row: fields, hints,
     counter, policy, confirmation and actions span the page with the photo frame. */
  padding-top: var(--prose-unit);
}
/* "Add your moment" (story 20.2) and the head buttons are short, fixed labels; the design system's
   26ch button cap cut the submit to "ADD YOUR MO" with the button's tracking (story 26.1). */
.addform__actions .oec-btn,
.page__head .actions .oec-btn { max-width: none; }
.addform { display: flex; flex-direction: column; gap: var(--row); min-width: 0; }
.addfield { display: flex; flex-direction: column; gap: var(--prose-unit); }
.addform__who {
  font-size: var(--fs-app-display-md);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-app-display-md);
  line-height: 1.2;
}
.addform__grow .oec-textarea { min-height: var(--ts-textarea-min-h); }
.addform__policy { margin: 0; font-size: var(--fs-app-text-sm); color: var(--fg-2); }
.addform__policy strong { color: var(--fg-1); font-weight: var(--w-semi); }
.addform__saved {
  padding: calc(var(--prose-unit) * 1.75) 0 0;
  border-top: var(--ts-hairline) solid var(--rule);
  font-size: var(--fs-app-text-lg);
  color: var(--fg-1);
}
.addform__saved a { color: var(--amber-text); }
.addform__actions {
  display: flex;
  justify-content: flex-end;
  gap: calc(var(--prose-unit) * 1.5);
  padding-top: calc(var(--prose-unit) * 2.5);
  border-top: var(--ts-hairline) solid var(--rule);
}

.addphoto { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 1.25); min-width: 0; }
.addphoto__drop {
  position: relative;
  aspect-ratio: 4 / 3;   /* same frame as the year page, so the framing carries over */
  padding: 0;
  align-items: stretch;
  justify-content: center;
  cursor: pointer;
  background: var(--paper-2);
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
}
.addphoto__drop:hover { border-color: var(--fg-1); }
/* With a photo the box is the framing editor (frame-editor.js, story 36.3), as the Modal's frame. */
.addphoto__drop[data-filled="true"] { border-style: solid; border-color: var(--rule); cursor: grab; touch-action: none; }
.addphoto__drop[data-panning] { cursor: grabbing; }
.addphoto__placeholder { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: calc(var(--prose-unit) * 2); flex: 1; padding: calc(var(--prose-unit) * 4); text-align: center; }
.addphoto__cta { font-size: var(--fs-app-display-sm); color: var(--fg-2); }
.addphoto__skel { position: absolute; inset: 0; --skh: 100%; }
.addphoto__skel .oec-skel__bar { height: 100%; }

/* Framed photo: positioned by framedStyle() in app.js, never cropped in the file. */
img.framed {
  position: absolute;
  max-width: none;
  transform: translate(-50%, -50%);
  object-fit: fill;
  filter: var(--ts-photo-filter);
  user-select: none;
  -webkit-user-drag: none;
}
.addframe__clip { position: absolute; inset: 0; overflow: hidden; }
/* The moment page's "Edit moment" Modal frames the photo as the add page does (story 14.7). */
.editframe { position: relative; aspect-ratio: 4 / 3; background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); cursor: grab; touch-action: none; overflow: hidden; }
.editframe:active,
.editframe[data-panning] { cursor: grabbing; }
.addframe__ctl {
  position: absolute;
  top: var(--prose-unit);
  right: var(--prose-unit);
  display: flex;
  gap: calc(var(--prose-unit) * 0.75);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.addframe__ctl .oec-btn { background: var(--paper); }
.addphoto__drop:hover .addframe__ctl,
.addphoto__drop:focus-within .addframe__ctl { opacity: 1; }
@media (hover: none) { .addframe__ctl { opacity: 1; } }
.addframe__tools { display: flex; align-items: center; flex-wrap: wrap; gap: calc(var(--prose-unit) * 1.5) calc(var(--prose-unit) * 2); padding: calc(var(--prose-unit) * 1.25) 0; border-bottom: var(--ts-hairline) solid var(--rule); }
.addframe__zoom { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) * 1.25); }
.addframe__zoom input { width: calc(var(--prose-unit) * 20); accent-color: var(--ink); }
.addframe__help { flex: 1; min-width: calc(var(--prose-unit) * 22.5); font-size: var(--fs-app-text-xs); color: var(--fg-2); }
.addphoto__foot { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--prose-unit) * 2); }

/* ——— Moment page (design/claude-design/Moments That Matter.dc.html) ——— */
.mview__fig { margin: 0; }
.mview__photo { display: flex; justify-content: center; background: var(--paper-2); }
/* Story 273 / 120.1: the photo is a link that opens it full size in a new window; it fills the grey box, as a picture page's. */
.mview__piclink { display: flex; justify-content: center; width: 100%; cursor: zoom-in; --ts-focus-offset: calc(-1 * var(--ts-focus-width)); }
.mview__photo img { display: block; max-width: 100%; max-height: var(--ts-photo-max-h); width: auto; height: auto; filter: var(--ts-photo-filter); }
.mview__desc {
  max-width: var(--ts-measure);
  margin: calc(var(--prose-unit) * 2.5) 0 0;
  font-size: var(--fs-app-text-2xl);
  line-height: 1.45;
  letter-spacing: var(--tr-app-text-2xl);
  color: var(--fg-2);
  text-wrap: pretty;
  overflow-wrap: anywhere;
}
/* The picture ID and the gallery link close the page, aligned right (stories 17.2 and 20.1). */
.mview__meta { margin: calc(var(--prose-unit) * 2.5) 0; font-size: var(--fs-app-text-sm); color: var(--fg-2); text-align: right; }
.mview__meta a { color: var(--amber-text); }
/* Reactions (story 14.8): compact ghost Buttons, the viewer's own pressed. The emoji are the
   design system's one emoji exception, here and in the year cards' counts only. Since story 214 /
   92.1 (replacing story 23.1's column beside the photo) a moment's page puts them under the photo,
   outside its gray box, as one centred row held to the reading measure, as a vibe's page does
   (.vibe__stage, story 191 / 82.3), at every width. */
.reactions { display: flex; flex-wrap: wrap; gap: var(--prose-unit); margin: calc(var(--prose-unit) * 2.5) 0 0; }
.mview__stage .reactions { justify-content: center; max-width: var(--ts-measure); margin: calc(var(--prose-unit) * 4) auto 0; }
.reaction { gap: calc(var(--prose-unit) * 0.75); }
/* 14px emoji (story 17.2). */
.reaction__emoji { font-size: var(--fs-app-text-md); line-height: 1; }
.reaction__count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--fs-app-label-lg); color: var(--fg-2); }
.reaction[aria-pressed="true"] { border-color: var(--amber); background: var(--paper-2); }
.reaction[aria-pressed="true"] .reaction__count { color: var(--amber-text); }

.mview__notice { margin: 0 0 calc(var(--prose-unit) * 2.5); }
/* The moment page's head is the year card's head (story 41.1): the name as the h1 in the card's
   amber mono capitals, the category in grey on the right, a hairline under them. A long name wraps. */
.mview__head { margin-bottom: var(--row); }
.mview__head h1 { margin: 0; font: inherit; }
/* Story 43.1 (owner): 16px bold for the name and the category; the category no longer truncates here. */
.mview__head .mitem__name { font-size: var(--fs-app-text-xl); font-weight: var(--ts-w-bold); white-space: normal; overflow-wrap: anywhere; }
.mview__head .mitem__cat { font-size: var(--fs-app-text-xl); font-weight: var(--ts-w-bold); max-width: none; }
/* "Request removal" (stories 6.3 and 14.6): a banner whose ghost Button opens the form in place.
   Without script (no `removal--js`) the banner's button and Cancel are hidden and the form shows.
   `.removal__banner` / `.removal__text` is the shared text-plus-ghost-button layout that the edit
   banner (story 17.2) and the waiting notice (story 46.1) reuse: the text takes the room and wraps,
   the button stays on the right and drops under the text only when there is no room for it. */
.removal { margin: calc(var(--prose-unit) * 2.5) 0 0; display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 2); }
.removal__banner { display: flex; align-items: center; justify-content: space-between; gap: var(--row); flex-wrap: wrap; }
.removal__text { margin: 0; flex: 1 1 24ch; min-width: 0; }
/* The engagement team's edit banner sits below the removal one, in its layout (story 17.2). */
.mview__edit { margin: calc(var(--prose-unit) * 2.5) 0 0; }
.removal:not(.removal--js) .removal__open,
.removal:not(.removal--js) .removal__cancel,
.removal--open .removal__open,
.removal--js:not(.removal--open) .removal__form { display: none; }
.removal__form { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 2); }
.removal .oec-textarea { width: 100%; box-sizing: border-box; }

/* Comments (story 118 / 52.1): after the photo, message and reactions, before "Request removal".
   Each comment its author and date over its text at the reading measure, then a ghost compact
   "Like" (the amber border when the viewer liked it) and, for its author and the engagement team, a
   ghost "Delete" in the danger text colour. No emoji, no icon. */
.comments__list { list-style: none; margin: 0; padding: 0; }
.comment { padding: calc(var(--prose-unit) * 2) 0; border-bottom: var(--ts-hairline) solid var(--rule); }
.comment:first-child { padding-top: 0; }
.comment__meta { display: flex; align-items: baseline; flex-wrap: wrap; gap: calc(var(--prose-unit) * 1.5); margin: 0 0 var(--prose-unit); }
.comment__author { font-weight: var(--ts-w-bold); overflow-wrap: anywhere; }
.comment__date { color: var(--fg-2); }
.comment__text { max-width: var(--ts-measure); text-wrap: pretty; overflow-wrap: anywhere; }
.comment__text p { margin: 0; }
.comment__text p + p { margin-top: var(--prose-gap-p); }
.comment__actions { display: flex; flex-wrap: wrap; gap: var(--prose-unit); margin-top: calc(var(--prose-unit) * 1.5); }
.comment__like[aria-pressed="true"] { border-color: var(--amber); background: var(--paper-2); }
.comment__delete { color: var(--danger-text); }
.comment[aria-busy="true"] { opacity: 0.6; }
.comments__more { margin: 0 0 var(--row); color: var(--fg-2); }
.comments__empty { margin: 0; color: var(--fg-2); }
.comments__form { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 2); margin-top: calc(var(--prose-unit) * 3); }
.comments__form .oec-textarea { width: 100%; box-sizing: border-box; }

/* ——— Manage page tabs (both manage pages, story 19.1) ———
   Equal-width tabs, each numbered "01", "02", … in amber mono, its label in sentence case and cut
   with an ellipsis when it does not fit. A strong rule runs over every tab, ink over the selected
   one, which also takes the card tint; a hairline closes the strip. The number is CSS generated
   content with empty alt text, so assistive tech reads only the label. Year tabs keep the design
   system's underline. */
.admin__tabs {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0;
  overflow: visible; margin-bottom: var(--row); counter-reset: ts-tab;
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.admin__tabs .oec-tab {
  counter-increment: ts-tab; min-width: 0; margin: 0;
  justify-content: flex-start; gap: calc(var(--prose-unit) * 1.5);
  padding: calc(var(--prose-unit) * 2.5) calc(var(--prose-unit) * 4);
  border-bottom: 0; border-top: var(--ts-tab-rule) solid var(--rule-strong);
  font-family: var(--font-sans); font-size: var(--fs-app-text-xl); letter-spacing: var(--tr-app-text-xl);
  font-weight: var(--w-medium); text-transform: none; color: var(--fg-2);
  transition: color var(--dur-fast, 140ms) var(--ease-standard, ease), border-color var(--dur-fast, 140ms) var(--ease-standard, ease), background var(--dur-fast, 140ms) var(--ease-standard, ease);
}
.admin__tabs .oec-tab::before {
  content: counter(ts-tab, decimal-leading-zero) / "";
  flex: none; font-family: var(--font-mono); font-size: var(--fs-app-label-lg);
  letter-spacing: var(--tr-app-num); font-feature-settings: "tnum" 1; color: var(--amber-text);
}
.admin__tabs .oec-tab__lab { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin__tabs .oec-tab__count { flex: none; }
.admin__tabs .oec-tab:hover { color: var(--fg-1); }
.admin__tabs .oec-tab[aria-selected="true"] { color: var(--fg-1); background: var(--paper-2); border-top-color: var(--ink); }
/* Phone: two tabs a row, each with its own top rule, so every row reads the same (fix 4). */
@media (max-width: 640px) {
  .admin__tabs { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .admin__tabs .oec-tab { padding: calc(var(--prose-unit) * 2) calc(var(--prose-unit) * 2); }
}
/* Both manage pages (story 10.4; fix 14, story 12.4; owner decision 2026-10-23). The inline
   script before the strip puts `js` and `data-tab` on <html>: from the first paint the strip shows
   with only the picked panel, and tabs.js keeps `data-tab` in step. Without script the strip is
   hidden (no dead buttons) and every panel stacks under its plain name (`.manage__panel-name`,
   stories 15.2 and 16.1), with the same gap as `.section`; the name shows only without
   script. A new manage page adds its panels to the list below. */
html:not(.js) .admin__tabs { display: none; }
html:not(.js) .manage__panel ~ .manage__panel { margin-top: var(--ts-section-gap); }
/* Removals: the remove-by-ID lookup sits a section below the requests (story 22.1); Add: the
   single add a section below the bulk import (story 22.2). */
.admin__removeby,
.admin__addone { margin-top: var(--ts-section-gap); }
/* A section head that is a heading (the panel names, Approvals' queue head) keeps the head's type. */
h2.section__head,
h3.section__head { margin-top: 0; font: inherit; }
.js .manage__panel-name { display: none; }
.js .manage__panel { display: none; }
.js[data-tab="approvals"] #panel-approvals,
.js[data-tab="removals"] #panel-removals,
.js[data-tab="add"] #panel-add,
.js[data-tab="categories"] #panel-categories,
.js[data-tab="team"] #panel-team,
.js[data-tab="initiatives"] #panel-initiatives,
.js[data-tab="home"] #panel-home,
.js[data-tab="email"] #panel-email,
.js[data-tab="calendars"] #panel-calendars,
.js[data-tab="holidays"] #panel-holidays,
.js[data-tab="contests"] #panel-contests,
.js[data-tab="save-the-dates"] #panel-save-the-dates,
.js[data-tab="details"] #panel-details,
.js[data-tab="pictures"] #panel-pictures,
.js[data-tab="votes"] #panel-votes,
.js[data-tab="employees"] #panel-employees,
.js[data-tab="fields"] #panel-fields,
.js[data-tab="endpoints"] #panel-endpoints,
.js[data-tab="custom-fields"] #panel-custom-fields,
.js[data-tab="badges"] #panel-badges,
.js[data-tab="archived"] #panel-archived,
.js[data-tab="sending"] #panel-sending { display: block; }
/* The error component (fix 16) sits on the page's left edge, with the crumbs and the h1 of
   every other page: no inline padding from the EmptyState's page size. */
.oec-empty.errstate { padding-left: 0; padding-right: 0; }
.admin__idfield { flex: 1; min-width: min(calc(var(--row) * 10), 100%); }
.admin__status { margin: calc(var(--prose-unit) * 2) 0 0; font-size: var(--fs-app-text-sm); color: var(--fg-1); }
.admin__moment {
  display: grid;
  grid-template-columns: minmax(0, calc(var(--prose-unit) * 27.5)) minmax(0, 1fr);
  gap: var(--row);
  align-items: start;
  padding: calc(var(--prose-unit) * 2.5) 0;
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.admin__thumb { display: flex; justify-content: center; background: var(--paper-2); }
.admin__thumb img { display: block; max-width: 100%; max-height: calc(var(--row) * 10); width: auto; height: auto; filter: var(--ts-photo-filter); }
.admin__nophoto { padding: calc(var(--prose-unit) * 5) calc(var(--prose-unit) * 1.5); font-family: var(--font-mono); font-size: var(--fs-app-label-md); letter-spacing: var(--tr-mono); text-transform: uppercase; color: var(--fg-2); }
.admin__facts { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 2.5); min-width: 0; }
.admin__desc { overflow-wrap: anywhere; }
@media (max-width: 640px) {
  .admin__moment { grid-template-columns: minmax(0, 1fr); }
  .admin__moment .facts { grid-template-columns: minmax(0, 1fr); gap: calc(var(--prose-unit) * 0.5); }
  .admin__moment .facts dd { margin-bottom: var(--prose-unit); }
}

/* ——— Engagement page, Removals tab (/moments/admin#removals, story 6.3) ——— */
.removals__reasons { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--prose-unit); }
.removals__reasons li { max-width: 40ch; overflow-wrap: anywhere; font-size: var(--fs-app-text-sm); }
.removals__who { font-weight: var(--w-semi); }
.removals__none { font-size: var(--fs-app-text-sm); color: var(--fg-2); }

/* ——— Engagement page, Approvals tab (/moments/admin#approvals, story 4.1) ——— */
.approvals__queue { margin-top: var(--prose-gap-h); }
.queue__badge { display: block; margin-top: var(--prose-unit); }
.queue__actions { justify-content: flex-end; flex-wrap: nowrap; }

/* ——— Approvals and Removals thumbnails (story 11.1) ———
   A 64 px square of the display rendition, cover-cropped, square corners (the system has no
   radius) and the rule hairline; the warm look as on every photo (AD-2). Without a picture the
   same square holds the picture ID in the mono label style. It stays 64 px at every width; the
   table scrolls inside .tablewrap, never the page. */
.thumbcell { width: calc(var(--ts-thumb) + 2 * var(--px, calc(var(--prose-unit) * 2))); }
.thumb {
  --thumb-size: var(--ts-thumb);
  display: flex;
  flex: none;
  width: var(--thumb-size);
  height: var(--thumb-size);
  box-sizing: border-box;
  border: var(--ts-hairline) solid var(--rule);
  background: var(--paper-2);
}
img.thumb { display: block; object-fit: cover; filter: var(--ts-photo-filter); }
.thumb--none { align-items: center; justify-content: center; padding: calc(var(--prose-unit) * 0.5); text-align: center; }
.thumb--none .oe-mono-label { line-height: 1.3; overflow-wrap: anywhere; word-break: break-all; }
/* The picture column takes the room the description preview gave up, so Approve and Reject stay in view at desktop width. */
.oec-table:has(.thumbcell) .desc-cell { max-width: 22ch; }
[data-page="moments-admin"] .oec-toast__body[data-mono="true"] { font-family: var(--font-mono); letter-spacing: var(--tr-mono); }
/* Toasts never run past a phone's edge (the stack pads by one row on each side). */
[data-page="moments-admin"] .oec-toaststack,
[data-page="manage"] .oec-toaststack { max-width: 100vw; box-sizing: border-box; }
@media (max-width: 640px) {
  [data-page="moments-admin"] .oec-toast,
  [data-page="manage"] .oec-toast { min-width: 0; max-width: calc(100vw - 2 * var(--row)); }
}

/* ——— Engagement page, Add tab, "Add in bulk" (/moments/admin#add, AD-18; story 22.2) ——— */
.import__pickers { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--row); }
.import__sample { color: var(--amber-text); }
.import__result { margin-top: calc(var(--prose-unit) * 4); }
.import__result:empty { display: none; }
.import__badges { display: flex; flex-direction: column; align-items: flex-start; gap: calc(var(--prose-unit) * 0.5); }
.import__result .oec-badge { white-space: nowrap; }
.import__result td { overflow-wrap: break-word; }
@media (max-width: 640px) {
  .import__pickers { grid-template-columns: minmax(0, 1fr); }
}

/* ——— Manage Lets Move (/lets-move/admin, story 292 / 124.1): the uploads table ——— */
.lmimports__file { overflow-wrap: anywhere; }
.lmimports .oec-badge { white-space: nowrap; }
/* Story 298 / 124.7: who published the published upload and when, under its badge, in small type. */
.lmimports__by { display: block; margin-top: calc(var(--prose-unit) * 0.5); font-size: var(--fs-app-text-sm); color: var(--fg-2); overflow-wrap: anywhere; }
.lmimports [data-publish] { white-space: nowrap; }
/* Story 299 / 124.8, story 302 / 124.11: "03 Members", the Members table: the GymRats name, the
   people picker, "Logs in miles" and the status; the table scrolls inside .tablewrap, never the page. */
.lmmembers__count { margin: 0; color: var(--fg-2); }
.lmmembers__table th[scope="row"] { font-weight: inherit; text-align: left; }
.lmmembers__name { min-width: 0; overflow-wrap: anywhere; }
.lmmembers__account { width: 40%; min-width: 0; }
.lmmembers__picker { margin: 0; }
.lmmembers__miles .oec-check, .lmmembers__miles .oec-check__label { white-space: nowrap; }
.lmmembers__status { overflow-wrap: anywhere; }
.lmmembers__state { display: block; }
.lmmembers__status [data-use] { margin-top: calc(var(--prose-unit) * 0.5); white-space: nowrap; }

/* ——— Engagement page, Categories tab (/moments/admin#categories, story 4.2) ———
   Rows and save bar from the design system's templates/settings-page, as named classes. */
.vh {
  position: absolute;
  width: var(--ts-vh-size);
  height: var(--ts-vh-size);
  margin: calc(-1 * var(--ts-vh-size));
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Room under the last row while the fixed save bar shows. */
.cats--bar { padding-bottom: calc(var(--row) * 4); }
.cats__head { margin-bottom: var(--prose-unit); }
.cats__desc { margin: 0; max-width: var(--prose-measure); font-size: var(--fs-app-text-xs); letter-spacing: var(--tr-app-text-xs); line-height: var(--prose-lh); color: var(--fg-2); text-wrap: pretty; }
.catlist { list-style: none; margin: 0; padding: 0; }
.catrow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--prose-gap-p) var(--prose-gap-rule);
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--row) 0;
  border-top: var(--ts-hairline) solid var(--rule);
}
/* The name field runs up to the move buttons, and the add field up to its button (story 29.1). */
.catrow__field { flex: 1 1 calc(var(--row) * 14); min-width: 0; }
.catrow__controls { display: flex; align-items: center; gap: var(--prose-unit); flex: none; }
.catrow__switch { min-width: calc(var(--row) * 4.5); margin-left: var(--prose-gap-p); }
.catadd { padding: var(--row) 0; border-top: var(--ts-hairline) solid var(--rule); }
.catadd__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--prose-gap-p); }
.catadd__row .oec-input { flex: 1 1 calc(var(--row) * 9); min-width: 0; }
/* Manage ONE Vibes' Categories tab (story 165 / 77.1): each category row, its cards indented under it, then "Add card". */
.vcats { display: flex; flex-direction: column; gap: var(--prose-unit); }
.vcatlist { list-style: none; margin: 0; padding: 0; }
.vcat { border-top: var(--ts-hairline) solid var(--rule-strong); padding-bottom: var(--row); }
.vcat__row { border-top: 0; }
.vcards { list-style: none; margin: 0; padding: 0 0 0 var(--prose-gap-rule); }
.vcat__addcard { padding: var(--prose-unit) 0 0 var(--prose-gap-rule); }
.vcats__add { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); }
.vcats__save { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); padding-top: var(--row); border-top: var(--ts-hairline) solid var(--rule); }
@media (max-width: 640px) {
  .vcards, .vcat__addcard { padding-left: var(--prose-unit); }
}
/* A vibe's page (story 167 / 77.3): the card head, the category an amber mono label, the question the h1, "Private" muted. */
/* Story 191 / 82.3 (replacing story 185's column): the reactions under the vibe, one centred row
   that wraps into balanced lines, held to the reading measure; the stage keeps the hairline under both. */
.vibe__stage { padding-bottom: var(--row); border-bottom: var(--ts-hairline) solid var(--rule); }
.vibe__stage .reactions { justify-content: center; max-width: var(--ts-measure); margin: calc(var(--prose-unit) * 4) auto 0; }
.vibe { min-width: 0; display: flex; flex-direction: column; gap: var(--prose-gap-p); }
.vibe__head { display: flex; flex-direction: column; gap: var(--prose-unit); }
.vibe__cat { margin: 0; color: var(--amber-text); }
.vibe__question { margin: 0; overflow-wrap: anywhere; }
.vibe__private { margin: 0; color: var(--fg-2); }
.vibe__message { max-width: var(--ts-measure); font-size: var(--fs-app-text-2xl); line-height: 1.45; color: var(--fg-2); overflow-wrap: anywhere; }
.vibe__message p { margin: 0 0 var(--prose-gap-p); }
.vibe__people { margin: 0; color: var(--fg-1); overflow-wrap: anywhere; text-align: right; }
.vibe__date { margin: 0; color: var(--fg-2); text-align: right; }
/* Story 186 / 80.8: the team's private or public switch, between the stage and Comments. */
.vibe__team { padding: var(--row) 0; border-bottom: var(--ts-hairline) solid var(--rule); }
.vibe__team .oec-field { margin: 0; }
/* My vibes (story 168 / 77.4): the totals in large mono numerals, the per-category mono labels, the
   Received / Sent tabs over the year strip, and a vibe card: the category amber, "Private" muted, the
   question the title, the message cut to three lines, the reaction band across the card's foot. */
.mytotals { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--prose-unit); margin: 0 0 calc(var(--prose-unit) * 3); font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); }
.mytotals__num { font-size: var(--fs-app-display-2xl); letter-spacing: var(--tr-app-display-2xl); line-height: var(--ts-lh-head); font-variant-numeric: tabular-nums; color: var(--fg-1); }
.mytotals__sep { color: var(--fg-3); }
.mycats { display: flex; flex-wrap: wrap; gap: var(--prose-unit) calc(var(--prose-unit) * 3); margin: 0 0 calc(var(--prose-unit) * 4); padding: 0; list-style: none; }
.mycats__item { overflow-wrap: anywhere; }
.mycats__count { color: var(--amber-text); font-variant-numeric: tabular-nums; }
/* Story 195 / 84.2: under the year strip, the Received / Sent tabs, then the grid. */
.mytabs { margin-bottom: calc(var(--prose-unit) * 4); }
.mytabs + .section { margin-top: 0; }
/* Up to five years and more on a phone: the strips scroll inside themselves, never the page. */
#my-vibes .oec-tabs { max-width: 100%; overflow-x: auto; }
.vgrid { align-items: stretch; }
/* Story 213 / 93.1: --ts-vcard-band is one reaction band line, the place a card with no band keeps empty. */
.vcard { --ts-vcard-band: calc(var(--prose-unit) * 2 + var(--fs-app-text-md)); gap: var(--prose-unit); min-width: 0; overflow: hidden; }
.vcard__head { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--prose-unit) * 2); }
.vcard__cat { min-width: 0; color: var(--amber-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vcard__private { flex-shrink: 0; margin-left: auto; color: var(--fg-2); }
.vcard__question { overflow-wrap: anywhere; }
.vcard__message {
  margin: 0;
  font-size: var(--fs-app-text-md);
  line-height: var(--lh-small);
  color: var(--fg-2);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Story 189 / 82.1: the names line muted, its names still bold. */
.vcard__people { margin: auto 0 0; color: var(--fg-2); overflow-wrap: anywhere; } /* the top margin: the free space, story 213 */
/* Story 180 / 80.2: the names on a card in bold. */
.vcard__name { font-weight: var(--w-semi); }
/* Story 181 / 80.3: the date aligned right, as .mview__meta; it is its own block <p> under the names. */
.vcard__date { margin: 0 0 var(--prose-unit); color: var(--fg-2); text-align: right; }
.mymoments__more,
.myvibes__more { margin: calc(var(--prose-unit) * 3) 0 0; color: var(--fg-2); }
/* The ONE Vibes landing's wall (story 170 / 77.6): "Show more" under the cards, its label whole (no "My vibes" since story 179 / 80.1). */
.vwall__more { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--prose-gap-p); margin-top: calc(var(--prose-unit) * 4); }
.vwall__more .oec-btn { max-width: none; }
/* Story 213 / 93.1: the names line and the date sit at the bottom of every card, right above the band's
   place; a card with no band keeps that place empty (::after, the band's height), so the names of
   every card in a row line up. */
.vcard .mitem__reactions { position: static; box-sizing: border-box; min-height: var(--ts-vcard-band); margin: 0 calc(-1 * var(--p)) calc(-1 * var(--p)); }
.vcard:not(:has(> .mitem__reactions))::after { content: ""; flex: none; height: var(--ts-vcard-band); margin: 0 calc(-1 * var(--p)) calc(-1 * var(--p)); }
@keyframes savebar-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
.savebar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  background: var(--ink);
  border-top: var(--ts-hairline) solid var(--ink);
  box-shadow: var(--elev-3);
  animation: savebar-in var(--dur-base) var(--ease-standard);
}
/* As wide as the page, with the page's side padding. */
.savebar__inner {
  max-width: var(--ts-page);
  margin: 0 auto;
  padding: calc(var(--prose-unit) * 1.5) var(--ts-pad-x);
  display: flex;
  align-items: center;
  gap: var(--prose-gap-p);
  flex-wrap: wrap;
  box-sizing: border-box;
}
.savebar__label {
  flex: 1;
  min-width: calc(var(--row) * 7.5);
  font-family: var(--font-mono);
  font-size: var(--fs-ui-badge);
  letter-spacing: var(--tr-mono-wide);
  text-transform: uppercase;
  color: var(--muted-on-dark);
}
.savebar[data-state="conflict"] .savebar__label { color: var(--paper-on-dark); }
/* Toasts sit above the save bar while it shows (admin-categories.js sets --savebar-h). */
[data-page="moments-admin"] .oec-toaststack[data-position^="bottom"] { bottom: var(--savebar-h, 0); }
@media (max-width: 640px) {
  .catrow { gap: calc(var(--prose-unit) * 1.5); }
  .catrow__switch { margin-left: var(--prose-unit); }
  .savebar__inner { gap: calc(var(--prose-unit) * 1.5); }
  .savebar__label { min-width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .savebar { animation: none; }
}

/* ——— Sign-in screen (AD-7 /auth/login) ——— */
.login {
  flex: 1;
  display: grid;
  place-items: center;
  padding: calc(var(--prose-unit) * 6) calc(var(--prose-unit) * 2) calc(var(--prose-unit) * 9);
  animation: page-in var(--dur-slow) var(--ease-standard);
}
.login__panel {
  width: 100%;
  max-width: var(--ts-login-max);
  display: flex;
  flex-direction: column;
  gap: calc(var(--prose-unit) * 2);
  padding: calc(var(--prose-unit) * 5) calc(var(--prose-unit) * 4.5) calc(var(--prose-unit) * 4);
  background: var(--paper);
  border: var(--ts-hairline) solid var(--rule);
  box-shadow: var(--elev-2);
}
.login__title {
  margin: 0;
  font-size: var(--fs-app-display-xl);
  font-weight: var(--w-medium);
  letter-spacing: var(--tr-app-display-xl);
  line-height: 1.1;
}
.login__lede { margin: 0; font-size: var(--fs-app-text-lg); color: var(--fg-2); text-wrap: pretty; }
.login__domain { font-family: var(--font-mono); font-size: 0.9em; color: var(--fg-1); }
.login__google { margin-top: var(--prose-unit); width: 100%; max-width: none; gap: calc(var(--prose-unit) * 1.5); }
/* Google's mark keeps its own colours (Google sign-in branding); it is the only multi-colour glyph in the app.
   On the ink primary button it sits on a light tile so every colour keeps its contrast. */
.login__gtile {
  display: inline-flex;
  flex: none;
  padding: var(--ts-google-tile-pad);
  background: var(--ts-google-tile);
  border-radius: var(--ts-google-tile-radius);
}
.login__g { width: var(--icon-lg); height: var(--icon-lg); flex: none; }
.login__foot { margin: var(--prose-unit) 0 0; padding-top: calc(var(--prose-unit) * 2); border-top: var(--ts-hairline) solid var(--rule); font-size: var(--fs-app-text-xs); color: var(--fg-2); }
@media (max-width: 480px) {
  .login__panel { padding: calc(var(--prose-unit) * 3.5) calc(var(--prose-unit) * 2.5) var(--row); box-shadow: none; }
}

/* ——— Manage Toolshed, engagement team (/manage, story 4.3, moved by story 7.1) ——— */
.team__list { list-style: none; margin: 0; padding: 0; }
.teamrow { flex-wrap: nowrap; gap: var(--prose-gap-p); }
.teamrow__text { flex: 1 1 auto; min-width: 0; }
.teamrow__email { overflow-wrap: anywhere; }
.teamrow .oec-btn { flex: none; }
/* Manage Toolshed, Initiatives (stories 7.2 and 13.5): one row per initiative with its status label. */
/* Story 47.3: "Add initiative", a ghost link with the arrow, above the list. */
/* "Add initiative" on the right with its whole label (story 125 / 56.3): no 26ch button cap, never past the panel. */
.initadd { display: flex; justify-content: flex-end; margin: 0 0 var(--prose-gap-p); }
.initadd .oec-btn { max-width: 100%; }
.initlist { list-style: none; margin: 0; padding: 0; }
.initrow { gap: var(--prose-gap-p); }
.initrow__text { flex: 1 1 auto; min-width: 0; }
.initrow__name { margin: 0; overflow-wrap: anywhere; }
.initrow__status { flex: none; white-space: nowrap; }
/* Story 7.6: "Edit details" sits next to the status label (story 13.5); the pair wraps under the name on phones. */
.initrow__controls { display: flex; align-items: center; gap: var(--prose-gap-p); flex: none; }
/* Story 7.6: "Edit details" is a link to the initiative's edit page (story 47.1), with the arrow. */
.initrow__controls .oec-btn { flex: none; }
/* The initiative edit page (/manage/initiatives/{id}, story 47.1): six numbered sections of stacked
   Fields that span the page (no measure cap), then the actions row, safe action first. */
.detailsform { display: flex; flex-direction: column; max-width: none; }
.detailsform > .section:first-child { margin-top: 0; }
.detailsform__fields { display: flex; flex-direction: column; gap: var(--prose-gap-p); }
.detailsform__actions { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); margin-top: var(--ts-section-gap); }
/* Manage Toolshed, Home (story 40.1): the home page head's two Fields, then the save button. The Fields span
   the panel (story 44.1); the measure cap that held them at reading width went. */
.homeform { display: flex; flex-direction: column; gap: var(--prose-gap-p); }
.homeform__actions { display: flex; }
/* Site Content's sub-heads, "Home" and "Footer" (story 163 / 76.5): the form's gap spaces them; a
   second one sits a row further from the Field above it. */
.homeform > h3.section__head { margin-bottom: 0; }
.homeform > .oec-field + h3.section__head { margin-top: var(--row); }
/* Story 161 / 76.3: the Groups form under the engagement team list. */
.accessform { margin-top: var(--prose-gap-block); }

/* Manage ONE Talk (/one-talk/admin, story 49.1): the add form's Fields span the page, then the actions
   row (the primary "Add talk" or "Save talk", a ghost "Cancel" while editing); the list is one row per
   talk, newest first, the date as its title, the links as a mono line, "Edit" and "Remove" on the right. */
.talkform { display: flex; flex-direction: column; gap: var(--prose-gap-p); max-width: none; }
.talkform__editing { margin: 0; }
.talkform__actions { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); }
.talkrow[aria-current="true"] { background: var(--paper-2); }

/* Share feedback (/feedback, story 113 / 50.3): "Reported by" as a detail block, then the Fields, spanning
   the page, then "Send feedback". */
.feedbackform { display: flex; flex-direction: column; gap: var(--prose-gap-p); max-width: none; }
.feedbackform .detail { margin-bottom: var(--prose-gap-p); }
.feedbackform__actions { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); }
/* Story 169 / 77.5: Send a vibe. Every Field spans the page; the chips wrap under "To"; the card
   radios stack, a long question wrapping inside its label. */
.sendvibe { display: flex; flex-direction: column; gap: var(--prose-gap-p); max-width: none; min-width: 0; }
.sendvibe__chips { display: flex; flex-wrap: wrap; gap: var(--prose-unit); margin: 0; padding: 0; list-style: none; min-width: 0; }
.sendvibe__chips[hidden] { display: none; }
.sendvibe__chip { max-width: 100%; min-width: 0; cursor: default; }
.sendvibe__chip[data-refused="true"] { border-color: var(--status-danger-text); color: var(--status-danger-text); }
.sendvibe__noscript { margin: 0 0 var(--prose-gap-p); color: var(--fg-2); }
.sendvibe__chipname { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: default; }
.sendvibe__set { border: 0; margin: 0; padding: 0; min-width: 0; }
.sendvibe__set > legend { padding: 0; margin-bottom: var(--prose-unit); }
.sendvibe__cards { display: flex; flex-direction: column; gap: var(--prose-unit); min-width: 0; }
.sendvibe__cards .oec-check { min-width: 0; overflow-wrap: anywhere; }
.sendvibe__actions { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); }
.talklist { list-style: none; margin: 0; padding: 0; }
.talkrow { gap: var(--prose-gap-p); }
.talkrow__text { flex: 1 1 auto; min-width: 0; }
.talkrow__date { margin: 0 0 calc(var(--prose-unit) * 0.5); }
.talkrow__links { overflow-wrap: anywhere; }
.talkrow__controls { display: flex; align-items: center; gap: var(--prose-gap-p); flex: none; }
.talkrow__controls .oec-btn { flex: none; }

/* Official Company Calendars (story 139 / 69.1): the manage page's form and list as Manage ONE Talk's
   (the name as the row title, the link's host as a mono line); on the landing a calendar card keeps
   the description's line breaks. */
.calform { display: flex; flex-direction: column; gap: var(--prose-gap-p); max-width: none; }
.calform__editing { margin: 0; }
.calform__actions { display: flex; flex-wrap: wrap; gap: var(--prose-gap-p); }
.calform .oec-textarea { width: 100%; box-sizing: border-box; }
.callist { list-style: none; margin: 0; padding: 0; }
.calrow { gap: var(--prose-gap-p); }
.calrow[aria-current="true"] { background: var(--paper-2); }
.calrow__text { flex: 1 1 auto; min-width: 0; }
.calrow__name { margin: 0 0 calc(var(--prose-unit) * 0.5); overflow-wrap: anywhere; }
.calrow__host { overflow-wrap: anywhere; }
.calrow__controls { display: flex; align-items: center; gap: var(--prose-gap-p); flex: none; }
.calrow__controls .oec-btn { flex: none; }
/* Story 276 / 121.3: the endpoint form's Fields, a column of checkboxes (two from 640 px). */
.epform__fields { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--prose-gap-p) var(--ts-grid-gap); }
@media (min-width: 640px) { .epform__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.epform__field .oec-check__label { overflow-wrap: anywhere; }
/* Story 277 / 121.4: the key Modal's read-only key, in mono. */
.keymodal__key { font-family: var(--font-mono); width: 100%; }
.calcard .oec-card__body { white-space: pre-line; overflow-wrap: anywhere; }

/* Official holidays (story 140 / 69.2), the Holidays tab: the Type radios side by side, the fixed
   date's month and day Selects side by side, a moving date's year rows (a year Select, the date and
   a ghost "Remove"; the date under the year on a phone); the list rows as the calendars'. */
.holform__set { border: 0; margin: 0; padding: 0; min-width: 0; }
.holform__set[hidden] { display: none; }
.holform__set > legend { padding: 0; margin-bottom: var(--prose-unit); }
.holform__radios { display: flex; flex-wrap: wrap; gap: calc(var(--prose-gap-p) * 2); }
.holform__daymonth { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--prose-gap-p); }
.holyears { list-style: none; margin: 0 0 var(--prose-gap-p); padding: 0; display: flex; flex-direction: column; gap: var(--prose-gap-p); }
.holyear { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto; gap: var(--prose-gap-p); align-items: center; }
@media (max-width: 640px) {
  .holyear { grid-template-columns: minmax(0, 1fr) auto; }
  .holyear__date { grid-column: 1 / -1; grid-row: 2; }
}
.holrow__country { margin: 0 0 calc(var(--prose-unit) * 0.5); }
.holrow__date { overflow-wrap: anywhere; }

/* ONE Picture's contest setup (story 203 / 90.1), "Manage {name}" of Picture Award and Beyond the
   Calendar: the form as the calendars' (the fieldsets as the holidays'), a category row its name and
   a ghost "Remove", the three medal points side by side, the four dates two by two (one column on a
   phone); a row the name, the step and the dates as mono lines. */
.contestcats { list-style: none; margin: 0 0 var(--prose-gap-p); padding: 0; display: flex; flex-direction: column; gap: var(--prose-gap-p); }
.contestcat { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--prose-gap-p); align-items: center; }
/* Story 221 / 94.5: the prizes in the points' three equal columns, the shared hint under the row. */
.contestform__points, .contestform__prizes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--prose-gap-p); }
.contestform__dates { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--prose-gap-p); }
.contestform__points .oec-field, .contestform__dates .oec-field, .contestform__prizes .oec-field { min-width: 0; }
.contestform__cap[hidden] { display: none; }
@media (max-width: 640px) {
  .contestform__dates, .contestform__prizes { grid-template-columns: minmax(0, 1fr); }
}
.contestrow__step { margin: 0 0 calc(var(--prose-unit) * 0.5); }
.contestrow__dates { overflow-wrap: anywhere; }
.contestrow__note { margin: calc(var(--prose-unit) * 0.5) 0 0; color: var(--fg-2); }
/* Story 323 / 123.10: a past contest's Winners tab, three Selects per category. */
.winners__medals { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--prose-gap-p); }
.winners__medals .oec-field, .winners__medals .oec-select { min-width: 0; }
@media (max-width: 640px) {
  .winners__medals { grid-template-columns: minmax(0, 1fr); }
}

/* Story 223 / 95.1: Beyond the Calendar's Save the Dates form. The banner preview shows whole at the
   form's width (never cropped); the pictures are a row of small previews, each with a ghost "Remove". */
.stdform__banner { position: relative; min-height: calc(var(--prose-unit) * 20); margin-bottom: var(--prose-gap-p); background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); }
.stdform__banner[data-filled="true"] { min-height: 0; }
.stdform__bannerimg { display: block; width: 100%; height: auto; filter: var(--ts-photo-filter); }
.stdform__skel { position: absolute; inset: 0; --skh: 100%; }
.stdform__skel .oec-skel__bar { height: 100%; }
.stdpics { list-style: none; margin: 0 0 var(--prose-gap-p); padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--prose-unit) * 18), 1fr)); gap: var(--prose-gap-p); }
.stdpics:empty { display: none; }
.stdpic { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 0.5); min-width: 0; }
.stdpic__frame { position: relative; aspect-ratio: 4 / 3; background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); overflow: hidden; }
.stdpic__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; filter: var(--ts-photo-filter); }
.stdpic__frame .oec-skel { position: absolute; inset: 0; --skh: 100%; }
.stdpic__frame .oec-skel .oec-skel__bar { height: 100%; }
.stdrow__when { overflow-wrap: anywhere; }
/* Story 230 / 99.1: "Holidays" as a fieldset per country, each a column of checkboxes; two columns,
   one at 640px and below. */
.stdform__hgroups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(var(--prose-gap-p) * 2) var(--prose-gap-p); margin-bottom: var(--prose-gap-p); }
.stdform__hgroups:empty { display: none; }
.stdform__hgroup > legend { color: var(--fg-2); }
.stdform__checks { display: flex; flex-direction: column; gap: var(--prose-unit); }
@media (max-width: 640px) {
  .stdform__hgroups { grid-template-columns: minmax(0, 1fr); }
}

/* A Save The Date's page (story 224 / 95.2): the banner whole at the content's full width, first
   after the crumbs; then the head, the description, the video in a 16:9 frame and the pictures as a
   grid of whole pictures. Each img carries its stored size, so the space is reserved. Story 231 /
   99.2 (owner, 2026-11-02): the banner's corners are rounded (its hairline and background with them,
   the picture clipped), the only rounded image; the description spans the content's full width. */
.stdpage__banner { margin: 0 0 calc(var(--prose-unit) * 3); background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); border-radius: var(--ts-std-banner-radius); overflow: hidden; }
.stdpage__img { display: block; width: 100%; height: auto; filter: var(--ts-photo-filter); }
.stdpage__bannerimg { border-radius: inherit; } /* WebKit may not clip a filtered child to the banner's radius */
/* Story 235 / 102.1 (owner, 2026-11-02): the content sits closer to the date lines, at every width
   (24 px under the head, plus the last date line's own 12 px; the id outranks the phone rule's `.page__head`). */
#save-the-date .page__head { margin-bottom: calc(var(--prose-unit) * 3); }
.stdpage__desc { overflow-wrap: anywhere; text-wrap: pretty; }
.stdpage__desc p { margin: 0 0 var(--prose-gap-p); }
.stdpage__video { position: relative; aspect-ratio: 16 / 9; margin: calc(var(--prose-unit) * 3) 0 0; background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); }
.stdpage__watch { margin: var(--prose-unit) 0 0; }
.stdpage__video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.stdpage__pics { list-style: none; margin: calc(var(--prose-unit) * 3) 0 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--prose-unit) * 30), 1fr)); gap: var(--prose-gap-p); align-items: start; }
.stdpage__pic { min-width: 0; background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); }
.stdpage__piclink { display: block; }

/* Official holidays on the landing (story 141 / 69.3): the year tabs, then a block per country, its
   name an h3 over the rows; each row the name, the date under it (story 156 / 75.5) and "Details" on
   the right; on a phone the name, the date and Details stack, Details at the right edge in every
   layout (story 261 / 114.1). */
.holcountries { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 4); }
.holcountry__name { margin: 0 0 var(--prose-unit); font-size: var(--fs-app-text-lg); font-weight: var(--w-semi); }
.holcountry__none { margin: 0; padding: calc(var(--prose-unit) * 2) 0; color: var(--fg-2); border-top: var(--ts-hairline) solid var(--rule); }
.holdays { list-style: none; margin: 0; padding: 0; border-top: var(--ts-hairline) solid var(--rule); }
.holday {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--prose-unit) calc(var(--prose-unit) * 2);
  align-items: center;
  padding: calc(var(--prose-unit) * 2) 0;
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.holday__main { min-width: 0; }
.holday__name { margin: 0; font-weight: var(--w-semi); overflow-wrap: anywhere; }
.holday__date { margin: calc(var(--prose-unit) * 0.5) 0 0; color: var(--fg-2); }
/* Story 261 / 114.1: "Details" at the row's right edge in every layout, the stacked rows (640px and
   below, the 900px columns) included. */
.holday__act { justify-self: end; }
/* Story 152 / 75.1: a holiday before today (São Paulo) is greyed: a past row greys its name in --fg-2
   (the owner at review: --fg-3 is under AA); the date is always muted; "Details" unchanged. The same
   in every layout. Story 155 / 75.4: the rows have no labels line. */
.holday--past .holday__name { color: var(--fg-2); }
@media (max-width: 640px) {
  .holday { grid-template-columns: minmax(0, 1fr); align-items: start; }
}
/* Story 149 / 72.2: from 900px the three countries sit side by side, Brazil | India | United States,
   each row stacked (name, date, Details) to fit its column. */
@media (min-width: 900px) {
  .holcountries { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: calc(var(--prose-unit) * 4); align-items: start; }
  .holday { grid-template-columns: minmax(0, 1fr); align-items: start; }
}

/* ONE Vibes' leaderboards (story 171 / 77.7): "Most thanked" and "Most thankful" side by side above
   640px, stacked at 640px and below; each row the rank (mono), the full name and the count (mono, muted) with a
   hairline between rows, as the holiday lists; the viewer's line above them (story 182 / 80.4) and
   Past members under them, across the section's full width (story 183 / 80.5). */
.boards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: calc(var(--prose-unit) * 4); align-items: start; }
.board { min-width: 0; }
.board__title { margin: 0 0 var(--prose-unit); font-size: var(--fs-app-text-lg); font-weight: var(--w-semi); }
/* Story 295 / 124.4: a title with a control beside it (the Lets Move Distance board's "mi" toggle, right-aligned). */
.board__head { display: flex; align-items: center; justify-content: space-between; gap: calc(var(--prose-unit) * 2); margin: 0 0 var(--prose-unit); }
.board__head .board__title { margin: 0; }
.board__hint { margin: 0 0 var(--prose-unit); color: var(--fg-2); }
.board__empty { margin: 0; padding: calc(var(--prose-unit) * 2) 0; color: var(--fg-2); border-top: var(--ts-hairline) solid var(--rule); }
.board__list { list-style: none; margin: 0; padding: 0; border-top: var(--ts-hairline) solid var(--rule); }
.board__row {
  display: grid;
  grid-template-columns: calc(var(--prose-unit) * 4) minmax(0, 1fr) auto;
  gap: calc(var(--prose-unit) * 2);
  align-items: baseline;
  padding: calc(var(--prose-unit) * 1.5) 0;
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.board__rank { font-family: var(--font-mono); color: var(--amber-text); font-variant-numeric: tabular-nums; }
.board__name { min-width: 0; overflow-wrap: anywhere; }
.board__count { font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.boards__you { margin: 0 0 calc(var(--prose-unit) * 4); text-align: right; font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); }
.boards__past { margin-top: calc(var(--prose-unit) * 5); }
#leaderboards .oec-tabs { max-width: 100%; overflow-x: auto; }
/* Moments That Matter's Leaderboard (story 197 / 85.1): the same look, its one board across the width. */
.boards--single { grid-template-columns: minmax(0, 1fr); }
#leaderboard .oec-tabs { max-width: 100%; overflow-x: auto; }
/* Lets Move's Distance table (story 313 / 124.14): rank, member, Walking, Bike, Total. Tight cell padding and a
   fixed layout so the three number columns and the name fit a phone without a sideways scroll; the name wraps.
   Each distance header is a link ranking the board by that column; its emoji are decorative. */
.oec-table.lmdist { --px: calc(var(--prose-unit) / 2); --py: calc(var(--prose-unit) * 1.5); table-layout: fixed; }
.lmdist th:first-child, .lmdist td:first-child { width: calc(var(--prose-unit) * 4); }
.lmdist th[data-align="right"], .lmdist td[data-align="right"] { width: calc(var(--prose-unit) * 8); }
.lmdist th.board__name { min-width: 0; overflow-wrap: anywhere; font-family: inherit; font-size: inherit; font-weight: inherit; letter-spacing: inherit; text-transform: none; color: inherit; white-space: normal; padding: var(--py) var(--px); border-bottom: var(--ts-hairline) solid var(--rule); vertical-align: top; }
.lmdist .board__rank { font-family: var(--font-mono); color: var(--amber-text); font-variant-numeric: tabular-nums; }
.lmdist__sort { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) / 2); color: inherit; text-decoration: none; }
.lmdist__sort:hover { text-decoration: underline; }
/* Story 317 / 124.16: every sortable header shows the up-down icon, muted; the one the board is ranked by shows
   the down arrow, label and icon in amber, with an amber bar under the header. */
.lmdist__icon { width: var(--icon-xs); height: var(--icon-xs); color: var(--fg-2); }
.lmdist th[aria-sort] { box-shadow: inset 0 calc(var(--ts-sort-bar) * -1) 0 var(--amber); }
.lmdist th[aria-sort] .lmdist__sort, .lmdist th[aria-sort] .lmdist__icon { color: var(--amber-text); }
/* Lets Move's Highlights (story 296 / 124.5): the Global Challenge banner uses the design system's meter
   as is; only its checkpoint labels ("300", "400") are new: under the track at their threshold, mono
   and muted, amber (the threshold too) once reached. The meter keeps room under the track for them. */
.lmgoal { margin-bottom: calc(var(--prose-unit) * 4); }
.lmgoal__meter { padding-bottom: calc(var(--prose-unit) * 3); }
.lmgoal__cp { position: absolute; top: 100%; margin-top: calc(var(--prose-unit) * 0.5); transform: translateX(-50%); font-family: var(--font-mono); font-size: var(--fs-app-label-sm); letter-spacing: var(--tr-app-label-sm); color: var(--fg-3); font-variant-numeric: tabular-nums; }
.lmgoal__cp[data-reached="true"] { color: var(--amber-text); }
.lmgoal .oec-meter__threshold[data-reached="true"] { background: var(--amber); }
/* Lets Move's member highlights (story 297 / 124.6): four design-system cards under the banner, in one
   row, two by two at phone width; the name (or "Tie · {n}" since story 300, "TBA"), the subtitle. */
.lmhigh { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: calc(var(--prose-unit) * 2); margin-top: calc(var(--prose-unit) * 2); }
.lmhigh__card { gap: var(--prose-unit); min-width: 0; }
.lmhigh__card .board__title { margin: 0; }
.lmhigh__name { margin: 0; font-size: var(--fs-app-text-lg); letter-spacing: var(--tr-app-text-lg); font-weight: var(--w-semi); color: var(--fg-1); overflow-wrap: anywhere; }
.lmhigh__sub { margin: 0; }
/* Story 314 / 124.15: the winners stand out from the ink titles: a winner's name and the "Tie · {n}" text
   in the AA-safe amber for text; "TBA", the subtitle and the info button keep theirs. So does the Global
   Challenge total's number, its words staying in ink. The results line sits under the board. */
.lmhigh__who, .lmgoal__n { color: var(--amber-text); }
#leaderboard #board-updated { margin: calc(var(--prose-unit) * 2) 0 0; }
/* Story 300 / 124.9: a tie reads "Tie · {n}" and a small info button; the tied names sit only in a
   popover, a variant of the design system's tooltip (.oec-tip: ink panel, light text) that wraps, keeps
   sentence case, takes the pointer, scrolls past about twelve lines and spans the card's content width
   under the "Tie" line (placed against .lmhigh__tie, never the button), so it stays inside the viewport
   at phone width and the card's height never depends on the number tied. Shown on hover and keyboard
   focus (.oec-tipwrap), and on data-open="true" (assets/app/lets-move-ties.js); data-open="false" is
   a popover closed by a tap or Escape while still hovered or focused. The pointer never leaves the wrap
   on its way to the list: the wrap fills the "Tie" line's height and the popover starts at the line's
   bottom, its visual gap a transparent top border (a bridge). The list itself scrolls and is focusable
   (tabindex 0, "Tied members"), so a keyboard can scroll it and :focus-within keeps it open. */
.lmhigh__tie { position: relative; display: flex; align-items: center; gap: calc(var(--prose-unit) / 2); }
.lmhigh .lmtip { position: static; align-self: stretch; align-items: center; }
.lmhigh .lmtip__pop {
  top: 100%;
  left: 0;
  right: 0;
  max-width: calc(100vw - var(--prose-unit) * 4);
  border-top: var(--prose-unit) solid transparent;
  background-clip: padding-box;
  padding: var(--prose-unit) calc(var(--prose-unit) * 1.5);
  font-family: var(--font-sans);
  font-size: var(--fs-app-text-sm);
  font-weight: var(--w-regular);
  letter-spacing: var(--tr-app-text-sm);
  line-height: 1.4;
  text-transform: none;
  white-space: normal;
  overflow-wrap: anywhere;
  pointer-events: auto;
}
.lmtip__list { list-style: none; margin: 0; padding: 0; max-height: calc(var(--fs-app-text-sm) * 1.4 * 12); overflow-y: auto; }
.oec-tipwrap.lmtip[data-open="true"] .lmtip__pop { opacity: 1; visibility: visible; transition-delay: 0ms; }
.oec-tipwrap.lmtip[data-open="false"] .lmtip__pop { opacity: 0; visibility: hidden; transition-delay: 0ms; }
@media (max-width: 640px) {
  .lmhigh { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .boards { grid-template-columns: minmax(0, 1fr); }
}

/* ONE Talk year pages (/one-talk/{year}, story 49.3; two halves from 900px, story 260): the twelve months as rows with a hairline
   between them, the name a mono label on the left, the month's talks (links with the arrow) or
   "No talk." on the right; on a phone the talks drop under the name. */
.talkmonths { list-style: none; margin: 0; padding: 0; border-top: var(--ts-hairline) solid var(--rule); }
.talkmonth {
  display: grid;
  grid-template-columns: minmax(0, calc(var(--prose-unit) * 24)) minmax(0, 1fr);
  gap: var(--prose-unit) calc(var(--prose-unit) * 2);
  align-items: baseline;
  padding: calc(var(--prose-unit) * 2) 0;
  border-bottom: var(--ts-hairline) solid var(--rule);
}
.talkmonth__name { margin: 0; }
.talkmonth__none { margin: 0; color: var(--fg-2); }
.talkmonth__talks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--prose-unit); }
.talkmonth__link { display: inline-flex; align-items: center; gap: calc(var(--prose-unit) / 2); text-decoration: none; }
.talkmonth__link:hover { text-decoration: underline; text-underline-offset: calc(var(--prose-unit) * 0.375); }
.talkmonth__link .oec-icon { color: var(--amber-text); }
@media (max-width: 640px) {
  .talkmonth { grid-template-columns: minmax(0, 1fr); }
}
/* Story 260 / 113.1: from 900px the months in two equal halves, January to June on the left and
   July to December on the right: the one list, in order, filled column by column, six rows each, so
   a row's two months line up; each column's first month carries the top hairline, so no rule crosses
   the gap. Below 900px one column, January to December (a half under 900px leaves the talks too
   narrow beside the month's name). */
@media (min-width: 900px) {
  .talkmonths { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(6, auto); grid-auto-flow: column; column-gap: var(--ts-grid-gap); border-top: 0; }
  .talkmonth:nth-child(6n + 1) { border-top: var(--ts-hairline) solid var(--rule); }
}

/* Admin "Add" tab (story 11.3): "Add another" under the confirmation. */
.addform__again { margin-top: calc(var(--prose-unit) * 1.5); }

/* ——— People picker (story 164 / 76.6): an email Field with suggestions from the people directory,
   the listbox in the design system's Menu panel (`.oec-menu__panel`, `.oec-menu__opt`). ——— */
.personpicker__box { position: relative; }
.personpicker__panel { left: 0; right: 0; min-width: 0; max-width: none; }
.personpicker__opt { flex-direction: column; align-items: flex-start; gap: var(--ts-hairline); }
.personpicker__name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.personpicker__email { max-width: 100%; overflow-wrap: anywhere; font-family: var(--font-mono); font-size: var(--fs-app-label-md); letter-spacing: var(--tr-app-label-md); color: var(--fg-2); }

/* ——— ONE Picture uploads (story 206 / 90.4): the upload form and the picture cards. A picture
   shows whole, never cropped: `object-fit: contain` in a fixed-height tile. ——— */
.pictureform { display: flex; flex-direction: column; gap: var(--row); min-width: 0; }
.pictureform__close { margin: 0; color: var(--fg-2); }
.pictureform__photo { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 1.25); min-width: 0; }
.pdrop {
  position: relative;
  height: calc(var(--prose-unit) * 45);
  padding: 0;
  align-items: stretch;
  justify-content: center;
  cursor: pointer;
  background: var(--paper-2);
}
.pdrop:hover { border-color: var(--fg-1); }
.pdrop[data-filled="true"] { border-style: solid; border-color: var(--rule); }
.pdrop__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; filter: var(--ts-photo-filter); }
.pdrop__placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: calc(var(--prose-unit) * 2); flex: 1; padding: calc(var(--prose-unit) * 4); text-align: center; }
.pdrop__skel { position: absolute; inset: 0; --skh: 100%; }
.pdrop__skel .oec-skel__bar { height: 100%; }
.pgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--row); margin: 0; padding: 0; list-style: none; }
.pgrid[hidden] { display: none; }
.pcard { display: flex; flex-direction: column; min-width: 0; padding: 0; overflow: hidden; }
.pcard__media { height: calc(var(--prose-unit) * 30); background: var(--paper-2); border-bottom: var(--ts-hairline) solid var(--rule); }
.pcard__img { display: block; width: 100%; height: 100%; object-fit: contain; filter: var(--ts-photo-filter); }
.pcard__body { display: flex; flex-direction: column; gap: var(--prose-unit); padding: calc(var(--prose-unit) * 2); min-width: 0; }
.pcard__cat, .pcard__desc { margin: 0; overflow-wrap: anywhere; }
.pcard__owner { margin: 0; overflow-wrap: anywhere; font-weight: var(--w-semi); }
.pcard__actions { display: flex; flex-wrap: wrap; gap: var(--prose-unit); justify-content: flex-end; margin-top: auto; padding-top: var(--prose-unit); }
@media (max-width: 900px) { .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .pgrid { grid-template-columns: minmax(0, 1fr); } }
/* ——— ONE Picture voting (story 208 / 90.6): the gallery's cards link to the picture's page, with
   ONE Emoji's counts on the picture's band and the medal buttons under the link. ——— */
.pcard__link { display: flex; flex-direction: column; flex: 1; min-width: 0; color: inherit; text-decoration: none; }
.pcard--gallery .pcard__media { position: relative; }
.pcard__desc--clamp { color: var(--fg-2); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.pcard__link:hover .pcard__desc { color: var(--fg-1); }
.pcard__vote { padding: 0 calc(var(--prose-unit) * 2) calc(var(--prose-unit) * 2); }
/* Story 251 / 108.2: a gallery card's picture opens whole in the system Modal (picture-viewer.js);
   the zoom cursor on the cards' pictures, then the Modal's own rules. Story 265 / 116.3: the Modal
   takes the viewport's full height inside the scrim's padding and its body does not scroll: the
   stage shrinks to the room left under the head, the description and the foot, and the picture
   fills that room whole, never cropped, in its grey box; a long description scrolls on its own,
   capped at a quarter of the body, so the picture keeps its room. The stage never goes under 20
   units (160px): on a viewport too short for that the body scrolls, as it did before. Stories 251
   and 256 sized the picture by a fixed chrome allowance instead. */
[data-picture-viewer] .pcard__media { cursor: zoom-in; }
.oec-modal--pviewer { height: 100%; }
.oec-modal--pviewer .oec-modal__body { display: flex; flex-direction: column; min-height: 0; overflow-y: auto; }
.pviewer { display: flex; flex: 1 1 auto; flex-direction: column; gap: calc(var(--prose-unit) * 1.5); min-width: 0; min-height: 0; }
/* Story 256 / 109.5: Previous and Next beside the picture. */
.pviewer__stage { display: grid; flex: 1 1 auto; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--prose-unit); min-height: calc(var(--prose-unit) * 20); }
/* The frame keeps the middle column, so it spans the width when the arrows are hidden (one card). */
.pviewer__frame { grid-column: 2; display: flex; flex-direction: column; justify-content: center; min-width: 0; min-height: 0; height: 100%; overflow: hidden; }
.pviewer__media { display: flex; flex: 1 1 auto; justify-content: center; align-items: center; min-height: 0; background: var(--paper-2); }
/* Story 266 / 116.4: the picture's link fills the grey box; a click opens the picture full size in a new window. */
.pviewer__piclink { display: block; flex: 1 1 auto; min-height: 0; width: 100%; height: 100%; cursor: zoom-in; --ts-focus-offset: calc(-1 * var(--ts-focus-width)); }
.pviewer__img { display: block; width: 100%; height: 100%; object-fit: contain; filter: var(--ts-photo-filter); }
.pviewer__desc { flex: none; max-height: 25%; margin: 0; overflow-y: auto; overflow-wrap: anywhere; }
.pviewer__desc:empty { display: none; }
.pviewer__failed { margin: 0; color: var(--fg-2); text-align: center; }
.pviewer__nav[hidden] { display: none; }
.pviewer__media[hidden] { display: none; }
/* Story 264 / 116.2: the owner's name is the title; a long one wraps in the head. */
.oec-modal--pviewer .oec-modal__title { overflow-wrap: anywhere; }
@media (max-width: 640px) { .oec-modal--pviewer .oec-modal__foot { justify-content: stretch; } .oec-modal--pviewer .oec-modal__foot .oec-btn { flex: 1; } }
.medals { display: flex; flex-wrap: wrap; align-items: center; gap: var(--prose-unit); margin: 0; }
/* Story 247 / 107.1: "Cast your vote" on its own row above the medal buttons. */
.medals__title { flex-basis: 100%; margin: 0; }
.medal__emoji { line-height: 1; }
/* Story 306 / 123.4: a given medal is filled like the primary Button (ink ground, light label), so it reads as chosen at a glance. */
.medal[aria-pressed="true"] { border-color: var(--ink); background: var(--ink); color: var(--paper-on-dark); }
.medal[aria-pressed="true"]:hover { border-color: var(--ink-2); background: var(--ink-2); }
/* Story 253 / 109.2: aligned right at every width; the gallery only. */
.vote__progress { margin: 0 0 calc(var(--prose-unit) * 2); color: var(--fg-2); text-align: right; }
/* Story 290 / 123.2: the voting intro. */
.votingintro { margin: 0 0 calc(var(--prose-unit) * 2); }
.votingintro__title { display: flex; align-items: center; flex-wrap: wrap; gap: var(--prose-unit); margin: 0 0 var(--prose-unit); font-size: var(--fs-app-display-lg); letter-spacing: var(--tr-app-display-lg); font-weight: var(--w-semi); }
.votingintro__emoji { line-height: 1; }
.votingintro__body { margin: 0; font-size: var(--fs-app-text-2xl); letter-spacing: var(--tr-app-text-2xl); }
/* Stories 288 / 123.1 and 290 / 123.2: "Choose your podium for {category}": three centred slots in one row at every width (the thumbs shrink on a narrow phone), square thumbs; not a control. */
/* Story 306 / 123.4: the podium sits in a darker band (--paper-2), the squares on --paper. */
.vpodium { display: flex; flex-direction: column; margin: 0 0 calc(var(--prose-unit) * 2); padding: var(--prose-unit); background: var(--paper-2); text-align: center; }
/* Story 308 / 123.6: the heading shows under the three squares; it stays first in the markup, so it still names the list for screen readers. */
.vpodium__title { order: 1; margin: var(--prose-unit) 0 0; }
.vpodium__slots { display: flex; justify-content: center; gap: var(--prose-unit); margin: 0; padding: 0; list-style: none; }
.vpodium__slot { display: flex; flex: 0 1 var(--ts-podium-thumb); flex-direction: column; align-items: center; gap: calc(var(--prose-unit) * 0.5); min-width: 0; }
/* Owner request 2026-11-06: the medal alone, centred and large (32px, the browser's xx-large); its name stays for screen readers only (.vh). */
.vpodium__medal { display: flex; justify-content: center; align-items: center; font-size: var(--fs-app-display-xl); }
.vpodium__emoji { line-height: 1; }
.vpodium__thumb { display: block; width: 100%; max-width: var(--ts-podium-thumb); height: auto; aspect-ratio: 1; box-sizing: border-box; object-fit: cover; border: var(--ts-hairline) solid var(--rule); background: var(--paper); }
/* Story 306 / 123.4: a slot with no medal shows "TBD" in the thumbnail's square; a slot with a picture shows the thumbnail. */
.vpodium__tbd { display: flex; align-items: center; justify-content: center; width: 100%; max-width: var(--ts-podium-thumb); aspect-ratio: 1; box-sizing: border-box; border: var(--ts-hairline) solid var(--rule); background: var(--paper); color: var(--fg-2); font-size: var(--fs-app-display-sm); letter-spacing: var(--tr-app-display-sm); font-weight: var(--w-semi); }
.vpodium__slot:not([data-picture]) .vpodium__thumb, .vpodium__slot[data-picture] .vpodium__tbd { display: none; }
.vote__noscript { margin: calc(var(--prose-unit) * 2) 0; color: var(--fg-2); }
/* A picture's own page: the picture whole at full width, never cropped. */
.ppage { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 2); }
.ppage__media { margin: 0; background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); }
/* Story 268 / 118.1: the picture is a link that opens it full size in a new window. */
.ppage__piclink { display: block; }
.ppage__piclink:is(a) { cursor: zoom-in; --ts-focus-offset: calc(-1 * var(--ts-focus-width)); }
.ppage__img { width: 100%; height: auto; max-height: 80vh; object-fit: contain; }
.ppage__owner { margin: 0; font-weight: var(--w-semi); overflow-wrap: anywhere; }
.ppage__desc { max-width: var(--ts-measure); overflow-wrap: anywhere; }
.ppage__desc p { margin: 0 0 var(--prose-unit); }
/* Story 255 / 109.4: one centred row held to the reading measure, as on a moment's and a vibe's page; 32 px above with the article's 16 px gap. */
.ppage .reactions { justify-content: center; width: 100%; max-width: var(--ts-measure); margin: calc(var(--prose-unit) * 2) auto 0; }
/* Story 257 / 110.1: on a picture's page the vote, its line and the three medals, is centred at every
   width, under the description and over the centred reactions; the gallery cards kept theirs left until
   story 263 / 116.1. */
.ppage .medals { justify-content: center; }
.ppage .medals__title, .ppage .vote__noscript { text-align: center; }
/* Story 263 / 116.1: the gallery cards' vote, its line and the three medals, centred in the card at
   every width too; the shared `.medals` rule and the markup stay as they are. */
.pcard__vote .medals { justify-content: center; }
.pcard__vote .medals__title { text-align: center; }

/* ——— ONE Picture count page (story 209 / 90.7) ———
   The picture column holds the picture whole (never cropped) beside its description and owner; the
   rank cell carries "Tied" under the rank on a tie of the top three; −1 and +1 sit at the right. */
img.thumb.thumb--whole { object-fit: contain; }
.count__pic { display: flex; align-items: flex-start; gap: calc(var(--prose-unit) * 2); min-width: 0; }
.count__text { display: flex; flex-direction: column; gap: var(--prose-unit); min-width: 0; }
.count__desc { margin: 0; overflow-wrap: anywhere; max-width: var(--ts-measure); }
.count__owner { margin: 0; overflow-wrap: anywhere; color: var(--fg-2); }
.count__rankcell { white-space: nowrap; }
.count__rank { display: block; }
.count__tied { display: block; margin-top: var(--prose-unit); color: var(--amber-text); }
.count__moves { justify-content: flex-end; flex-wrap: nowrap; }
.count__line { margin: 0 0 calc(var(--prose-unit) * 2); color: var(--fg-2); }
.count__publishbody { margin: 0; }
/* Story 244 / 105.3: on the contest's manage page's Votes tab, the lede by step and, during
   Counting, "Publish results" on one line, the button wrapping under the lede when narrow. */
.count__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: calc(var(--prose-unit) * 2); margin-bottom: calc(var(--prose-unit) * 3); }
.count__lede { margin: 0; max-width: var(--ts-measure); color: var(--fg-1); }
#panel-votes, #contest-votes, #count-root { min-width: 0; }

/* ——— ONE Picture results (story 210 / 90.8) ———
   The totals as My vibes' large mono numerals; per category its mono counts, the podium (cards with
   the medal, the picture whole, the owner and the points), then every picture ranked, a hairline
   between rows, the thumbnail whole. */
.results__published { margin: calc(var(--prose-unit) * -2) 0 calc(var(--prose-unit) * 4); color: var(--fg-2); }
.rtotals { list-style: none; padding: 0; gap: var(--prose-unit) calc(var(--prose-unit) * 5); margin-bottom: calc(var(--prose-unit) * 2); }
.rtotals__item { display: flex; align-items: baseline; gap: 0 var(--prose-unit); }
.results__base { margin: 0; color: var(--fg-2); max-width: var(--ts-measure); }
.results__counts { margin: 0 0 calc(var(--prose-unit) * 3); }
.results__sub { margin: calc(var(--prose-unit) * 5) 0 var(--prose-unit); font-size: var(--fs-app-text-lg); font-weight: var(--w-semi); }
.podium__medal { margin: 0; font-size: var(--fs-app-text-2xl); line-height: 1; }
.podium__medal .results__tied { margin-left: var(--prose-unit); vertical-align: middle; }
.results__tied { color: var(--amber-text); }
.rrow__rank .results__tied { display: block; margin-top: calc(var(--prose-unit) * 0.5); }
.podium__prize { margin: 0; font-size: var(--fs-app-text-md); color: var(--fg-1); overflow-wrap: anywhere; }
/* Story 217 / 94.1: "Prizes in each category" under the categories (above the results at Results). */
.contestprizes { margin: 0 0 calc(var(--prose-unit) * 4); }
.rtotals ~ .contestprizes { margin: calc(var(--prose-unit) * 3) 0 0; }
.contestprizes__title { margin: 0 0 var(--prose-unit); }
.contestprizes__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 0.5); }
.contestprizes__item { overflow-wrap: anywhere; max-width: var(--ts-measure); }
/* Story 219 / 94.3: a contest card's rows (the landings' Contests and Past results), in place of
   its body line: "Uploads" / "Voting" mono labels in a column sized to the widest, the short
   ranges lined up beside them; then "Prizes per category" and a medal row per place, left aligned,
   or a Past results card's totals. The details grow so the foot sits at the bottom of a grid row. */
.contestcard__details { flex: 1; display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 1.5); min-width: 0; margin-top: var(--prose-unit); }
.contestfacts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: calc(var(--prose-unit) * 0.5) calc(var(--prose-unit) * 2);
  align-items: baseline;
  margin: 0;
}
.contestfacts dt { margin: 0; color: var(--fg-2); }
.contestfacts dd { margin: 0; font-size: var(--fs-app-text-sm); color: var(--fg-1); overflow-wrap: anywhere; }
.contestcard__prizes { display: flex; flex-direction: column; gap: var(--prose-unit); }
/* Story 226 / 96.1: the label centred across the card. */
.contestcard__prizestitle { margin: 0; text-align: center; }
.contestcard .contestprizes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--prose-unit); }
/* The medal and the place on the left, the prize aligned right across the card (story 226 / 96.1). */
.contestprizes__row { display: grid; grid-template-columns: max-content max-content minmax(0, 1fr); gap: var(--prose-unit); align-items: baseline; }
.contestprizes__place { color: var(--fg-2); text-transform: none; }
.contestprizes__prize { font-size: var(--fs-app-text-sm); color: var(--fg-1); overflow-wrap: anywhere; min-width: 0; text-align: right; }
/* At the narrowest widths a range drops under its label rather than squeezing. */
@media (max-width: 480px) { .contestfacts { grid-template-columns: minmax(0, 1fr); row-gap: 0; } .contestfacts dd + dt { margin-top: calc(var(--prose-unit) * 0.5); } }
.contestcard__totals { margin: 0; font-size: var(--fs-app-text-sm); line-height: 1.5; color: var(--fg-2); }
/* Story 234 / 101.1: a contest page's head reads the card's rows, then the photo count; under it the
   categories (40%) and "Prizes in each category" (60%) in one row, one column at 640px and below;
   with no prizes the categories take the full width. */
.page__headtext .contestfacts { margin-top: var(--prose-unit); }
.page__headtext .contestfacts dd { font-size: var(--fs-app-text-md); }
.contesthead__count { margin: calc(var(--prose-unit) * 1.5) 0 0; color: var(--fg-2); }
.contestmeta__cats { min-width: 0; }
/* Story 237 / 101.2: the prizes first, on the left (60%), the categories on the right (40%). */
.contestmeta--prizes { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--ts-grid-gap); align-items: start; }
@media (max-width: 640px) { .contestmeta--prizes { grid-template-columns: minmax(0, 1fr); row-gap: 0; } }
/* Story 225 / 95.3: Beyond the Calendar's Save the Dates under the contests, one grid (story 236 /
   102.2); no date line since story 258 / 111.1. */
.stdcard .oec-card__body { overflow-wrap: anywhere; margin: 0; }
.podium__points { margin: 0; margin-top: auto; font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); font-variant-numeric: tabular-nums; }
.rrows { list-style: none; margin: 0; padding: 0; border-top: var(--ts-hairline) solid var(--rule); }
.rrow { border-bottom: var(--ts-hairline) solid var(--rule); }
.rrow__link {
  display: grid;
  grid-template-columns: calc(var(--prose-unit) * 6) var(--ts-thumb) minmax(0, 1fr) auto;
  gap: calc(var(--prose-unit) * 2);
  align-items: center;
  padding: calc(var(--prose-unit) * 1.5) 0;
  color: inherit;
  text-decoration: none;
}
.rrow__link:hover .rrow__desc { color: var(--fg-1); }
.rrow__rank { font-family: var(--font-mono); color: var(--amber-text); font-variant-numeric: tabular-nums; }
.rrow .rrow__thumb { width: var(--ts-thumb); height: var(--ts-thumb); box-sizing: border-box; border: var(--ts-hairline) solid var(--rule); background: var(--paper-2); }
.rrow__text { display: flex; flex-direction: column; gap: calc(var(--prose-unit) * 0.5); min-width: 0; }
.rrow__desc { overflow-wrap: anywhere; color: var(--fg-2); }
.rrow__owner { overflow-wrap: anywhere; font-weight: var(--w-semi); }
.rrow__points { font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 640px) {
  .rrow__link { grid-template-columns: calc(var(--prose-unit) * 6) var(--ts-thumb) minmax(0, 1fr); }
  .rrow__points { grid-column: 3; }
}
.ppage__place { margin: 0 0 calc(var(--prose-unit) * 2); font-family: var(--font-mono); font-size: var(--fs-app-text-md); color: var(--fg-2); }

/* The public page of an issued badge (story 279 / 122.2): the image beside its facts; the image is
   a square, and the verified mark is a mono label with the check. */
.badgepage { display: grid; grid-template-columns: minmax(0, calc(var(--row) * 16)) minmax(0, 1fr); gap: var(--row); align-items: start; }
/* Story 309 / 122.11: badge images are transparent PNGs and show alone, with no box or border; only the stand-in for a badge without a PNG (div.mybadge__img) keeps the frame. */
.badgepage__img { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: contain; }
.badgepage__verified { display: flex; align-items: center; gap: calc(var(--prose-unit) / 2); margin: 0 0 var(--prose-gap-block); color: var(--fg-2); }
.badgepage__signin { margin: var(--prose-gap-block) 0 0; }
@media (max-width: 640px) { .badgepage { grid-template-columns: minmax(0, 1fr); } }

/* My badges (story 282 / 122.5): a card per badge, the image a square. */
.mybadges { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--row) * 12), 1fr)); gap: var(--row); }
.mybadge { display: flex; flex-direction: column; gap: var(--prose-gap-block); padding: var(--row); }
.mybadge__img { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: contain; }
div.mybadge__img { background: var(--paper-2); border: var(--ts-hairline) solid var(--rule); }
.mybadge__body { display: flex; flex-direction: column; gap: calc(var(--prose-unit) / 2); flex: 1; }
/* Owner requests 2026-11-06 (screenshots): every badge card (My badges, the landing's week, the year pages) reads the name,
   the description or "Awarded to", the dates on the right, then its actions stacked full width ("View badge" first). */
.mybadge__name { margin: 0; font-size: var(--fs-app-text-xl); font-weight: var(--w-semi); }
.mybadge__desc { margin: 0; font-size: var(--fs-app-text-lg); line-height: var(--lh-body); color: var(--fg-2); }
.mybadge__narrative { margin: 0; font-size: var(--fs-app-text-lg); line-height: var(--lh-body); color: var(--fg-1); white-space: pre-line; }
.mybadge__label { margin: calc(var(--prose-unit) / 2) 0 0; }
.mybadge__date { margin: calc(var(--prose-unit) / 2) 0 0; text-align: right; }
.mybadge__actions { display: flex; flex-direction: column; gap: calc(var(--prose-unit) / 2); }
.mybadge__actions .oec-btn { width: 100%; max-width: none; justify-content: center; }

/* Manage Object Badges (story 280 / 122.3): square thumbnails in the list and the form's preview. */
.badgerow { align-items: flex-start; }
.badgerow__img { flex: none; display: block; width: calc(var(--row) * 2.5); height: calc(var(--row) * 2.5); aspect-ratio: 1 / 1; object-fit: contain; }
.badgeform { max-width: calc(var(--row) * 30); }
.badgeform__preview { margin: 0 0 calc(var(--prose-unit) * 2); }
.badgeform__img { display: block; width: calc(var(--row) * 5); height: calc(var(--row) * 5); aspect-ratio: 1 / 1; object-fit: contain; }
.badgeform__img[hidden] { display: none; }
