/* Components. They read semantic tokens only, so every one of them follows
   Live's look or the event style it sits inside. */

@layer components {
  /* ---- Type ------------------------------------------------------- */

  .ui-title {
    font-family: var(--font-display);
    font-weight: var(--title-weight);
    font-size: var(--title-size, var(--text-3xl));
    line-height: var(--title-leading);
    letter-spacing: var(--title-tracking);
    text-transform: var(--title-case);
    color: var(--color-heading);
    text-wrap: balance;
    overflow-wrap: anywhere;
  }

  /* DS8: a headline the display face cannot render is set entirely in the
     safe face, never letter by letter. Applied by the display_font filter. */
  .ui-title--safe,
  .ui-heading--safe {
    font-family: var(--font-display-safe);
    font-weight: var(--title-weight-safe);
    letter-spacing: var(--title-tracking-safe);
  }

  .ui-title em,
  .ui-heading em {
    font-style: var(--title-style-accent);
    color: var(--color-brand);
  }

  /* Each size takes the base tracking scaled, rather than its own value, so an
     event style that sets --title-tracking keeps its character at every size. */
  .ui-title--lg { --title-size: var(--text-4xl); letter-spacing: calc(var(--title-tracking) * 1.3); }
  .ui-title--md { --title-size: var(--text-2xl); letter-spacing: calc(var(--title-tracking) * 0.6); }

  .ui-heading {
    font-family: var(--heading-font);
    font-weight: var(--heading-weight);
    font-size: var(--heading-size, var(--text-2xl));
    letter-spacing: var(--heading-tracking);
    line-height: 1.1;
    color: var(--color-heading);
  }

  .ui-heading--sm { --heading-size: var(--text-xl); }

  /* Programme sets headings as small mono labels. */
  [data-style="programme"] .ui-heading {
    --heading-size: var(--text-xs);
    text-transform: uppercase;
    color: var(--color-faint);
  }

  .ui-label {
    font-family: var(--label-font);
    font-size: var(--label-size);
    font-weight: var(--label-weight);
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
    color: var(--label-color);
    line-height: 1.3;
  }

  /* Half of these are a section's actual heading -- an <h2> carrying the id
     something else is aria-labelledby -- and half sit above a value. One
     wants to be found and the other wants to get out of the way, so they
     cannot share a colour. The markup already tells them apart, which is why
     this reads the element rather than asking for a second class. */
  h2.ui-label,
  h3.ui-label {
    color: var(--label-strong-color);
  }

  .ui-lede {
    font-size: var(--text-lg);
    line-height: 1.55;
    color: var(--color-text);
    max-width: 38rem;
  }

  .ui-data {
    font-family: var(--font-data);
    font-weight: var(--data-weight);
    font-variant-numeric: var(--data-figures);
    color: var(--color-heading);
  }

  .ui-muted { color: var(--color-muted); }
  .ui-small { font-size: var(--text-sm); }

  /* An icon that opens a line of small print belongs *on* that line. The
     reset makes every svg a block, which is right for a standalone icon and
     wrong for `{% icon %} some words`: it pushed the words onto a line of
     their own, so a shield and its sentence read as two things. Only the
     opening icon, so icons used elsewhere in a line are untouched. */
  .ui-small > .ui-icon:first-child {
    display: inline-block;
    vertical-align: -0.15em;
  }

  /* ---- Weekly hours ------------------------------------------------ */

  /* A week of opening times. One row per day: the day's name, then the
     stretches it is open, each a pair of time pickers. Typing
     "09:00-12:00, 14:00-17:00" was a format to get wrong rather than a thing
     to choose, so the only thing left to get wrong is the time itself.

     Everything here works with no JavaScript: every stretch is rendered, and
     the script only hides the empty ones behind a button. */
  .ui-hours {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-2);
  }

  .ui-hours__day {
    display: grid;
    grid-template-columns: 7.5rem 1fr;
    align-items: start;
    gap: var(--p-space-2) var(--p-space-3);
    margin: 0;
    padding: var(--p-space-3) 0;
    border: 0;
    border-top: 1px solid var(--color-line);
  }

  .ui-hours__day:first-child { border-top: 0; }

  /* A legend is placed by the browser, not by the grid, so it is taken out
     of the flow and put back as an ordinary cell. */
  .ui-hours__name {
    float: left;
    width: 7.5rem;
    padding: 0;
    font-weight: 600;
    line-height: 2.5rem;
  }

  .ui-hours__stretches {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-2);
    min-width: 0;
  }

  .ui-hours__stretch {
    display: flex;
    align-items: center;
    gap: var(--p-space-2);
  }

  .ui-hours__time { width: auto; min-width: 7.5rem; flex: 0 1 9rem; }

  .ui-hours__to { color: var(--color-muted); font-size: var(--text-sm); }

  .ui-hours__day .ui-field__error { grid-column: 2; }

  /* Injected by the script; never in the markup, so a page without
     JavaScript shows no button that would do nothing. */
  .ui-hours__controls { grid-column: 2; }

  /* Narrow: the day's name sits above its times rather than beside them,
     and the two pickers take the width between them. A time control draws
     its own "09:00 AM" and a clock, and clips rather than overflows when it
     is squeezed, so the pair gets the row and Clear drops below it. */
  @media (max-width: 34rem) {
    .ui-hours__day { grid-template-columns: 1fr; }
    .ui-hours__name { float: none; width: auto; line-height: 1.4; }
    .ui-hours__day .ui-field__error,
    .ui-hours__controls { grid-column: 1; }

    .ui-hours__stretch {
      display: grid;
      grid-template-columns: 1fr auto 1fr;
      align-items: center;
    }

    .ui-hours__stretch { gap: var(--p-space-1); }

    /* A time control draws "09:00 AM" and a clock in space it will not
       give up, so the few pixels it needs come out of the padding, the
       gap and the indicator's own margin rather than out of the text. */
    .ui-hours__time {
      width: 100%;
      min-width: 0;
      padding-inline: var(--p-space-1);
      font-size: 0.9375rem;
    }

    .ui-hours__time::-webkit-calendar-picker-indicator {
      margin-inline-start: 0;
      padding-inline: 0;
    }

    .ui-hours__stretch [data-hours-action] {
      grid-column: 1 / -1;
      justify-self: start;
      padding-inline: 0;
    }
  }

  /* ---- The six setup steps -------------------------------------------

     An event and a Conversation are both set up in six steps, so they are
     set up by the same component. Two names, one set of rules: the
     assistant's script reaches for `.ev-setup-steps` by that name, so it
     stays. New markup uses `.ui-steps`. */

  .ui-steps,
  .ev-setup-steps { overflow-x: auto; scrollbar-width: none; }

  .ui-steps ol,
  .ev-setup-steps ol { display: flex; gap: var(--p-space-2); margin: 0; padding: 0; list-style: none; }

  .ui-steps a,
  .ev-setup-steps a {
    display: flex;
    align-items: center;
    gap: var(--p-space-2);
    padding: 0.4rem 0.75rem 0.4rem 0.4rem;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-chip);
    color: var(--color-muted);
    font-size: var(--text-sm);
    white-space: nowrap;
  }

  .ui-steps a:hover,
  .ev-setup-steps a:hover { text-decoration: none; color: var(--color-heading); border-color: var(--color-line-strong); }

  .ui-steps a[aria-current="step"],
  .ev-setup-steps a[aria-current="step"] { border-color: var(--color-brand); background: var(--color-brand-soft); color: var(--color-brand); font-weight: 600; }

  .ui-steps__num,
  .ev-setup-steps__num {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: var(--color-sunken);
    font-size: var(--text-xs);
    font-weight: 600;
  }

  .ui-steps a[aria-current="step"] .ui-steps__num,
  .ev-setup-steps a[aria-current="step"] .ev-setup-steps__num { background: var(--color-brand); color: var(--color-on-brand); }

  .ui-steps a.is-missing .ui-steps__num,
  .ev-setup-steps a.is-missing .ev-setup-steps__num { box-shadow: inset 0 0 0 2px var(--color-warn, #b45309); }

  @media (max-width: 47.99rem) {
    .ui-steps a:not([aria-current="step"]) .ui-steps__label,
    .ev-setup-steps a:not([aria-current="step"]) .ev-setup-steps__label { display: none; }
  }

  /* Wrap rather than clip where there is room. The row scrolls sideways with
     `scrollbar-width: none`, which is right on a phone -- the labels are
     hidden there and the chips are small -- but on a desktop the last step
     was cut mid-word ("Review and shar") with nothing to say it could be
     scrolled to. Wrapping shows every step and costs a second line. */
  @media (min-width: 48rem) {
    .ui-steps ol,
    .ev-setup-steps ol { flex-wrap: wrap; row-gap: var(--p-space-2); }
  }

  /* ---- Buttons ---------------------------------------------------- */

  .ui-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--p-space-2);
    min-height: var(--control-height);
    padding: 0 var(--p-space-5);
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    background: var(--color-surface);
    color: var(--color-heading);
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition:
      background-color var(--duration) var(--ease),
      border-color var(--duration) var(--ease),
      box-shadow var(--duration) var(--ease),
      transform var(--p-duration-fast) var(--ease);
  }

  .ui-btn:hover { text-decoration: none; }
  .ui-btn:active { transform: translateY(1px); }

  .ui-btn--primary {
    background: var(--color-brand);
    color: var(--color-on-brand);
    box-shadow: var(--shadow-action);
  }

  .ui-btn--primary:hover {
    background: var(--color-brand-hover);
    color: var(--color-on-brand);
  }

  .ui-btn--secondary {
    border-color: var(--color-line-strong);
  }

  .ui-btn--secondary:hover {
    border-color: var(--color-heading);
    color: var(--color-heading);
  }

  .ui-btn--quiet {
    background: transparent;
    color: var(--color-brand);
    padding-inline: var(--p-space-2);
  }

  .ui-btn--quiet:hover {
    color: var(--color-brand-hover);
    background: var(--color-brand-soft);
  }

  .ui-btn--lg {
    min-height: var(--control-height-lg);
    padding-inline: var(--p-space-7);
    font-size: var(--text-lg);
  }

  .ui-btn--sm {
    min-height: 2.25rem;
    padding-inline: var(--p-space-4);
    font-size: var(--text-sm);
    width: fit-content;
  }

  .ui-btn--danger {
    background: var(--color-bad);
    color: #ffffff;
  }

  .ui-btn--danger:hover { background: color-mix(in srgb, var(--color-bad) 88%, black); color: #ffffff; }

  .ui-btn--block { width: 100%; }

  .ui-btn:disabled,
  .ui-btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
  }

  /* ---- Fields ----------------------------------------------------- */

  .ui-field {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-2);
  }

  /* Heading colour against the help text's muted is the whole difference, the
     way a form's label and its hint differ in print. */
  .ui-field__label {
    font-size: var(--text-sm);
    font-weight: var(--label-weight);
    color: var(--color-heading);
  }

  /* A file input carries a wide intrinsic width and will not shrink for
     its container, so one that missed the house class pushed its card -- and
     with it the whole page -- sideways. Belt to the braces of the class. */
  .ui-field input[type="file"] { max-width: 100%; }

  .ui-field__help {
    font-size: var(--text-sm);
    color: var(--color-muted);
  }

  .ui-field__errors {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-1);
  }

  .ui-field__error {
    display: flex;
    align-items: center;
    gap: var(--p-space-2);
    font-size: var(--text-sm);
    font-weight: 400;
    color: var(--color-bad);
  }

  .ui-field__required { color: var(--color-bad); margin-left: 2px; }
  .ui-field__optional { color: var(--color-faint); font-weight: 400; margin-left: 2px; white-space: nowrap; }

  .ui-field.has-error .ui-input { border-color: var(--color-bad); }

  select.ui-input {
    appearance: none;
    padding-right: 2.5rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236f6a88' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.875rem center;
  }

  input[type="file"].ui-input { padding: 0.45rem 0.8rem; }

  input[type="file"].ui-input::file-selector-button {
    margin-right: var(--p-space-3);
    padding: 0.35rem 0.8rem;
    border: 0;
    border-radius: var(--radius-inner);
    background: var(--color-brand-soft);
    color: var(--color-brand);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    cursor: pointer;
  }

  /* "You are looking at a preview." Sticky rather than scrolled away with the
     page: a preview is meant to look exactly like the thing it previews, so
     this is the only thing standing between a screenshot of a preview ticket
     and somebody trying it at a door. */
  .ui-preview-bar {
    position: sticky;
    top: 0;
    z-index: 70;
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--p-space-4);
    padding: var(--p-space-3) var(--p-space-4);
    border-radius: var(--radius-field);
    background: var(--color-heading);
    color: #fff;
    box-shadow: 0 10px 24px -14px rgba(0, 0, 0, 0.5);
  }

  .ui-preview-bar__tag {
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    background: #fff;
    color: var(--color-heading);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
  }

  .ui-preview-bar__what { font-size: var(--text-sm); flex: 1 1 14rem; }

  /* Something a preview shows but will not let you press. `inert` already
     takes it out of reach and out of the tab order; this is so it looks it. */
  .ui-guest [inert] { opacity: 0.55; }
  .ui-preview-bar__jump { width: auto; max-width: 14rem; }
  .ui-preview-bar .ui-btn { color: #fff; border-color: rgba(255, 255, 255, 0.4); }

  /* Radio and checkbox options as clickable cards. */
  .ui-choices { display: grid; gap: var(--p-space-2); }

  /* A scale: a rating or recommendation score, read as a row of numbers
     rather than a column of cards. Eleven points have to fit a phone, so the
     row wraps instead of scrolling sideways - a number off the edge of the
     screen is a number nobody picks. */
  .ui-choices[data-layout="scale"] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--p-space-1);
  }

  .ui-choices[data-layout="scale"] .ui-choice {
    position: relative;
    flex: 1 1 2.5rem;
    min-width: 2.5rem;
    max-width: min(4.5rem, 100%);
    justify-content: center;
    padding: 0.7rem 0.3rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
  }

  /* The card is the control; a visible dot as well says it twice and costs
     the width a two-digit number needs. */
  .ui-choices[data-layout="scale"] .ui-choice input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .ui-scale-ends {
    display: flex;
    justify-content: space-between;
    gap: var(--p-space-4);
    margin-bottom: var(--p-space-1);
    font-size: var(--text-xs);
    color: var(--color-faint);
  }

  .ui-choices[data-scrollable="true"] {
    max-height: 16rem;
    overflow-y: auto;
    padding: var(--p-space-2);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-field);
    background: var(--color-sunken);
  }

  .ui-choices__group {
    padding: var(--p-space-2) var(--p-space-1) 0;
    font-family: var(--label-font);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
    color: var(--color-faint);
  }

  .ui-choice {
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--color-line-strong);
    border-radius: var(--radius-field);
    background: var(--color-surface);
    cursor: pointer;
    transition: border-color var(--duration) var(--ease), background-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
  }

  .ui-choice:hover { border-color: var(--color-brand-line); }

  .ui-choice:has(input:checked) {
    border-color: var(--color-brand);
    background: var(--color-brand-soft);
    box-shadow: 0 0 0 3px var(--color-brand-soft);
  }

  .ui-choice input,
  .ui-consent input {
    width: 1.125rem;
    height: 1.125rem;
    margin: 0;
    flex-shrink: 0;
    accent-color: var(--color-brand);
    cursor: pointer;
  }

  .ui-choice__label { color: var(--color-heading); font-weight: 400; }

  /* A searchable dropdown selection: an input with a chevron and a popup
     menu that opens beneath it. The value lives in a hidden field, the
     input only filters and shows the picked label. */
  .ui-combobox {
    position: relative;
    display: flex;
    align-items: stretch;
    min-height: var(--control-height);
    border: 1px solid var(--color-line-strong);
    border-radius: var(--radius-field);
    background: var(--color-surface);
    transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
  }

  .ui-combobox:focus-within {
    border-color: var(--color-brand);
    box-shadow: 0 0 0 3px var(--color-brand-soft);
  }

  .ui-combobox__input {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    padding: 0.625rem 0.25rem 0.625rem 0.875rem;
    border: 0;
    background: transparent;
    color: var(--color-heading);
    font-size: var(--text-base);
  }

  .ui-combobox__input:focus { outline: none; box-shadow: none; }
  .ui-combobox__input::placeholder { color: var(--color-faint); }
  .ui-combobox__input::-webkit-search-cancel-button { display: none; }

  .ui-combobox__toggle,
  .ui-combobox__clear {
    flex-shrink: 0;
    width: 2.5rem;
    border: 0;
    background: transparent;
    color: var(--color-muted);
    cursor: pointer;
  }

  .ui-combobox__toggle {
    border-left: 1px solid var(--color-line);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236f6a88' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
  }

  .ui-combobox__clear {
    width: 1.75rem;
    font-size: var(--text-lg);
    line-height: 1;
  }

  .ui-combobox__toggle:hover,
  .ui-combobox__clear:hover { color: var(--color-brand); background-color: var(--color-brand-soft); }

  .ui-combobox__menu {
    position: absolute;
    top: calc(100% + 0.375rem);
    left: 0;
    right: 0;
    z-index: 30;
    display: grid;
    gap: var(--p-space-1);
    max-height: 16rem;
    overflow-y: auto;
    padding: var(--p-space-2);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-field);
    background: var(--color-surface);
    box-shadow: var(--shadow-raised);
  }

  .ui-combobox__menu[hidden] { display: none; }

  .ui-combobox__option {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    width: 100%;
    padding: 0.625rem 0.75rem;
    border: 0;
    border-radius: var(--radius-field);
    background: transparent;
    text-align: left;
    font: inherit;
    cursor: pointer;
    transition: background-color var(--duration) var(--ease);
  }

  .ui-combobox__option:hover,
  .ui-combobox__option:focus-visible,
  .ui-combobox__option[aria-selected="true"] { background: var(--color-brand-soft); }

  .ui-combobox__option-label { color: var(--color-heading); font-weight: 400; }
  .ui-combobox__option-meta { color: var(--color-muted); font-size: var(--text-sm); }

  .ui-combobox__empty {
    padding: 0.625rem 0.75rem;
    font-size: var(--text-sm);
    color: var(--color-muted);
  }

  /* A single agreement, e.g. terms or consent to be contacted. */
  .ui-consent {
    display: flex;
    align-items: flex-start;
    gap: var(--p-space-3);
    padding: 0.85rem 1rem;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-field);
    background: var(--color-sunken);
  }

  .ui-consent input { margin-top: 0.2rem; }
  .ui-consent label { color: var(--color-heading); cursor: pointer; }

  /* An organiser's section heading or note inside a form. */
  .ui-form-section {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-1);
    padding-top: var(--p-space-5);
    border-top: var(--divider);
  }

  form > .ui-form-section:first-child { padding-top: 0; border-top: 0; }

  .ui-form-note {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-1);
    padding: 0.85rem 1rem;
    border: 1px solid var(--color-brand-line);
    border-radius: var(--radius-inner);
    background: var(--color-brand-soft);
    color: var(--color-text);
    font-size: var(--text-sm);
  }

  .ui-form-note strong { color: var(--color-heading); }

  /* Questions side by side (docs/PLAN-form-rows.md): equal columns, top-aligned so a
     neighbour's help or error never pushes a label out of line. Each column is at least
     12rem, so where the form is narrow (a phone, the builder's preview) the row wraps,
     and below 24rem it is one column: the stacked form, in the same reading order. */
  .ui-form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    gap: var(--p-space-4);
    align-items: start;
  }

  .ui-form-row > .ui-field { min-width: 0; }

  .ui-input {
    width: 100%;
    min-height: var(--control-height);
    padding: 0.625rem 0.875rem;
    border: 1px solid var(--color-line-strong);
    border-radius: var(--radius-field);
    background: var(--color-surface);
    color: var(--color-heading);
    font-size: var(--text-base);
    transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
  }

  .ui-input::placeholder { color: var(--color-faint); }

  .ui-input:focus {
    outline: none;
    border-color: var(--color-brand);
    box-shadow: 0 0 0 3px var(--color-brand-soft);
  }

  .ui-input[aria-invalid="true"] {
    border-color: var(--color-bad);
  }

  textarea.ui-input {
    min-height: 7rem;
    resize: vertical;
    line-height: 1.5;
  }

  .ui-check {
    display: flex;
    align-items: flex-start;
    gap: var(--p-space-3);
    font-size: var(--text-base);
    color: var(--color-text);
    cursor: pointer;
  }

  .ui-check input {
    width: 1.25rem;
    height: 1.25rem;
    margin: 0.15rem 0 0;
    accent-color: var(--color-brand);
  }

  /* ---- Cards and rows -------------------------------------------- */

  .ui-card {
    background: var(--color-surface);
    border: var(--border-card);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
  }

  .ui-card--padded { padding: var(--p-space-5); }

  @media (min-width: 48rem) {
    .ui-card--padded { padding: var(--p-space-6); }
  }

  .ui-rows > * + * {
    border-top: var(--divider);
  }

  .ui-row {
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
    padding: var(--p-space-4) 0;
  }

  .ui-card .ui-row { padding-inline: var(--p-space-5); }

  .ui-row__body {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
    flex-grow: 1;
  }

  .ui-row__title {
    font-weight: 600;
    color: var(--color-heading);
  }

  .ui-row__meta {
    font-size: var(--text-sm);
    color: var(--color-muted);
  }

  .ui-row__icon {
    color: var(--color-brand);
    align-self: flex-start;
    flex-shrink: 0;
    padding-top: 0.125rem;
  }

  /* ---- A Space on the Spaces list: its picture, what it is, how much is on ---- */

  .ui-space-row {
    display: flex;
    align-items: center;
    gap: var(--p-space-4);
    padding: var(--p-space-4) var(--p-space-5);
    color: inherit;
    text-decoration: none;
  }

  .ui-space-row:hover .ui-space-row__title { color: var(--color-brand); }
  .ui-space-row:focus-visible { outline: 2px solid var(--color-brand); outline-offset: -2px; }

  .ui-space-row__picture {
    flex-shrink: 0;
    width: 6.5rem;
    aspect-ratio: 4 / 5;
    border-radius: var(--p-radius-sm);
    object-fit: cover;
    background: var(--p-line);
  }

  .ui-space-row__picture--blank {
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, #6d1de8, #1d1933);
    color: #fff;
    font-size: 2.25rem;
    font-weight: 600;
  }

  .ui-space-row__body {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-1);
    min-width: 0;
    flex-grow: 1;
  }

  .ui-space-row__title {
    font-weight: 600;
    font-size: var(--text-lg);
    color: var(--color-heading);
  }

  .ui-space-row__about {
    color: var(--color-text);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .ui-space-row__meta {
    font-size: var(--text-sm);
    color: var(--color-muted);
  }

  /* The whole row is the link; on a phone the chevron only takes room from the words. */
  .ui-space-row > svg { display: none; flex-shrink: 0; }

  @media (min-width: 48rem) {
    .ui-space-row__picture { width: 11rem; aspect-ratio: 16 / 10; }
    .ui-space-row > svg { display: block; }
  }

  /* ---- Chips ------------------------------------------------------ */

  .ui-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-1);
    padding: 0.25rem 0.625rem;
    border-radius: var(--radius-chip);
    background: var(--color-sunken);
    color: var(--color-text);
    font-family: var(--label-font);
    font-size: var(--text-xs);
    /* The tint behind a chip is what makes it a chip; the weight was saying
       the same thing a second time. Last of the four label tokens it reads. */
    font-weight: var(--label-weight);
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
    line-height: 1.4;
    white-space: nowrap;
    /* Keeps its own width inside a column instead of stretching. */
    width: fit-content;
  }

  .ui-chip--brand { background: var(--color-brand-soft); color: var(--color-brand); }
  .ui-chip--ok { background: var(--color-ok-soft); color: var(--color-ok); }
  .ui-chip--warn { background: var(--color-warn-soft); color: var(--color-warn); }
  .ui-chip--bad { background: var(--color-bad-soft); color: var(--color-bad); }

  /* ---- Stats ------------------------------------------------------ */

  .ui-stat {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-1);
  }

  .ui-stat__value {
    font-family: var(--font-display);
    font-weight: var(--title-weight);
    font-size: var(--text-3xl);
    line-height: 1;
    letter-spacing: var(--title-tracking);
    font-variant-numeric: var(--data-figures);
    color: var(--color-heading);
  }

  [data-style="programme"] .ui-stat__value {
    font-family: var(--font-data);
    font-weight: 500;
  }

  /* ---- Tabs ------------------------------------------------------- */

  .ui-tabs {
    display: flex;
    gap: var(--p-space-6);
    border-bottom: 1px solid var(--color-line);
    overflow-x: auto;
    scrollbar-width: none;
  }

  .ui-tab {
    padding: 0 0 var(--p-space-3);
    margin-bottom: -1px;
    border-bottom: 2px solid transparent;
    color: var(--color-muted);
    font-weight: 400;
    white-space: nowrap;
  }

  .ui-tab:hover { color: var(--color-heading); text-decoration: none; }

  /* "page" for a tab strip, "step" for a setup's steps -- both are the one
     you are on, and a step list that highlights nothing is a step list that
     does not tell you where you are. */
  .ui-tab[aria-current] {
    color: var(--color-heading);
    font-weight: 600;
    border-bottom-color: var(--color-brand);
  }

  /* ---- Event cover (generated from the title and style) ----------- */

  .ui-cover {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--p-space-4);
    min-height: var(--cover-height, 13rem);
    padding: var(--p-space-5) var(--p-space-6);
    border-radius: var(--cover-radius);
    background-color: var(--cover-bg);
    background-image: var(--cover-pattern);
    background-size: var(--cover-pattern-size);
    color: var(--cover-ink);
    overflow: hidden;
    isolation: isolate;
  }

  .ui-cover__eyebrow {
    font-family: var(--label-font);
    font-size: var(--text-sm);
    font-weight: 400;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
    color: var(--cover-eyebrow);
  }

  .ui-cover__title {
    font-family: var(--cover-title-font);
    font-weight: var(--cover-title-weight);
    font-style: var(--cover-title-style);
    text-transform: var(--cover-title-case);
    letter-spacing: var(--cover-title-tracking);
    font-size: var(--cover-title-size, clamp(2.75rem, 2rem + 3vw, 4rem));
    line-height: 0.92;
    color: var(--cover-ink);
    text-wrap: balance;
    overflow-wrap: anywhere;
  }

  .ui-cover__title.ui-title--safe {
    font-family: var(--font-display-safe);
    font-weight: var(--cover-title-weight-safe);
  }

  /* Invitation's quiet ornament: two hairline rings. */
  .ui-cover::before,
  .ui-cover::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    border: 1px solid rgba(250, 250, 253, 0.24);
    z-index: -1;
  }

  .ui-cover::before { width: 14rem; height: 14rem; right: -4rem; top: -3.5rem; }
  .ui-cover::after { width: 9rem; height: 9rem; right: -1.5rem; top: -1rem; opacity: 0.7; }

  [data-style="poster"] .ui-cover::before,
  [data-style="poster"] .ui-cover::after,
  [data-style="programme"] .ui-cover::before,
  [data-style="programme"] .ui-cover::after {
    content: none;
  }

  /* Poster's date sticker. */
  .ui-cover__sticker { display: none; }

  [data-style="poster"] .ui-cover__sticker {
    position: absolute;
    top: var(--p-space-7);
    right: var(--p-space-5);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 5.5rem;
    height: 5.5rem;
    border-radius: 50%;
    background: var(--color-accent);
    color: #ffffff;
    font-family: var(--font-display);
    font-weight: 800;
    line-height: 0.95;
    transform: rotate(-8deg);
    animation: ui-stamp 450ms 350ms var(--p-ease-spring) both;
  }

  .ui-cover__sticker b { font-size: 1.75rem; letter-spacing: -0.03em; }
  .ui-cover__sticker span { font-size: 0.75rem; letter-spacing: 0.06em; }

  /* ---- Tickets ---------------------------------------------------- */

  .ui-ticket-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--p-space-4);
    padding: var(--p-space-4) var(--p-space-5);
  }

  .ui-ticket-row__price {
    font-family: var(--font-data);
    font-weight: var(--data-weight);
    font-size: var(--text-lg);
    font-variant-numeric: var(--data-figures);
    color: var(--color-heading);
    white-space: nowrap;
  }

  [data-style="poster"] .ui-ticket-row__price { font-family: var(--font-display); font-size: var(--text-xl); }

  .ui-ticket-row.is-unavailable { opacity: 0.55; }

  /* The tear line between an event's details and its tickets. The notches
     are cut in the page colour; the line itself is the style's. */
  .ui-perforation {
    position: relative;
    height: 1.75rem;
  }

  .ui-perforation::before {
    content: "";
    position: absolute;
    inset: 50% var(--p-space-5) auto;
    border-top: var(--perforation);
  }

  [data-style="programme"] .ui-perforation {
    background:
      radial-gradient(circle at 0 50%, var(--color-page) 0.875rem, transparent 0.9rem),
      radial-gradient(circle at 100% 50%, var(--color-page) 0.875rem, transparent 0.9rem);
  }

  /* ---- Avatar ----------------------------------------------------- */

  .ui-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--avatar-size, 2.25rem);
    height: var(--avatar-size, 2.25rem);
    border-radius: var(--radius-control);
    background: var(--color-inverse);
    color: var(--color-on-inverse);
    font-size: var(--text-xs);
    font-weight: 600;
    flex-shrink: 0;
  }

  /* ---- Empty state ------------------------------------------------ */

  .ui-empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--p-space-3);
    padding: var(--p-space-7);
    border: 1px dashed var(--color-line-strong);
    border-radius: var(--radius-card);
  }

  /* ---- Notices ---------------------------------------------------- */

  .ui-notice {
    display: flex;
    align-items: flex-start;
    gap: var(--p-space-3);
    padding: var(--p-space-3) var(--p-space-4);
    border-radius: var(--radius-inner);
    background: var(--color-brand-soft);
    color: var(--color-heading);
    font-size: var(--text-sm);
    line-height: 1.5;
  }

  .ui-notice--ok { background: var(--color-ok-soft); color: var(--color-ok); }
  .ui-notice--warn { background: var(--color-warn-soft); color: var(--color-warn); }
  .ui-notice--bad { background: var(--color-bad-soft); color: var(--color-bad); }
  .ui-notice svg { margin-top: 0.1rem; }

  /* ---- Rich text an organiser wrote ------------------------------- */

  .ui-prose {
    color: var(--color-text);
    font-size: var(--text-base);
    line-height: 1.7;
    overflow-wrap: anywhere;
  }

  /* :where keeps this reset weaker than the spacing rule below. */
  .ui-prose :where(p, ul, ol, blockquote) { margin: 0; }
  .ui-prose > * + * { margin-top: var(--p-space-4); }
  .ui-prose h2, .ui-prose h3 { font-family: var(--font-body); font-weight: 600; font-size: var(--text-lg); color: var(--color-heading); }
  .ui-prose ul, .ui-prose ol { padding-left: 1.25rem; }
  .ui-prose li + li { margin-top: var(--p-space-1); }
  .ui-prose a { text-decoration: underline; }
  .ui-prose img, .ui-prose video { border-radius: var(--radius-inner); height: auto; }
  .ui-prose :where(img, video) { display: block; max-width: 100%; }
  .ui-prose audio { display: block; max-width: 100%; }
  .ui-prose figcaption { margin-top: var(--p-space-2); font-size: var(--text-sm); color: var(--color-muted); }
  .ui-prose blockquote { padding-left: var(--p-space-4); border-left: 3px solid var(--color-brand-line); color: var(--color-muted); }

  /* ---- An uploaded event cover ------------------------------------ */

  .ui-cover-image {
    margin: 0;
    overflow: hidden;
    border-radius: var(--cover-radius);
    background: var(--color-sunken);
  }

  .ui-cover-image img {
    width: 100%;
    height: auto;
    max-height: 32rem;
    object-fit: contain;
  }

  /* A few seconds of motion over the cover (events/motion.py). The cover stays
     underneath and is the poster, so the clip fades in once it has a frame and
     a clip that never plays leaves the page exactly as it was. */
  .ui-cover-image:has(.ui-cover-image__motion) { position: relative; }

  .ui-cover-image__motion {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    transition: opacity 240ms ease;
    pointer-events: none;
  }

  .ui-cover-image.is-moving .ui-cover-image__motion { opacity: 1; }

  /* Belt and braces. The clip is never attached when a device asks for less
     motion (events/js/motion.js), so this only matters if the preference is
     turned on while the page is open. */
  @media (prefers-reduced-motion: reduce) {
    .ui-cover-image__motion { display: none; }
  }

  /* ---- Map ---------------------------------------------------------- */

  .ui-map {
    overflow: hidden;
    border-radius: var(--radius-inner);
    aspect-ratio: 16 / 10;
    background: var(--color-sunken);
  }

  .ui-map iframe { width: 100%; height: 100%; border: 0; }

  .ui-map-link {
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
    padding: var(--p-space-3) var(--p-space-4);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-inner);
    background: var(--color-surface);
    color: var(--color-heading);
  }

  .ui-map-link:hover { border-color: var(--color-brand-line); text-decoration: none; color: var(--color-heading); }
  .ui-map-link > svg:first-child, .ui-map-link > .ev-maps-icon { width: 2.5rem; height: 2.5rem; flex-shrink: 0; }
  .ui-map-link > .ui-icon:last-child { color: var(--color-brand); flex-shrink: 0; }

  /* ---- Event page ---------------------------------------------------
     One column on a phone. From 60rem, two: the flyer and ticket panel on
     the left, held in view, and everything there is to read on the right,
     scrolling on its own so the ticket button never leaves the screen. */

  .ui-event__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-2);
    width: fit-content;
    color: var(--color-brand);
  }

  .ui-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--p-space-2) var(--p-space-5);
    margin: 0;
    padding: 0;
    list-style: none;
    color: var(--color-text);
  }

  .ui-facts li {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-2);
  }

  .ui-facts svg { color: var(--color-brand); flex-shrink: 0; }

  .ui-event__layout {
    display: grid;
    gap: var(--p-space-5);
    align-items: start;
  }

  .ui-event__details { min-width: 0; }

  @media (min-width: 60rem) {
    .ui-event__layout { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--p-space-6); }

    .ui-event__aside {
      position: sticky;
      top: var(--p-space-5);
    }

    .ui-event__details {
      max-height: calc(100dvh - 2 * var(--p-space-5));
      overflow-y: auto;
      /* No overscroll containment: reaching the end of the details carries on
         scrolling the page, and a column short enough to fit never traps. */
      scrollbar-width: none;
    }

    .ui-event__details::-webkit-scrollbar { width: 0; height: 0; }
  }

  @media (min-width: 75rem) {
    .ui-event__layout { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); }
  }

  .ui-card__section { padding: var(--p-space-5); }

  @media (min-width: 48rem) {
    .ui-card__section { padding: var(--p-space-6); }
  }

  .ui-card__section > .ui-label:first-child { display: block; margin-bottom: var(--p-space-3); }

  /* The ticket panel: tickets, the key facts, then the way in. */
  .ui-panel > * + * { border-top: var(--divider); }
  .ui-panel .ui-row { padding-block: var(--p-space-3); }
  .ui-panel .ui-rows .ui-row:first-child { padding-top: var(--p-space-4); }
  .ui-panel .ui-rows .ui-row:last-child { padding-bottom: var(--p-space-4); }
  .ui-panel .ui-row__body .ui-label { font-size: var(--text-xs); }

  .ui-row__value { color: var(--color-heading); }

  .ui-price-headline {
    display: flex;
    align-items: baseline;
    gap: var(--p-space-2);
    margin: 0;
  }

  .ui-price-headline .ui-data {
    font-family: var(--font-display);
    font-weight: var(--title-weight);
    font-size: var(--text-3xl);
    line-height: 1;
  }

  [data-style="programme"] .ui-price-headline .ui-data { font-family: var(--font-data); font-weight: 500; }

  /* Places taken against capacity. */
  .ui-meter { display: flex; flex-direction: column; gap: var(--p-space-2); }

  .ui-meter__track {
    height: 0.375rem;
    border-radius: 999px;
    background: var(--color-sunken);
    overflow: hidden;
  }

  .ui-meter__track > span {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--color-brand);
  }

  /* A bar that is updated in place (progress polled every couple of seconds)
     slides to its new width instead of stepping. */
  .ui-meter__track > span.is-live { transition: width 600ms ease-out; }

  @media (prefers-reduced-motion: reduce) {
    .ui-meter__track > span.is-live { transition: none; }
  }

  /* ---- Registration -------------------------------------------------
     The form on the left, the event summary on the right from 60rem. */

  .ui-register {
    display: grid;
    gap: var(--p-space-5);
    align-items: start;
  }

  @media (min-width: 60rem) {
    .ui-register { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--p-space-6); }
    .ui-register__summary { position: sticky; top: var(--p-space-5); }
  }

  @media (min-width: 75rem) {
    .ui-register { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); }
  }

  .ui-register > .ui-stack > .ui-btn { width: fit-content; }

  .ui-register__assurance {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--p-space-2);
  }

  /* One ticket type with its quantity box. */
  .ui-pick-row {
    display: flex;
    align-items: center;
    /* A narrow phone drops the picker under the ticket rather than squeezing it. */
    flex-wrap: wrap;
    gap: var(--p-space-4);
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-field);
    background: var(--color-surface);
  }

  .ui-pick-row.is-unavailable { opacity: 0.55; }

  .ui-pick-row__price {
    margin-left: var(--p-space-2);
    font-family: var(--font-data);
    font-weight: var(--data-weight);
    font-variant-numeric: var(--data-figures);
    color: var(--color-brand);
    white-space: nowrap;
  }

  .ui-qty { width: 5.5rem; flex: 0 0 auto; text-align: center; }

  .ui-code {
    width: fit-content;
    padding: 0.1rem 0.4rem;
    border-radius: var(--radius-chip);
    background: var(--color-surface);
    border: 1px solid var(--color-brand-line);
    font-family: var(--p-face-plex-mono);
    font-size: 0.9em;
    color: var(--color-heading);
  }

  /* ---- Ticket card ----------------------------------------------------
     A physical admission ticket translated into UI (docs/PLAN-ticket-card.md):
     a white sheet inside a thin violet outline, with a notch cut into each
     side. Live's own look rather than the event's - this reads the primitive
     --p-* faces and fixed radii instead of --font-display and --radius-card,
     so an invitation, a poster and a programme all hand out the same ticket.

     The outline is drawn by ui/static/ui/js/ticket.js as one SVG path, and
     the light that traces it once on load is a dash travelling that same
     path. The CSS border below is the fallback: without JavaScript the card
     is still bordered, it simply has straight sides. */

  .ui-ticket {
    --ticket-pad: clamp(1.25rem, 6%, 1.9rem);
    --notch: 15px;
    position: relative;
    /* A ticket is portrait. Without this it stretches to whatever column it
       is in and reads as a wide panel on a desktop. */
    max-width: 25rem;
    margin-inline: auto;
    border: 2px solid var(--p-violet);
    border-radius: 22px;
    background: #fff;
    box-shadow: 0 18px 40px -26px rgb(29 25 51 / 0.45);
    color: var(--p-ink);
    font-family: var(--p-face-franklin);
    text-align: left;
  }

  /* Once the path is drawn it is the border. Until then - and for a browser
     that never runs the script - the CSS border and a ringed disc stand in. */
  .ui-ticket.is-outlined { border-color: transparent; }

  .ui-ticket:not(.is-outlined) .ui-ticket__tear::before,
  .ui-ticket:not(.is-outlined) .ui-ticket__tear::after { border: 2px solid var(--p-violet); }

  .ui-ticket__sheet {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-4);
    padding: var(--ticket-pad);
  }

  /* The tear line, notch to notch. The notches hang off it rather than off
     the card, so they meet the line at whatever height the details above
     take - a long title or a second address line moves both together. They
     punch the card's white out so the page shows through; the drawn path
     (ui/static/ui/js/ticket.js) supplies the arc. */
  .ui-ticket__tear {
    position: relative;
    border-top: 2px dashed var(--color-brand-line);
  }

  .ui-ticket__tear::before,
  .ui-ticket__tear::after {
    content: "";
    position: absolute;
    top: -1px;
    width: calc(var(--notch) * 2);
    height: calc(var(--notch) * 2);
    border-radius: 50%;
    background: var(--color-page);
    transform: translateY(-50%);
    z-index: 1;
  }

  /* Centred on the card's edge: the padding, then the 2px border, out from the line's own end. */
  .ui-ticket__tear::before { left: calc(var(--ticket-pad) * -1 - var(--notch) - 2px); clip-path: inset(0 0 0 calc(50% - 3px)); }
  .ui-ticket__tear::after { right: calc(var(--ticket-pad) * -1 - var(--notch) - 2px); clip-path: inset(0 calc(50% - 3px) 0 0); }

  .ui-ticket__outline {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
    pointer-events: none;
    z-index: 2;
  }

  .ui-ticket__edge {
    fill: none;
    stroke: var(--p-violet);
    stroke-width: 2;
  }

  /* A light traced this outline once on load - the motion was right, the
     look never was. It is in the commit before the one that removed it, so
     bringing it back is a revert rather than a rewrite. */

  /* A button that is fetching something says so. The spinner replaces the
     icon's place rather than being added beside it, so the button does not
     change width and shuffle what is next to it. */
  .ui-btn.is-busy {
    cursor: progress;
    pointer-events: none;
  }

  .ui-btn.is-busy > .ui-icon { display: none; }

  .ui-btn.is-busy::before {
    content: "";
    width: 1em;
    height: 1em;
    flex: none;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    opacity: 0.85;
    animation: ui-spin 0.7s linear infinite;
  }

  @keyframes ui-spin { to { transform: rotate(360deg); } }

  @media (prefers-reduced-motion: reduce) {
    .ui-btn.is-busy::before { animation-duration: 2s; }
  }

  /* Header: who hosts it. The seller's logo, or their initial in Live's
     violet, then every host's name with the seller first. Spacious and
     white, never a coloured bar. */
  .ui-ticket__top {
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
  }

  .ui-ticket__logo { flex: none; max-height: 2.5rem; max-width: 8.5rem; object-fit: contain; }

  .ui-ticket__logo + .ui-ticket__hosts {
    padding-left: var(--p-space-3);
    border-left: 1px solid var(--color-brand-line);
  }

  .ui-ticket__monogram {
    flex: none;
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: var(--p-violet);
    color: #fff;
    font-weight: 600;
    font-size: var(--text-lg);
  }

  .ui-ticket__hosts {
    min-width: 0;
    margin: 0;
    font-weight: 600;
    font-size: var(--text-base);
    line-height: 1.25;
  }

  .ui-ticket__hosts-label {
    display: block;
    color: var(--color-muted);
    font-size: var(--text-xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
  }

  .ui-ticket__pill {
    align-self: center;
    margin: 0;
    padding: 0.3rem 0.95rem;
    border-radius: 999px;
    background: var(--p-violet-soft, #F1ECFF);
    color: var(--p-violet);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }

  .ui-ticket__title {
    margin: 0;
    font-family: var(--p-face-bricolage);
    font-weight: 800;
    font-size: clamp(1.6rem, 6vw, 2.1rem);
    line-height: 1.05;
    letter-spacing: -0.03em;
  }

  .ui-ticket__lines { display: flex; flex-direction: column; gap: var(--p-space-2); }

  .ui-ticket__line {
    display: flex;
    align-items: flex-start;
    gap: var(--p-space-3);
    margin: 0;
    font-size: var(--text-base);
  }

  .ui-ticket__line .ui-icon { flex: none; margin-top: 0.15em; color: var(--p-violet); }
  .ui-ticket__address { display: block; margin-top: 0.15rem; color: var(--color-muted); font-size: var(--text-sm); }

  /* The code. Generous padding, plain field, nothing decorative inside. */
  .ui-ticket__code {
    align-self: center;
    width: min(16rem, 74%);
    padding: var(--p-space-4);
    border-radius: 18px;
    background: var(--p-violet-soft, #F4F1FE);
  }

  .ui-ticket__code img { display: block; width: 100%; height: auto; aspect-ratio: 1; }

  /* Two columns, a hairline between them. */
  .ui-ticket__meta {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--p-space-3);
    margin: 0;
    padding-top: var(--p-space-4);
    border-top: 1px solid var(--color-brand-line);
  }

  .ui-ticket__meta:has(div + div) { grid-template-columns: 1fr 1fr; }
  .ui-ticket__meta > div + div { padding-left: var(--p-space-4); border-left: 1px solid var(--color-brand-line); }

  .ui-ticket__meta dt {
    color: var(--color-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
  }

  .ui-ticket__meta dd {
    margin: 0.3rem 0 0;
    font-size: var(--text-lg);
    font-weight: 600;
  }

  .ui-ticket__ref {
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-lg);
    letter-spacing: 0.02em;
    user-select: all;
    /* A reference is read whole; breaking it at its hyphen reads as two. */
    white-space: nowrap;
  }

  .ui-ticket__note {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--p-space-2);
    margin: 0;
    color: var(--color-muted);
    font-size: var(--text-sm);
    text-align: center;
  }

  .ui-ticket__note .ui-icon { flex: none; }

  /* A refused ticket draws no code, so its line stands where one would be.
     A used ticket keeps its code for re-entry, so its line is a short marker
     above the panel instead - hence the padding split below. */
  .ui-ticket__spent,
  .ui-ticket__voided {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--p-space-2);
    margin: 0;
    padding: var(--p-space-6) 0;
    font-weight: 600;
    font-size: var(--text-lg);
  }

  .ui-ticket__spent { padding: 0; color: var(--p-violet); }
  .ui-ticket__voided { color: var(--color-bad, #B3261E); }

  .ui-ticket__foot {
    display: flex;
    justify-content: center;
    padding-top: var(--p-space-4);
    border-top: 1px solid var(--color-brand-line);
  }

  .ui-ticket__foot img { height: 1.4rem; width: auto; opacity: 0.9; }

  /* Home: the event's name and who hosts it, then the admission. */
  .ui-ticket--compact { --ticket-pad: var(--p-space-4); }
  .ui-ticket--compact .ui-ticket__title { font-size: var(--text-xl); }
  .ui-ticket--compact .ui-ticket__logo { max-width: 6rem; }

  /* No notch at this size, and no tear: Home is a list, and the
     silhouette is doing no work there. */
  .ui-ticket--compact .ui-ticket__tear { display: none; }
  .ui-ticket--compact .ui-ticket__code { width: min(13rem, 80%); }
  .ui-ticket--compact .ui-ticket__foot { display: none; }

  @media print {
    .ui-ticket { background: #000; break-inside: avoid; }
    .ui-ticket__sheet { background: #fff; }

    /* Printers drop backgrounds by default, and the code's field going with
       them is fine - white behind it is what a scanner wants anyway. */
    .ui-ticket__code { background: #fff !important; border: 1px solid #ddd; }
    .ui-ticket__pill { border: 1px solid #000; }
  }

  /* ---- Confirmation ---------------------------------------------------
     Centred like a receipt; the organiser's own rows keep their alignment. */

  .ui-confirm { text-align: center; }

  .ui-confirm__head,
  .ui-confirm__block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--p-space-2);
  }

  .ui-confirm__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 4.25rem;
    height: 4.25rem;
    margin-bottom: var(--p-space-2);
    border-radius: 50%;
    background: var(--color-ok-soft);
    color: var(--color-ok);
    box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-ok) 6%, transparent);
    animation: ui-stamp-in 450ms 150ms var(--p-ease-spring) both;
  }

  .ui-confirm__icon--brand { background: var(--color-brand-soft); color: var(--color-brand); box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-brand) 6%, transparent); }
  .ui-confirm__icon--warn { background: var(--color-warn-soft); color: var(--color-warn); box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-warn) 6%, transparent); }
  .ui-confirm__icon--bad { background: var(--color-bad-soft); color: var(--color-bad); box-shadow: 0 0 0 10px color-mix(in srgb, var(--color-bad) 6%, transparent); }
  .ui-confirm__icon--muted { background: var(--color-sunken); color: var(--color-muted); box-shadow: none; }

  .ui-confirm .ui-title { text-wrap: balance; }

  /* A ticket's QR code: dark on white whatever the style, so any scanner
     reads it. */
  .ui-qr {
    width: min(14rem, 70vw);
    aspect-ratio: 1;
    padding: var(--p-space-2);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-inner);
    background: #ffffff;
  }

  .ui-qr svg { display: block; width: 100%; height: 100%; }

  /* An image keeps its square: its width/height attributes (there so the page
     doesn't jump while it loads) must not stretch it to the frame's height. */
  .ui-qr img { display: block; width: 100%; height: auto; aspect-ratio: 1; }

  /* The event link's QR is a whole card (wordmark, code, line underneath):
     it brings its own frame. */
  .ui-qr--poster { padding: 0; border: 0; background: none; }

  /* How many tickets: minus and plus around the number, and a slider when
     there is a range worth dragging (ui/static/ui/js/inputs.js). */
  .ui-qty-field {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 0 auto;
  }

  .ui-qty-control {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-inner);
    background: var(--color-surface);
    overflow: hidden;
  }

  .ui-qty-control .ui-qty,
  .ui-qty-control input[type="number"] {
    width: 3.5rem;
    border: 0;
    border-radius: 0;
    text-align: center;
    -moz-appearance: textfield;
    appearance: textfield;
  }

  .ui-qty-control input[type="number"]::-webkit-outer-spin-button,
  .ui-qty-control input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }

  .ui-qty-step {
    border: 0;
    background: var(--color-surface-sunken, #f5f4f9);
    color: var(--color-ink);
    font-size: var(--text-lg, 1.125rem);
    line-height: 1;
    min-width: 2.5rem;
    cursor: pointer;
  }

  .ui-qty-step:hover:not(:disabled) { background: var(--color-brand-soft); color: var(--color-brand); }
  .ui-qty-step:disabled { opacity: 0.4; cursor: default; }

  .ui-qty-slider {
    display: block;
    width: 100%;
    min-width: 9rem;
    max-width: 12rem;
    margin-top: var(--p-space-2);
    accent-color: var(--color-brand);
  }

  /* A code typed one character per box, the way it arrives by email. */
  .ui-code-boxes {
    display: flex;
    align-items: center;
    gap: var(--p-space-1, 4px);
  }

  /* The boxes narrow to keep one code on one line, however small the screen. */
  .ui-code-box {
    flex: 1 1 2.5rem;
    width: 2.5rem;
    min-width: 0;
    max-width: 3rem;
    height: 3rem;
    padding: 0;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-inner);
    background: var(--color-surface);
    color: var(--color-ink);
    font-size: var(--text-lg, 1.125rem);
    font-weight: 600;
    text-align: center;
    text-transform: uppercase;
  }

  .ui-code-box:focus {
    outline: 2px solid var(--color-brand);
    outline-offset: 1px;
  }

  .ui-code-boxes__dash { color: var(--color-muted); padding-inline: 2px; }

  /* Label and value pairs, one per line: an order and its amounts. */
  .ui-dl {
    display: flex;
    flex-direction: column;
    margin: 0;
    text-align: left;
    border-top: var(--divider);
  }

  .ui-dl > div {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--p-space-4);
    padding: var(--p-space-3) 0;
    border-bottom: var(--divider);
  }

  .ui-dl dt { color: var(--color-muted); }
  .ui-dl dd { margin: 0; color: var(--color-heading); text-align: right; }
  .ui-dl__total dt, .ui-dl__total dd { font-weight: 600; color: var(--color-heading); }

  .ui-ref {
    padding: 0.65rem 1.3rem;
    border-radius: var(--radius-field);
    background: var(--color-brand-soft);
    color: var(--color-brand);
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-xl);
    font-weight: 600;
    letter-spacing: 0.1em;
  }

  .ui-confirm__tickets { justify-content: center; }

  /* A chip may now be a link to its own card; it must not look different for it. */
  a.ui-ticket-chip { text-decoration: none; color: inherit; }
  a.ui-ticket-chip:hover { border-color: var(--color-brand); }

  .ui-ticket-chip {
    display: flex;
    flex-direction: column;
    padding: 0.5rem 1rem;
    border: 1px solid var(--color-brand-line);
    border-radius: var(--radius-field);
    background: var(--color-surface);
    color: var(--color-heading);
  }

  .ui-ticket-chip code {
    font-family: var(--p-face-plex-mono);
    font-weight: 600;
    color: var(--color-brand);
  }

  .ui-confirm__facts { text-align: left; }
  .ui-confirm__facts .ui-row { padding: 0; align-items: flex-start; }

  .ui-confirm__actions { justify-content: center; }
  .ui-confirm__note { text-align: center; }

  /* Rows of blocks an organiser placed on the confirmation page. */
  .ui-blocks { display: grid; gap: var(--p-space-5); text-align: left; }

  .ui-blocks__row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--p-space-4) var(--p-space-5);
  }

  .ui-blocks__row--left { justify-content: flex-start; }
  .ui-blocks__row--center { justify-content: center; text-align: center; }
  .ui-blocks__row--right { justify-content: flex-end; text-align: right; }
  .ui-blocks__item { min-width: 0; }
  .ui-blocks__item :where(img, video) { max-width: 100%; height: auto; }
  .ui-blocks__item audio { display: block; max-width: 100%; }

  @keyframes ui-stamp-in {
    from { opacity: 0; transform: scale(0.6); }
    to { opacity: 1; transform: scale(1); }
  }

  /* ---- "or" between two ways of doing the same thing --------------- */

  .ui-divider {
    display: flex;
    align-items: center;
    gap: var(--p-space-3);
    color: var(--color-faint);
    font-size: var(--text-sm);
  }

  .ui-divider::before,
  .ui-divider::after {
    content: "";
    flex: 1;
    border-top: var(--divider);
  }

  /* ---- A quiet disclosure: "Use a password instead" ---------------- */

  .ui-details > summary {
    width: fit-content;
    color: var(--color-muted);
    font-size: var(--text-sm);
    cursor: pointer;
  }

  .ui-details > summary:hover { color: var(--color-heading); }
  .ui-details[open] > summary { margin-bottom: var(--p-space-4); }

  /* ---- Door check-in ----------------------------------------------- */

  .ui-checkin {
    width: 100%;
    max-width: 30rem;
    margin-inline: auto;
    text-align: center;
  }

  .ui-checkin__input {
    min-height: var(--control-height-lg);
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-lg);
    letter-spacing: 0.05em;
    text-align: center;
  }

  .ui-checkin-result {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--p-space-1);
    padding: var(--p-space-6) var(--p-space-4);
    border-radius: var(--radius-card);
    background: var(--color-sunken);
    color: var(--color-muted);
    animation: ui-rise var(--p-duration-slow) var(--ease) both;
  }

  .ui-checkin-result__title { font-size: var(--text-lg); font-weight: 600; }
  .ui-checkin-result__name { font-size: var(--text-2xl); font-weight: 600; color: var(--color-heading); }
  .ui-checkin-result__meta { display: flex; flex-wrap: wrap; justify-content: center; gap: 0 0.35rem; font-size: var(--text-sm); }

  .ui-checkin-result--admitted { background: var(--color-ok-soft); color: var(--color-ok); }
  .ui-checkin-result--already_checked_in { background: var(--color-warn-soft); color: var(--color-warn); }
  .ui-checkin-result--voided { background: var(--color-bad-soft); color: var(--color-bad); }

  /* ---- Organiser pages: headers, search, tables ------------------- */

  .ui-page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--p-space-4);
  }

  .ui-back { width: fit-content; margin-left: calc(-1 * var(--p-space-2)); }
  .ui-narrow { max-width: 40rem; }

  .ui-rule {
    width: 100%;
    height: 1px;
    margin: 0;
    border: 0;
    background: var(--color-line);
  }

  .ui-search {
    display: flex;
    flex-wrap: wrap;
    gap: var(--p-space-2);
    max-width: 36rem;
  }

  .ui-search .ui-input { flex: 1 1 14rem; width: auto; }

  /* An input with a fixed prefix welded to its left edge, like a URL bar that
     repeats the site address. The add-on takes the input's own border on its
     right, so the two read as one control. */
  .ui-affix {
    display: flex;
    align-items: stretch;
    width: 100%;
  }

  .ui-affix__addon {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-2);
    flex: 0 0 auto;
    min-height: var(--control-height);
    padding: 0 var(--p-space-3);
    border: 1px solid var(--color-line-strong);
    border-right: 0;
    border-radius: var(--radius-field) 0 0 var(--radius-field);
    background: var(--color-sunken);
    color: var(--color-faint);
    font-size: var(--text-sm);
    white-space: nowrap;
    overflow: hidden;
  }

  .ui-affix .ui-input {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
  }

  .ui-affix:focus-within .ui-affix__addon {
    border-color: var(--color-brand);
    box-shadow: inset 0 0 0 3px var(--color-brand-soft);
  }

  .ui-table-wrap { overflow-x: auto; }

  .ui-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
  }

  .ui-table th {
    padding: var(--p-space-3) var(--p-space-4);
    border-bottom: var(--divider);
    font-family: var(--label-font);
    font-size: var(--text-xs);
    font-weight: 600;
    text-align: left;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
    color: var(--color-faint);
    white-space: nowrap;
  }

  .ui-table td {
    padding: var(--p-space-3) var(--p-space-4);
    border-bottom: var(--divider);
    color: var(--color-text);
    vertical-align: middle;
  }

  /* A column of numbers right-aligns, and so does the heading over it, or the
     two sit against opposite edges of the same column and the eye reads them
     as not lining up. The markup already asks for this -- the header carries
     .ui-num -- but `.ui-table th` is the more specific selector and was
     winning, so the class sat there doing nothing. */
  .ui-table th.ui-num { text-align: right; }

  /* A row's heading labels its row, not its column. Matching `.ui-table th`
     left it at --text-xs in --color-faint, which is quieter than the numbers
     beside it: the label for a row read as less important than the row. The
     scope attribute is what makes it a heading; it does not have to look like
     the ones along the top. */
  .ui-table th[scope="row"] {
    font-family: inherit;
    font-size: inherit;
    text-transform: none;
    letter-spacing: normal;
    color: var(--color-heading);
  }

  .ui-table tbody tr:last-child td { border-bottom: 0; }
  .ui-table tbody tr:hover td { background: color-mix(in srgb, var(--color-sunken) 55%, transparent); }
  .ui-table--compact th, .ui-table--compact td { padding: var(--p-space-2) var(--p-space-3); white-space: nowrap; }
  .ui-table th.is-email { color: var(--color-brand); }
  .ui-table__primary { font-weight: 600; color: var(--color-heading); }
  a.ui-table__primary:hover { color: var(--color-brand); }
  .ui-table__actions { text-align: right; white-space: nowrap; }
  /* A column of numbers: right-aligned, digits the same width so they line up. */
  .ui-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .ui-table__actions form { display: inline; }
  .ui-table__note td { background: var(--color-bad-soft); color: var(--color-bad); }

  /* On a phone a stacking table shows each row as a card of label and value. */
  @media (max-width: 47.99rem) {
    .ui-table--stack thead { display: none; }
    .ui-table--stack, .ui-table--stack tbody, .ui-table--stack tr, .ui-table--stack td { display: block; }
    .ui-table--stack tr { padding: var(--p-space-3) var(--p-space-4); border-bottom: var(--divider); }
    .ui-table--stack tbody tr:last-child { border-bottom: 0; }
    .ui-table--stack td {
      display: flex;
      justify-content: space-between;
      gap: var(--p-space-4);
      padding: var(--p-space-1) 0;
      border: 0;
      text-align: right;
      overflow-wrap: anywhere;
    }
    .ui-table--stack td[data-label]::before {
      content: attr(data-label);
      flex-shrink: 0;
      color: var(--color-faint);
      text-align: left;
    }
    .ui-table--stack td:first-child { font-size: var(--text-base); }
    .ui-table--stack .ui-table__actions { justify-content: flex-start; padding-top: var(--p-space-2); }
    .ui-table--stack tbody tr:hover td { background: none; }
    /* A note row belongs to the card above it: one block of left-aligned text. */
    .ui-table--stack tr.has-note { border-bottom: 0; }
    .ui-table--stack tr.ui-table__note { padding-top: 0; }
    .ui-table--stack .ui-table__note td {
      display: block;
      padding: var(--p-space-2) var(--p-space-3);
      border-radius: var(--radius-inner);
      text-align: left;
    }
    .ui-table--stack tbody tr.ui-table__note:hover td { background: var(--color-bad-soft); }
  }

  .ui-btn--danger-quiet { color: var(--color-bad); }
  .ui-btn--danger-quiet:hover { color: var(--color-bad); background: var(--color-bad-soft); }

  .ui-row--link { color: inherit; }
  .ui-row--link:hover { background: var(--color-sunken); text-decoration: none; color: inherit; }
  .ui-card .ui-row--link:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
  .ui-row--link > .ui-icon:last-child { color: var(--color-faint); }

  .ui-choice--button { width: 100%; text-align: left; font: inherit; }
  .ui-choice--button[aria-selected="true"] { border-color: var(--color-brand); background: var(--color-brand-soft); }
  .ui-choice--button > .ui-icon { color: var(--color-brand); flex-shrink: 0; }

  .ui-facts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: var(--p-space-4);
    margin: 0;
  }

  .ui-facts-grid dt { font-size: var(--text-sm); color: var(--color-muted); }
  .ui-facts-grid dd { margin: 0; font-weight: 600; color: var(--color-heading); overflow-wrap: anywhere; }

  .ui-codeblock {
    margin: 0;
    padding: var(--p-space-3) var(--p-space-4);
    overflow-x: auto;
    border-radius: var(--radius-inner);
    background: var(--color-sunken);
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-sm);
    color: var(--color-heading);
  }

  .ui-list { margin: 0; padding-left: 1.1rem; display: grid; gap: var(--p-space-2); }

  .ui-column-row {
    display: grid;
    gap: var(--p-space-3);
    margin: 0;
    padding: var(--p-space-4) 0;
    border: 0;
  }

  .ui-column-row legend { float: left; width: 100%; padding: 0; margin-bottom: var(--p-space-1); }
  .ui-column-row__fields { display: grid; gap: var(--p-space-3); grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr)); clear: both; }

  .ui-report .ui-stat__value { font-size: var(--text-2xl); }

  /* ---- Guest page action bar -------------------------------------- */

  .ui-action-bar {
    position: sticky;
    bottom: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--p-space-4);
    padding: var(--p-space-3) var(--space-gutter) calc(var(--p-space-3) + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--color-surface) 94%, transparent);
    border-top: 1px solid var(--color-line);
    backdrop-filter: blur(8px);
  }

  .ui-action-bar .ui-data { white-space: nowrap; }

  /* A setup's buttons on a phone: Back and Save share a row, Save & continue
     gets the full width below. An event's setup and a Conversation's are the
     same three buttons, so they wrap the same way. */
  @media (max-width: 30rem) {
    .ui-setup-actions,
    .ev-setup-actions { flex-wrap: wrap; row-gap: var(--p-space-2); }

    .ui-setup-actions > .ui-cluster,
    .ev-setup-actions > .ui-cluster { display: contents; }

    .ui-setup-actions [value="continue"],
    .ev-setup-actions [value="continue"] { order: 3; flex: 1 1 100%; justify-content: center; }
  }

  @media (min-width: 60rem) {
    .ui-action-bar { position: static; border: 0; background: none; padding-inline: 0; }
    /* On a desktop the ticket panel is always in view and carries the button. */
    .ui-action-bar--phone { display: none; }
  }

  /* ---- Email editor ----------------------------------------------- */

  /* The form first; on a wide screen the preview sits beside it and stays in view. */
  .ui-editor {
    display: grid;
    gap: var(--p-space-5);
    align-items: start;
  }

  .ui-editor__form,
  .ui-editor__aside,
  .ui-editor > * { min-width: 0; }

  @media (min-width: 72rem) {
    .ui-editor { grid-template-columns: minmax(0, 1fr) minmax(22rem, 34rem); }
    .ui-editor__aside { position: sticky; top: var(--p-space-5); }
    /* The review page lists its sections flat; the preview takes the second column. */
    .ui-editor--review { grid-template-rows: auto auto 1fr; }
    .ui-editor--review > * { grid-column: 1; }
    .ui-editor--review > .ui-editor__aside { grid-column: 2; grid-row: 1 / span 3; }
  }

  /* A step that keeps everything in one column, stacked: no side preview. */
  .ui-editor--stacked { grid-template-columns: minmax(0, 1fr); }
  .ui-editor--stacked .ui-editor__aside { display: none; }

  .ui-editor__preview { overflow: hidden; }
  .ui-editor__preview > * + * { border-top: var(--divider); }

  .ui-editor__preview iframe {
    display: block;
    width: 100%;
    height: min(70vh, 44rem);
    border: 0;
    background: #f5f4f8;
  }

  .ui-block { margin: 0; min-width: 0; }
  .ui-block legend { float: left; padding: 0; }

  /* A group of choices under one heading, without the browser's box: the
     surrounding card is the frame (Spaces on the Visibility step). */
  .ui-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
  .ui-fieldset > legend { padding: 0; }
  .ui-block.is-focus { box-shadow: 0 0 0 3px var(--color-brand-soft); border-color: var(--color-brand-line); }
  .ui-block:target { box-shadow: 0 0 0 3px var(--color-brand-soft); }

  .ui-code-input {
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-sm);
    line-height: 1.5;
  }

  .ui-chip--button {
    border: 1px solid var(--color-line);
    text-transform: none;
    letter-spacing: 0;
    font-family: inherit;
    cursor: pointer;
  }

  .ui-chip--button:hover { border-color: var(--color-brand-line); color: var(--color-heading); }

  .ui-variables > summary { cursor: pointer; }

  /* ---- Motion ----------------------------------------------------- */

  @keyframes ui-rise {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
  }

  @keyframes ui-stamp {
    from { opacity: 0; transform: rotate(-8deg) scale(1.25); }
    to { opacity: 1; transform: rotate(-8deg) scale(1); }
  }

  /* One orchestrated entrance: children rise in sequence. */
  .ui-rise > * {
    animation: ui-rise var(--p-duration-slow) var(--ease) both;
  }

  .ui-rise > :nth-child(2) { animation-delay: 60ms; }
  .ui-rise > :nth-child(3) { animation-delay: 120ms; }
  .ui-rise > :nth-child(4) { animation-delay: 180ms; }
  .ui-rise > :nth-child(n + 5) { animation-delay: 240ms; }
}

@layer components {
  /* ---- Home: a person's own events (flow A4) ------------------------ */

  .ui-account__main { max-width: 46rem; }

  /* Someone's own words, quoted: a guest's refund request. */
  .ui-quote {
    margin: 0;
    padding: var(--p-space-3) var(--p-space-4);
    border-left: 3px solid var(--color-brand-line);
    border-radius: var(--radius-inner);
    background: var(--color-sunken);
    color: var(--color-heading);
  }
}

@layer components {
  /* ---- Browse and organiser profiles (flows A1, A6) ------------------- */

  .ui-browse__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
    gap: var(--p-space-4);
  }

  .ui-browse-card {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-3);
    padding: var(--p-space-3);
    color: inherit;
    transition: border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
  }

  .ui-browse-card:hover { border-color: var(--color-brand-line); text-decoration: none; color: inherit; }
  .ui-browse-card .ui-cover { --cover-height: 9.5rem; padding: var(--p-space-4); }
  .ui-browse-card .ui-cover-image img { aspect-ratio: 16 / 9; max-height: none; object-fit: cover; }
  .ui-browse-card__body { padding: 0 var(--p-space-1) var(--p-space-1); flex-grow: 1; }
  .ui-browse-card__title { font-weight: 600; font-size: var(--text-lg); line-height: 1.3; color: var(--color-heading); overflow-wrap: anywhere; }
  .ui-browse-card__foot { margin-top: auto; padding-top: var(--p-space-2); gap: var(--p-space-2); }
  .ui-browse-card__foot strong { white-space: nowrap; }
  .ui-browse-card__org { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

  .ui-browse__city { width: auto; min-width: 12rem; flex: 1 1 12rem; }
  .ui-browse__date { width: auto; }
  .ui-chip--button.is-on { background: var(--color-brand); border-color: var(--color-brand); color: #ffffff; }
  .ui-chip--button:has(input:focus-visible) { outline: 2px solid var(--color-brand); outline-offset: 2px; }

  /* ---- Live's front door (discovery/landing.html, flows §1.1) ------------ */

  /* ---- Hero: the words, and an event in motion beside them ---------- */

  .ui-landing__hero {
    display: grid;
    gap: var(--p-space-7);
    align-items: center;
    padding-top: var(--p-space-6);
  }

  .ui-landing__hero-text { max-width: 44rem; }

  @media (min-width: 60rem) {
    .ui-landing__hero { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  }

  /* The art is a container sized by its width; everything inside is in em
     of a font size set from that width, so the scene scales like a picture. */
  .ui-landing__art {
    position: relative;
    width: 100%;
    max-width: 32rem;
    justify-self: center;
    aspect-ratio: 100 / 86;
    container-type: inline-size;
  }

  /* cqi measures the nearest container *above* an element, never the element
     itself, so the size is set on a scene inside the container. */
  .ui-landing__scene { position: absolute; inset: 0; font-size: calc(100cqi / 28); }
  @supports not (font-size: 1cqi) { .ui-landing__scene { font-size: 14px; } }
  .ui-landing__scene > * { position: absolute; }

  .ui-landing__trail { inset: 0; width: 100%; height: 100%; overflow: visible; }
  .ui-landing__trail path {
    fill: none;
    stroke: var(--color-brand);
    stroke-width: 0.5;
    stroke-dasharray: 1.2 1.6;
    stroke-linecap: round;
    opacity: 0.5;
    vector-effect: non-scaling-stroke;
  }
  .ui-landing__trail path { stroke-width: 2px; stroke-dasharray: 2px 6px; }

  .ui-landing__invite {
    left: 3%;
    top: 10%;
    width: 62%;
    min-height: 0;
    padding: 1.2em 1.4em;
    gap: 0.6em;
    --cover-title-size: 2.5em;
    rotate: -4deg;
    box-shadow: 0 1.6em 3em -1.4em rgba(87, 22, 194, 0.55);
  }

  .ui-landing__invite .ui-cover__eyebrow { font-size: 0.8em; }
  .ui-landing__invite-when { font-size: 0.8em; color: var(--cover-eyebrow); }

  .ui-landing__toast {
    right: 0;
    top: 3%;
    width: 50%;
    display: flex;
    align-items: center;
    gap: 0.6em;
    padding: 0.6em 0.8em;
    border: var(--border-card);
    border-radius: 1em;
    background: var(--color-surface);
    box-shadow: 0 1em 2em -1em rgba(29, 25, 51, 0.3);
    font-size: 0.8em;
    line-height: 1.3;
  }

  .ui-landing__toast .ui-avatar { --avatar-size: 2.2em; font-size: 0.75em; }
  .ui-landing__toast > span:last-child { display: flex; flex-direction: column; min-width: 0; color: var(--color-heading); }

  .ui-landing__count {
    left: 1%;
    bottom: 11%;
    display: flex;
    align-items: center;
    gap: 0.45em;
    padding: 0.5em 0.9em;
    border: var(--border-card);
    border-radius: 999px;
    background: var(--color-surface);
    box-shadow: 0 1em 2em -1em rgba(29, 25, 51, 0.3);
    font-size: 0.85em;
    color: var(--color-heading);
    white-space: nowrap;
  }

  .ui-landing__count b { font-variant-numeric: tabular-nums; }

  .ui-landing__live-dot {
    position: relative;
    width: 0.55em;
    height: 0.55em;
    border-radius: 50%;
    background: var(--color-ok);
  }

  .ui-landing__live-dot::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: var(--color-ok);
    animation: ui-landing-ping 2.4s var(--ease) infinite;
  }

  /* The ticket: its details, a perforation, and a code that really scans. */
  .ui-landing__art-ticket {
    right: 1%;
    bottom: 4%;
    width: 60%;
    display: grid;
    grid-template-columns: 1fr auto;
    border: var(--border-card);
    border-radius: 0.9em;
    background: var(--color-surface);
    box-shadow: 0 1.4em 2.6em -1.2em rgba(29, 25, 51, 0.35);
    rotate: 3deg;
    overflow: hidden;
  }

  .ui-landing__art-ticket-info {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.15em;
    padding: 0.9em 1em;
    border-right: 1px dashed var(--color-line-strong);
    font-size: 0.8em;
    line-height: 1.3;
    min-width: 0;
  }

  .ui-landing__art-ticket-info strong { color: var(--color-heading); font-size: 1.15em; }
  .ui-landing__art-ticket-label { font-size: 0.8em; font-weight: 600; letter-spacing: var(--label-tracking); text-transform: var(--label-case); color: var(--color-brand); }

  .ui-landing__qr { position: relative; width: 6.4em; padding: 0.55em; overflow: hidden; }
  .ui-landing__qr svg { display: block; width: 100%; height: auto; fill: #1d1933; }
  .ui-landing__qr-bg { fill: #ffffff; }

  /* A scan line passing over the code, as at the door. */
  .ui-landing__qr-scan {
    position: absolute;
    left: 0.4em;
    right: 0.4em;
    top: 0.55em;
    height: 2px;
    border-radius: 2px;
    background: var(--color-accent);
    box-shadow: 0 0 0.6em var(--color-accent);
    opacity: 0;
  }

  .ui-landing__confetti { width: 0.6em; height: 0.6em; border-radius: 50%; background: var(--color-brand); opacity: 0.35; }
  .ui-landing__confetti--a { left: 70%; top: 36%; background: var(--color-accent); }
  .ui-landing__confetti--b { left: 12%; top: 2%; width: 0.4em; height: 0.4em; }
  .ui-landing__confetti--c { left: 46%; bottom: 2%; width: 0.45em; height: 0.45em; background: var(--color-accent); }
  .ui-landing__confetti--plus { right: 4%; top: 42%; width: auto; height: auto; background: none; color: var(--color-brand); font-weight: 600; font-size: 1.3em; line-height: 1; }

  /* Arrival, in the order an event happens; then everything drifts gently. */
  .ui-landing__scene > .ui-landing__invite,
  .ui-landing__scene > .ui-landing__toast,
  .ui-landing__scene > .ui-landing__count,
  .ui-landing__scene > .ui-landing__art-ticket {
    transition: opacity 0.7s var(--ease), translate 0.7s var(--ease);
    animation: ui-landing-float 7s ease-in-out infinite;
  }

  .ui-landing__scene > .ui-landing__toast { transition-delay: 0.5s; animation-delay: -2s; }
  .ui-landing__scene > .ui-landing__count { transition-delay: 0.9s; animation-delay: -4s; }
  .ui-landing__scene > .ui-landing__art-ticket { transition-delay: 1.3s; animation-delay: -5.5s; }

  .ui-landing.is-animated .ui-landing__art:not(.is-in) .ui-landing__scene > .ui-landing__invite { opacity: 0; translate: 0 1em; }
  .ui-landing.is-animated .ui-landing__art:not(.is-in) .ui-landing__scene > .ui-landing__toast { opacity: 0; translate: -1.2em 0; }
  .ui-landing.is-animated .ui-landing__art:not(.is-in) .ui-landing__scene > .ui-landing__count { opacity: 0; translate: 0 0.8em; }
  .ui-landing.is-animated .ui-landing__art:not(.is-in) .ui-landing__scene > .ui-landing__art-ticket { opacity: 0; translate: 0 1.6em; }

  .ui-landing.is-animated .ui-landing__art.is-in .ui-landing__qr-scan { animation: ui-landing-scan 3.2s ease-in-out 2.2s infinite; }

  @keyframes ui-landing-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-4px); }
  }

  @keyframes ui-landing-ping {
    0% { transform: scale(1); opacity: 0.6; }
    80%, 100% { transform: scale(2.6); opacity: 0; }
  }

  @keyframes ui-landing-scan {
    0% { top: 0.55em; opacity: 0; }
    15% { opacity: 0.9; }
    60% { top: calc(100% - 0.55em); opacity: 0.9; }
    75%, 100% { top: calc(100% - 0.55em); opacity: 0; }
  }


  /* The journey reads in order, so it only ever splits evenly: four across,
     two by two, or one column on a phone. Never three and an orphan, which
     auto-fit would give at tablet widths. Each stage is a numbered node over
     its card; a line joins the nodes where the order runs in one direction
     (across on a wide screen, down on a phone) and draws itself on arrival. */
  .ui-landing__steps {
    --steps-gap: var(--p-space-4);
    --node: 2.25rem;
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--p-space-6) var(--steps-gap);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  @media (min-width: 40rem) { .ui-landing__steps { grid-template-columns: repeat(2, 1fr); } }
  @media (min-width: 75rem) { .ui-landing__steps { grid-template-columns: repeat(4, 1fr); } }

  .ui-landing__steps::before {
    content: "";
    position: absolute;
    z-index: 0;
    background: linear-gradient(90deg, var(--color-brand-line), var(--color-brand), var(--color-accent));
    transition: transform 1.4s var(--ease);
  }

  /* Down the middle on a phone: it shows between the cards, hidden behind them. */
  @media (max-width: 39.99rem) {
    .ui-landing__steps::before {
      top: calc(var(--node) / 2);
      bottom: 3rem;
      left: calc(50% - 1px);
      width: 2px;
      background: linear-gradient(180deg, var(--color-brand-line), var(--color-brand), var(--color-accent));
      transform-origin: top;
    }
    .ui-landing.is-animated .ui-landing__steps:not(.is-in)::before { transform: scaleY(0); }
  }

  /* Two by two has no single direction to draw, so the nodes stand alone. */
  @media (min-width: 40rem) and (max-width: 74.99rem) {
    .ui-landing__steps::before { display: none; }
  }

  /* Across, from the centre of the first node to the centre of the last. */
  @media (min-width: 75rem) {
    .ui-landing__steps::before {
      top: calc(var(--node) / 2 - 1px);
      left: calc((100% - 3 * var(--steps-gap)) / 8);
      right: calc((100% - 3 * var(--steps-gap)) / 8);
      height: 2px;
      transform-origin: left;
    }
    .ui-landing.is-animated .ui-landing__steps:not(.is-in)::before { transform: scaleX(0); }
  }

  .ui-landing__stage {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--p-space-3);
    transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
    transition-delay: calc(var(--i, 0) * 140ms + 200ms);
  }

  .ui-landing.is-animated .ui-landing__steps:not(.is-in) .ui-landing__stage { opacity: 0; transform: translateY(12px); }

  .ui-landing__stage > .ui-card {
    align-self: stretch;
    flex-grow: 1;
    transition: transform var(--duration) var(--ease), box-shadow var(--duration) var(--ease), border-color var(--duration) var(--ease);
  }

  .ui-landing__node {
    display: grid;
    place-items: center;
    width: var(--node);
    height: var(--node);
    border-radius: 50%;
    border: 1px solid var(--color-brand-line);
    background: var(--color-surface);
    color: var(--color-brand);
    font-family: var(--p-face-plex-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    box-shadow: 0 0 0 4px var(--color-page);
    transition: background-color var(--duration) var(--ease), color var(--duration) var(--ease), transform var(--duration) var(--ease);
  }

  /* Pointing at a stage lights its node and lifts its card. */
  @media (hover: hover) {
    .ui-landing__stage:hover > .ui-card { transform: translateY(-3px); border-color: var(--color-brand-line); box-shadow: var(--shadow-card-hover, 0 12px 28px -14px rgba(29, 25, 51, 0.25)); }
    .ui-landing__stage:hover .ui-landing__node { background: var(--color-brand); color: var(--color-on-brand); transform: scale(1.08); }
  }

  .ui-landing__step {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-2);
    color: var(--color-brand);
    font-family: var(--label-font);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
  }

  .ui-landing__step-title { margin: 0; font-size: var(--text-lg); font-weight: 600; line-height: 1.3; color: var(--color-heading); }

  /* A small picture of each step, drawn with the real components (cover,
     chips, avatars, the door's result) so it restyles along with them.
     Decorative: aria-hidden in the template, and nothing in it responds. */
  .ui-landing__preview {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--p-space-2);
    height: 13rem;
    padding: var(--p-space-3);
    border-radius: calc(var(--radius-card) - 0.25rem);
    background: var(--color-sunken);
    font-size: var(--text-xs);
    line-height: 1.4;
    overflow: hidden;
    pointer-events: none;
    user-select: none;
  }

  .ui-landing__cover {
    --cover-height: 6.25rem;
    --cover-title-size: 1.6rem;
    padding: var(--p-space-3) var(--p-space-4);
    gap: var(--p-space-2);
  }

  .ui-landing__cover .ui-cover__eyebrow { font-size: var(--text-xs); }

  .ui-landing__ticket {
    padding: var(--p-space-2) var(--p-space-3);
    border: var(--border-card);
    border-radius: var(--p-radius-sm);
    background: var(--color-surface);
  }

  .ui-landing__ticket-name { display: flex; flex-direction: column; }

  .ui-landing__mail {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: var(--p-space-2) var(--p-space-3);
    border: var(--border-card);
    border-radius: var(--p-radius-sm);
    background: var(--color-surface);
  }

  .ui-landing__mail strong { color: var(--color-heading); }
  .ui-landing__mail--next { flex-direction: row; align-items: center; gap: var(--p-space-2); opacity: 0.75; }

  .ui-landing__code {
    width: fit-content;
    margin-top: 0.15rem;
    padding: 0.1rem 0.45rem;
    border: 1px dashed var(--color-line-strong);
    border-radius: var(--radius-chip);
    font-family: var(--p-face-plex-mono);
    color: var(--color-heading);
  }

  .ui-landing__guest {
    --avatar-size: 1.75rem;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--p-space-2);
    padding: 0.35rem var(--p-space-2);
    border-radius: var(--p-radius-sm);
    background: var(--color-surface);
  }

  .ui-landing__guest > span:nth-child(2) { display: flex; flex-direction: column; color: var(--color-heading); font-weight: 600; min-width: 0; }
  .ui-landing__guest small { font-weight: 400; }
  .ui-landing__guest .ui-avatar { font-size: 0.625rem; }

  .ui-landing__door { height: 100%; justify-content: center; padding: var(--p-space-3); animation: none; }
  .ui-landing__door .ui-checkin-result__name { font-size: var(--text-xl); }

  /* The guest list is the part competitors don't keep, so it gets its own band. */
  .ui-landing__audience {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
    gap: var(--p-space-6);
    align-items: center;
    background: var(--color-brand-soft);
    border-color: var(--color-brand-line);
  }

  .ui-landing__audience-points li::marker { color: var(--color-brand); }

  /* Past events' guests gathering into one audience. The lines are drawn by
     animating a dash along pathLength="1": no measuring in script. */
  .ui-landing__net-wrap { width: 100%; max-width: 30rem; justify-self: center; }
  .ui-landing__net { display: block; width: 100%; height: auto; overflow: visible; font-family: var(--font-body, inherit); }

  .ui-landing__net-guest circle { stroke: var(--color-brand-soft); stroke-width: 3; }
  .ui-landing__net-guest text { fill: #ffffff; font-size: 10px; font-weight: 600; text-anchor: middle; }
  .ui-landing__net-guest--a circle { fill: var(--color-inverse); }
  .ui-landing__net-guest--b circle { fill: var(--color-brand); }
  .ui-landing__net-guest--c circle { fill: var(--color-accent); }
  .ui-landing__net-label { fill: var(--color-muted); font-size: 13px; }

  .ui-landing__net-line {
    fill: none;
    stroke: var(--color-brand);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
    opacity: 0.55;
    transition: stroke-dashoffset 0.9s var(--ease);
    transition-delay: calc(0.5s + var(--i, 0) * 0.15s);
  }

  .ui-landing__net-line--out { stroke: var(--color-accent); transition-delay: calc(1.3s + (var(--i, 0) - 3) * 0.15s); }

  .ui-landing__net-hub rect { fill: var(--color-brand); }
  .ui-landing__net-hub text { fill: var(--color-on-brand); font-size: 14px; font-weight: 600; text-anchor: middle; }
  .ui-landing__net-end rect { fill: var(--color-surface); stroke: var(--color-brand-line); stroke-width: 1.5; }
  .ui-landing__net-end text { fill: var(--color-heading); font-size: 13px; font-weight: 600; text-anchor: middle; }

  .ui-landing__net-guest,
  .ui-landing__net-hub,
  .ui-landing__net-end {
    transform-box: fill-box;
    transform-origin: center;
    transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
  }

  .ui-landing__net-guest { transition-delay: calc(var(--i, 0) * 50ms); }
  .ui-landing__net-hub { transition-delay: 1.1s; }
  .ui-landing__net-end { transition-delay: calc(1.8s + var(--i, 0) * 0.15s); }

  .ui-landing.is-animated .ui-landing__net-wrap:not(.is-in) .ui-landing__net-guest { opacity: 0; transform: scale(0.6); }
  .ui-landing.is-animated .ui-landing__net-wrap:not(.is-in) .ui-landing__net-line { stroke-dashoffset: 1; }
  .ui-landing.is-animated .ui-landing__net-wrap:not(.is-in) .ui-landing__net-hub { opacity: 0; transform: scale(0.85); }
  .ui-landing.is-animated .ui-landing__net-wrap:not(.is-in) .ui-landing__net-end { opacity: 0; transform: translateX(-6px); }

  /* ---- Real events: the first one featured, all of them alive to the pointer */

  /* A card's cover title is set for a full-width event page; sized to the
     card here, so long words don't break mid-word. */
  .ui-landing__events .ui-cover { --cover-title-size: clamp(1.5rem, 1.1rem + 0.9vw, 2rem); }

  /* Fixed columns (one, two, three) rather than Browse's fit-as-many, so the
     five cards with the first spanning two fill whole rows at every width. */
  .ui-landing__events { grid-template-columns: 1fr; }
  @media (min-width: 48rem) { .ui-landing__events { grid-template-columns: repeat(2, 1fr); } }
  @media (min-width: 64rem) { .ui-landing__events { grid-template-columns: repeat(3, 1fr); } }

  /* The date is already above the title; only the featured cover has room
     for the poster style's round date sticker too. */
  .ui-landing__events .ui-cover__sticker { display: none; }

  /* The first of three or more spans two columns with a taller cover. */
  @media (min-width: 48rem) {
    .ui-landing__events--featured > :first-child { grid-column: span 2; }
    [data-style="poster"].ui-browse-card:first-child .ui-cover__sticker { display: flex; }
    .ui-landing__events--featured > :first-child .ui-cover { --cover-height: 17rem; --cover-title-size: clamp(2.25rem, 1.6rem + 1.6vw, 3.25rem); }
    .ui-landing__events--featured > :first-child .ui-cover-image img { aspect-ratio: 21 / 9; }
    .ui-landing__events--featured > :first-child .ui-browse-card__title { font-size: var(--text-xl); }
  }

  .ui-landing__events .ui-browse-card { transition: transform var(--duration) var(--ease), box-shadow var(--duration) var(--ease), border-color var(--duration) var(--ease); }
  .ui-landing__events .ui-cover,
  .ui-landing__events .ui-cover-image { overflow: hidden; }
  .ui-landing__events .ui-cover-image img,
  .ui-landing__events .ui-cover__title { transition: transform 0.6s var(--ease); }

  @media (hover: hover) {
    .ui-landing__events .ui-browse-card:hover { transform: translateY(-3px); box-shadow: 0 14px 30px -16px rgba(29, 25, 51, 0.3); }
    .ui-landing__events .ui-browse-card:hover .ui-cover-image img { transform: scale(1.04); }
    .ui-landing__events .ui-browse-card:hover .ui-cover__title { transform: scale(1.03); transform-origin: left bottom; }
  }

  /* "See all events": the arrow leans the way it goes. */
  .ui-landing__go .ui-icon { transition: transform var(--duration) var(--ease); }
  .ui-landing__go:hover .ui-icon { transform: translateX(3px); }

  /* ---- The moments people gather: a mosaic, photographs where there are any */
  /* Spans are chosen so the tiles fill whole rows with no holes: 16 cells in
     four columns, 12 in two (Conference and Social wide, the rest single). */
  .ui-landing__moments {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 8.5rem;
    grid-auto-flow: dense;
    gap: var(--p-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .ui-landing__moment[data-kind="conference"],
  .ui-landing__moment[data-kind="social"] { grid-column: span 2; }

  @media (min-width: 40rem) {
    .ui-landing__moments { grid-auto-rows: 10.5rem; }
    .ui-landing__moment[data-kind="conference"] { grid-row: span 2; }
    .ui-landing__moment[data-kind="music"],
    .ui-landing__moment[data-kind="community"] { grid-row: span 2; }
  }

  @media (min-width: 64rem) {
    .ui-landing__moments { grid-template-columns: repeat(4, 1fr); grid-auto-rows: 11rem; }
  }

  .ui-landing__moment > a {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    height: 100%;
    padding: var(--p-space-4);
    border: var(--border-card);
    border-radius: var(--radius-card);
    background-color: var(--color-surface);
    /* A faint dot grid: the quiet tile reads as a drawing, not an empty box. */
    background-image: radial-gradient(var(--color-brand-line) 1px, transparent 1.2px);
    background-size: 14px 14px;
    color: var(--color-heading);
    text-decoration: none;
    overflow: hidden;
    isolation: isolate;
    transition: transform var(--duration) var(--ease), box-shadow var(--duration) var(--ease), border-color var(--duration) var(--ease);
  }

  .ui-landing__moment > a:hover { transform: translateY(-3px); border-color: var(--color-brand-line); box-shadow: 0 14px 30px -16px rgba(29, 25, 51, 0.3); color: var(--color-heading); text-decoration: none; }
  .ui-landing__moment > a:focus-visible { outline: 3px solid var(--color-brand); outline-offset: 3px; }

  /* Without a photograph: the kind's line icon, large, in a soft ring. */
  .ui-landing__moment-art {
    position: absolute;
    top: var(--p-space-4);
    right: var(--p-space-4);
    display: grid;
    place-items: center;
    width: 4.75rem;
    height: 4.75rem;
    border-radius: 50%;
    background: var(--color-brand-soft);
    box-shadow: 0 0 0 8px color-mix(in srgb, var(--color-brand-soft) 45%, transparent);
    color: var(--color-brand);
    transition: transform 0.5s var(--ease);
  }

  .ui-landing__moment > a:hover .ui-landing__moment-art { transform: scale(1.06) rotate(-4deg); }
  .ui-landing__moment-art svg { width: 55%; height: 55%; }

  /* Phone tiles are shorter: a smaller ring keeps clear of the words. */
  @media (max-width: 39.99rem) {
    .ui-landing__moment-art { top: var(--p-space-3); right: var(--p-space-3); width: 3rem; height: 3rem; box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-brand-soft) 45%, transparent); }
  }

  .ui-landing__moment-text { position: relative; display: flex; flex-direction: column; gap: 0.2rem; }

  .ui-landing__moment-label {
    display: inline-flex;
    align-items: center;
    gap: var(--p-space-2);
    font-family: var(--font-display);
    font-size: var(--text-xl);
    line-height: 1.1;
  }

  .ui-landing__moment-icon { display: none; }
  .ui-landing__moment-go { display: inline-flex; align-items: center; gap: 0.3rem; font-size: var(--text-sm); font-weight: 600; color: var(--color-brand); }
  .ui-landing__moment-go .ui-icon { transition: transform var(--duration) var(--ease); }
  .ui-landing__moment > a:hover .ui-landing__moment-go .ui-icon { transform: translateX(3px); }

  /* With a photograph: the picture fills the tile, darkened at the foot for the words. */
  .ui-landing__moment.has-photo > a { background: var(--color-inverse); border-color: transparent; color: #ffffff; }
  .ui-landing__moment.has-photo img {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.7s var(--ease);
  }
  .ui-landing__moment.has-photo > a::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(to top, rgba(20, 16, 40, 0.78), rgba(20, 16, 40, 0.15) 55%, transparent);
  }
  .ui-landing__moment.has-photo > a:hover img { transform: scale(1.04); }
  .ui-landing__moment.has-photo .ui-landing__moment-icon {
    display: inline-grid;
    place-items: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.35);
    backdrop-filter: blur(6px);
  }
  .ui-landing__moment.has-photo .ui-landing__moment-go { color: rgba(255, 255, 255, 0.92); }

  /* ---- Confetti, sparingly: beside the journey and behind the last call ---- */

  .ui-landing__journey-head { position: relative; max-width: 40rem; }

  .ui-landing__sparkles { display: none; }

  @media (min-width: 60rem) {
    .ui-landing__sparkles { display: block; position: absolute; top: 0.25rem; left: calc(100% + var(--p-space-6)); width: 12rem; height: 4.5rem; }
    .ui-landing__sparkles .ui-landing__spark:nth-child(1) { left: 0; top: 1.5rem; }
    .ui-landing__sparkles .ui-landing__spark:nth-child(2) { left: 3.5rem; top: 0.25rem; }
    .ui-landing__sparkles .ui-landing__spark:nth-child(3) { left: 7.5rem; top: 2rem; }
    .ui-landing__sparkles .ui-landing__spark:nth-child(4) { left: 10.5rem; top: 0.5rem; }
  }

  .ui-landing__spark {
    position: absolute;
    display: block;
    font-style: normal;
    animation: ui-landing-float 6s ease-in-out infinite;
  }

  .ui-landing__spark:nth-child(2n) { animation-delay: -3s; }

  .ui-landing__spark--dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; background: var(--color-brand); opacity: 0.45; }
  .ui-landing__spark--pink { background: var(--color-accent); width: 0.45rem; height: 0.45rem; }
  .ui-landing__spark--plus { color: var(--color-accent); font-weight: 600; font-size: 1.25rem; line-height: 1; opacity: 0.7; }

  /* A ticket's outline: a rounded box with a notch bitten from each side. */
  .ui-landing__spark--ticket {
    width: 1.8rem;
    height: 1.1rem;
    border: 1.5px solid var(--color-brand);
    border-radius: 0.3rem;
    opacity: 0.5;
    rotate: -12deg;
    -webkit-mask: radial-gradient(circle 0.3rem at 0 50%, transparent 98%, #000) left / 51% 100% no-repeat,
                  radial-gradient(circle 0.3rem at 100% 50%, transparent 98%, #000) right / 51% 100% no-repeat;
            mask: radial-gradient(circle 0.3rem at 0 50%, transparent 98%, #000) left / 51% 100% no-repeat,
                  radial-gradient(circle 0.3rem at 100% 50%, transparent 98%, #000) right / 51% 100% no-repeat;
  }

  /* ---- The last call to action, with the next events drifting behind it --- */

  .ui-landing__cta { position: relative; overflow: hidden; isolation: isolate; }
  .ui-landing__cta-text { position: relative; z-index: 1; align-items: flex-start; }
  .ui-landing__cta-art { display: none; }

  @media (min-width: 60rem) {
    .ui-landing__cta { min-height: 15rem; display: flex; align-items: center; }
    .ui-landing__cta-text { max-width: 58%; }
    .ui-landing__cta-art { display: block; position: absolute; top: 0; right: 0; bottom: 0; width: 38%; }
  }

  .ui-landing__mini {
    position: absolute;
    width: 11rem;
    min-height: 0;
    padding: var(--p-space-3) var(--p-space-4);
    gap: var(--p-space-2);
    --cover-title-size: 1.45rem;
    box-shadow: 0 1rem 2rem -1rem rgba(29, 25, 51, 0.35);
    transition: opacity 0.7s var(--ease), translate 0.7s var(--ease);
    animation: ui-landing-float 7s ease-in-out infinite;
  }

  .ui-landing__mini .ui-cover__eyebrow { font-size: var(--text-xs); }
  .ui-landing__mini--a { --cover-bg: #0f766e; top: 12%; right: 44%; rotate: -8deg; animation-delay: -1s; }
  .ui-landing__mini--b { --cover-bg: #b45309; top: 50%; right: 34%; rotate: 5deg; transition-delay: 0.2s; animation-delay: -3.5s; }
  .ui-landing__mini--c { top: 22%; right: 6%; rotate: -2deg; transition-delay: 0.4s; animation-delay: -5s; }

  .ui-landing__cta-art .ui-landing__spark--dot { top: 10%; right: 8%; }
  .ui-landing__cta-art .ui-landing__spark--plus { bottom: 14%; right: 12%; }
  .ui-landing__cta-art .ui-landing__spark--ticket { bottom: 10%; right: 60%; }

  .ui-landing.is-animated .ui-landing__cta-art:not(.is-in) .ui-landing__mini { opacity: 0; translate: 0 1.25rem; }

  .ui-profile__head { display: flex; align-items: center; gap: var(--p-space-4); }
  .ui-profile__logo { width: 4.5rem; height: 4.5rem; border-radius: var(--radius-card); object-fit: cover; background: var(--color-sunken); flex-shrink: 0; }
}

@layer utilities {
  /* The first thing a keyboard reaches: jumps past the header to #main.
     Off-screen until focused, then shown over the page's top-left corner. */
  .ui-skip {
    position: absolute;
    top: var(--p-space-2);
    left: var(--p-space-2);
    z-index: 1000;
    padding: var(--p-space-2) var(--p-space-4);
    border-radius: var(--p-radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: 0 0 0 2px var(--color-brand);
    transform: translateY(calc(-100% - var(--p-space-4)));
  }

  .ui-skip:focus { transform: none; }

  /* Read by screen readers, not shown. */
  .ui-visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* ---- Pop-ups the scripts open (events/js/modal-shim.js), used by the confirm dialog and the builders' pickers ---------------------------- */

  .ev-modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    padding: var(--p-space-4);
    background: color-mix(in srgb, var(--color-heading) 45%, transparent);
  }

  .ev-modal .modal-dialog { width: min(32rem, 100%); }

  .ev-modal .modal-content {
    display: flex;
    flex-direction: column;
    border-radius: var(--radius-card, 1rem);
    background: var(--color-surface);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.18);
  }

  .ev-modal .modal-header,
  .ev-modal .modal-body,
  .ev-modal .modal-footer { padding: var(--p-space-4) var(--p-space-5); }

  .ev-modal .modal-header { display: flex; align-items: center; justify-content: space-between; border-bottom: var(--divider); }
  .ev-modal .modal-title { margin: 0; font-size: var(--text-lg); color: var(--color-heading); }
  .ev-modal .modal-body { display: flex; flex-direction: column; }
  .ev-modal .modal-footer { display: flex; justify-content: flex-end; gap: var(--p-space-2); border-top: var(--divider); }

  .ev-modal .btn-close { width: 2rem; height: 2rem; border: 0; border-radius: 50%; background: transparent; color: var(--color-muted); font-size: 1.3rem; cursor: pointer; }
  .ev-modal .btn-close::before { content: "×"; }
  .ev-modal .btn-close:hover { background: var(--color-sunken); color: var(--color-heading); }

  /* ---- Live's own footer (ui/_footer.html) ------------------------------------ */

  .ui-foot {
    display: flex;
    flex-direction: column;
    gap: var(--p-space-5);
    margin-top: auto;
    padding: var(--p-space-7) var(--space-gutter) var(--p-space-6);
    border-top: 1px solid var(--color-line);
    background: var(--color-surface-sunken, var(--color-surface));
    font-size: var(--text-sm);
  }

  .ui-foot a { color: var(--color-muted); text-decoration: none; }
  .ui-foot a:hover { color: var(--color-heading); text-decoration: underline; }

  .ui-foot__grid {
    display: grid;
    gap: var(--p-space-5);
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
  }

  .ui-foot__about { grid-column: span 2; max-width: 24rem; }

  .ui-foot__brand { display: flex; align-items: center; gap: var(--p-space-2); color: var(--color-heading); }
  .ui-foot__name { font-size: var(--text-base); }
  .ui-foot__mark { border-radius: 0.35rem; }

  .ui-foot__social {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    border: 1px solid var(--color-line);
    border-radius: 50%;
  }

  .ui-foot__base {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--p-space-2);
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
    padding-top: var(--p-space-4);
    border-top: 1px solid var(--color-line);
    color: var(--color-muted);
  }

  /* Signed in: one quiet row that never competes with the page. */
  .ui-foot--quiet {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--p-space-3);
    padding-block: var(--p-space-4);
    color: var(--color-muted);
  }

  @media (max-width: 47.99rem) {
    .ui-foot__about { grid-column: 1 / -1; }
  }

  /* ---- A Space's entrance (docs/PLAN-spaces.md E.2) ---------------- */

  /* An entrance sign, not a hero. Without a picture it is simply a heading,
     so a Space is never waiting on artwork to be worth visiting. */
  .ui-space__banner {
    display: grid;
    gap: var(--p-space-2);
  }

  .ui-space__banner--pictured {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-radius: var(--p-radius-lg);
    min-height: 12rem;
    align-content: end;
    padding: var(--p-space-5);
  }

  .ui-space__banner-img {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* The name must stay readable over whatever photograph the banner is, so the
     scrim belongs to the component rather than to whoever remembers it. */
  .ui-space__banner--pictured::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(to top, rgb(0 0 0 / 0.72), rgb(0 0 0 / 0.3) 55%, rgb(0 0 0 / 0.1));
  }

  .ui-space__banner--pictured .ui-space__banner-text { color: #fff; }
  .ui-space__banner--pictured .ui-title,
  .ui-space__banner--pictured .ui-lede { color: inherit; }

  @media (min-width: 48rem) {
    .ui-space__banner--pictured {
      min-height: 16rem;
      padding: var(--p-space-6);
    }
  }

  /* ---- A Space's banner, the billboard (docs/PLAN-space-banner.md E) ---- */

  /* Without JavaScript: a strip of every slide that scrolls sideways, so each
     one is reachable. banner.js adds .is-enhanced and shows one at a time. */
  .ui-banner {
    position: relative;
    border-radius: var(--p-radius-lg);
    overflow: hidden;
    background: var(--color-brand);
  }

  .ui-banner__track {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 100%;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
  }

  .ui-banner__slide {
    position: relative;
    isolation: isolate;
    display: grid;
    align-content: end;
    scroll-snap-align: start;
    min-height: max(22rem, min(125vw, 34rem));
    padding: var(--p-space-5);
    padding-bottom: calc(var(--p-space-5) + 2.75rem); /* room for the controls */
    color: #fff;
    background: linear-gradient(135deg, var(--color-brand), var(--color-accent));
  }

  @media (min-width: 48rem) {
    .ui-banner__slide {
      min-height: clamp(20rem, 42vw, 35rem);
      padding: var(--p-space-6);
      padding-bottom: calc(var(--p-space-6) + 2.75rem);
    }
  }

  .ui-banner__media {
    position: absolute;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* A clip (banner.js) sits over its still and fades in once it is actually
     playing, so a slow or failed load never shows as a black box. */
  .ui-banner__video {
    opacity: 0;
    transition: opacity var(--p-duration-slow, 400ms) ease;
  }

  .ui-banner__slide.is-playing .ui-banner__video {
    opacity: 1;
  }

  @media (prefers-reduced-motion: reduce) {
    .ui-banner__video {
      display: none;
    }
  }

  /* Text over any picture meets contrast because the scrim is the component's. */
  .ui-banner__slide--pictured::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(to top, rgb(0 0 0 / 0.78), rgb(0 0 0 / 0.35) 55%, rgb(0 0 0 / 0.05));
  }

  .ui-banner__text {
    display: grid;
    gap: var(--p-space-2);
    max-width: 40rem;
  }

  .ui-banner__label {
    justify-self: start;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    background: rgb(255 255 255 / 0.18);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: var(--label-tracking);
    text-transform: var(--label-case);
  }

  .ui-banner__headline {
    margin: 0;
    font-size: clamp(1.75rem, 5vw, 3.25rem);
    line-height: 1.05;
    font-weight: 600;
    color: inherit;
    text-wrap: balance;
  }

  .ui-banner__subline,
  .ui-banner__meta {
    margin: 0;
    font-size: clamp(1rem, 2vw, 1.25rem);
    color: rgb(255 255 255 / 0.92);
  }

  .ui-banner__meta { font-weight: 600; }
  /* White, so the button reads on the brand gradient and on any photograph alike. */
  .ui-banner__cta,
  .ui-banner__cta:hover {
    justify-self: start;
    margin-top: var(--p-space-2);
    background: #fff;
    color: var(--color-brand);
    border-color: #fff;
  }

  .ui-banner__cta:hover { background: rgb(255 255 255 / 0.9); }

  .ui-banner.is-enhanced .ui-banner__track {
    display: grid;
    grid-auto-flow: row;
    overflow: hidden;
  }

  /* Every slide in the same cell, so the banner keeps the tallest one's height and nothing jumps. */
  .ui-banner.is-enhanced .ui-banner__slide {
    grid-area: 1 / 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--p-duration-slow, 400ms) ease, visibility 0s linear var(--p-duration-slow, 400ms);
  }

  .ui-banner.is-enhanced .ui-banner__slide.is-current {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--p-duration-slow, 400ms) ease;
  }

  @media (prefers-reduced-motion: reduce) {
    .ui-banner.is-enhanced .ui-banner__slide,
    .ui-banner.is-enhanced .ui-banner__slide.is-current { transition: none; }
  }

  .ui-banner__controls {
    position: absolute;
    inset-inline: var(--p-space-4);
    bottom: var(--p-space-3);
    display: flex;
    align-items: center;
    gap: var(--p-space-2);
  }

  .ui-banner__controls[hidden] { display: none; }

  .ui-banner__button {
    display: inline-grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 0;
    border-radius: 999px;
    color: #fff;
    background: rgb(0 0 0 / 0.45);
    /* A ring, so the button's edge shows on a dark picture as well as a light one. */
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.4);
    cursor: pointer;
  }

  .ui-banner__button:hover { background: rgb(0 0 0 / 0.65); }
  .ui-banner__when-paused,
  .is-paused .ui-banner__when-playing { display: none; }
  .is-paused .ui-banner__when-paused { display: contents; }

  .ui-banner__button:focus-visible,
  .ui-banner__dot:focus-visible {
    outline: 3px solid #fff;
    outline-offset: 2px;
  }

  .ui-banner__dots {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-inline: auto;
  }

  /* 24px targets (WCAG 2.5.8) around an 8px dot. */
  .ui-banner__dot {
    width: 1.5rem;
    height: 1.5rem;
    border: 0;
    padding: 0;
    background: transparent;
    cursor: pointer;
    position: relative;
  }

  .ui-banner__dot::before {
    content: "";
    position: absolute;
    inset: 0.5rem;
    border-radius: 999px;
    background: rgb(255 255 255 / 0.5);
  }

  .ui-banner__dot[aria-current="true"]::before { background: #fff; inset: 0.4rem; }
}

@layer components {
  /* ---- Image field ------------------------------------------------------
     An upload that shows the image (ui/_image_field.html, ui/js/image-field.js). */

  .ui-image-field__box {
    display: grid;
    overflow: hidden;
    max-width: 30rem;
    border: 1px solid var(--color-line);
    border-radius: var(--p-radius-lg);
    background: var(--color-surface);
    transition: border-color var(--p-duration-fast, 120ms), background var(--p-duration-fast, 120ms);
  }
  .ui-image-field__box.is-empty { border-style: dashed; border-width: 1.5px; background: var(--color-sunken); }
  .ui-image-field__box.is-dragging { border-color: var(--color-brand); background: var(--color-brand-soft); }
  .ui-image-field__box.is-removing .ui-image-field__img { opacity: 0.3; filter: grayscale(1); }

  .ui-image-field__frame {
    display: grid;
    place-items: center;
    min-height: 7rem;
    padding: var(--p-space-5);
  }
  /* "email": the top of an email - white, the logo centred at the 40px emails draw it. */
  .ui-image-field__frame--email { background: #fff; }
  .ui-image-field__frame--email .ui-image-field__img { height: 40px; width: auto; max-width: 100%; object-fit: contain; }
  .ui-image-field__img { display: block; max-height: 10rem; max-width: 100%; }

  /* "cover": wide, the whole image shown and never cropped - posters and squares included. */
  .ui-image-field__box:has(.ui-image-field__frame--cover) { max-width: none; }
  .ui-image-field__frame--cover { min-height: 12.5rem; padding: 0; background: var(--color-sunken); }
  .ui-image-field__frame--cover .ui-image-field__img { width: 100%; max-height: 19rem; object-fit: contain; }

  .ui-image-field__empty { display: grid; justify-items: center; gap: var(--p-space-2); text-align: center; }
  .ui-image-field__empty .ui-small { display: inline-flex; align-items: center; gap: var(--p-space-2); }
  .ui-image-field__name { font-family: var(--p-face-bricolage), sans-serif; font-weight: 600; font-size: var(--text-xl); color: var(--color-heading); }
  .ui-image-field__choose { color: var(--color-brand); font-weight: 600; text-decoration: underline; cursor: pointer; }

  .ui-image-field__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--p-space-2) var(--p-space-3);
    padding: var(--p-space-3) var(--p-space-4);
    border-top: 1px solid var(--color-line);
    background: var(--color-surface);
  }
  .ui-image-field__meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .ui-image-field__note { margin: 0; color: var(--color-muted); }
  .ui-image-field__note[data-tone="bad"] { color: var(--color-bad); }
  .ui-image-field__note button { margin-left: var(--p-space-1); }

  /* Without the script, the native input is the control: no second "Replace" beside it. */
  .ui-image-field:not(.is-enhanced) [data-image-replace] { display: none; }

  /* Once the script runs, it drives these and they leave the page's flow. */
  .ui-image-field.is-enhanced .ui-image-field__native { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

  /* ---- Picking a day and a time (conversations/public.html) --------- */

  /* The calendar beside the chosen day's times on a desktop, stacked on a
     phone. Choosing a day is the first half of the decision and the times are
     the second, so they sit side by side where there is room for both. */
  .ui-when { display: grid; gap: var(--p-space-4); align-items: start; }

  @media (min-width: 48rem) {
    .ui-when { grid-template-columns: minmax(17rem, 22rem) 1fr; gap: var(--p-space-5); }
  }

  .ui-cal { width: 100%; border-collapse: collapse; table-layout: fixed; }

  .ui-cal th {
    padding-bottom: var(--p-space-1);
    color: var(--color-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: var(--label-case);
    letter-spacing: var(--label-tracking);
  }

  .ui-cal th abbr { text-decoration: none; border: 0; }
  .ui-cal td { padding: 1px; text-align: center; }

  .ui-cal__day {
    position: relative;
    display: grid;
    place-items: center;
    aspect-ratio: 1;
    border-radius: var(--radius-chip);
    color: var(--color-muted);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
  }

  /* A day nobody can book is not a target: no pointer, no hover, and dimmed
     further when it belongs to a neighbouring month. */
  .ui-cal__day.is-away { opacity: 0.35; }

  a.ui-cal__day {
    color: var(--color-heading);
    font-weight: 600;
    border: 1px solid var(--color-line);
  }

  a.ui-cal__day:hover {
    text-decoration: none;
    border-color: var(--color-brand);
    background: var(--color-brand-soft);
    color: var(--color-brand);
  }

  /* The dot the caption explains: a day with time on it, told apart from an
     empty one by more than colour. */
  a.ui-cal__day.is-free::after {
    content: "";
    position: absolute;
    bottom: 0.3rem;
    width: 0.25rem;
    height: 0.25rem;
    border-radius: 50%;
    background: var(--color-brand);
  }

  a.ui-cal__day.is-on,
  a.ui-cal__day.is-on:hover {
    background: var(--color-brand);
    border-color: var(--color-brand);
    color: var(--color-on-brand);
  }

  a.ui-cal__day.is-on::after { background: var(--color-on-brand); }

  /* ---- Where a conversation happens (setup_where.html) ------------- */

  /* The place only matters in person. Written as "hide when online" rather
     than "show when in person" on purpose: where `:has()` is unsupported the
     section simply stays visible, which is how this behaved before, instead
     of disappearing and leaving no way to choose a venue at all. */
  .ui-where:has(input[name="where"][value="online"]:checked) .ui-where__place { display: none; }

  /* By the same token the joining-link note belongs to the online answer. */
  .ui-where:has(input[name="where"][value="in_person"]:checked) .ui-where__online-note { display: none; }
}
