/**
 * The glass surface. Signed off 2026-08-02, rolled out page by page.
 *
 * Everything here is scoped under body.ts-glass-page, which
 * _tubespanner_purple_glass_pages() in the .theme adds by route. Nothing
 * outside a converted page can be touched by this file, which is what makes a
 * page-by-page rollout safe: an unconverted page cannot half-convert.
 *
 * A body class rather than a class in the markup, because there are 75 page
 * templates each carrying its own copy of the dashboard wrappers. Putting it in
 * the markup would mean editing 75 files and would guarantee a half-converted
 * page somewhere. One list in the .theme is the whole record of what is done.
 *
 * See styleguide 04-cards-and-surfaces for the four costs this carries.
 */
/* The field. Without it there is no glass: backdrop-filter blurs what is
   behind it, and a flat tint blurs to itself. Fixed attachment so the wash
   stays put while the content scrolls over it, which is what makes the
   panels read as sitting ON something. */
body.ts-glass-page {
  background:
    radial-gradient(44% 48% at 8% 6%, rgba(103, 44, 114, 0.30), transparent 62%),
    radial-gradient(38% 44% at 90% 12%, rgba(31, 95, 155, 0.26), transparent 64%),
    /* A fourth wash across the middle. Without it the field clusters at the
       corners and goes flat exactly where the largest panel sits, so the
       biggest piece of glass had the least to refract. */
    radial-gradient(52% 40% at 46% 48%, rgba(103, 44, 114, 0.14), transparent 68%),
    radial-gradient(42% 48% at 80% 84%, rgba(23, 128, 85, 0.22), transparent 66%),
    radial-gradient(36% 42% at 20% 92%, rgba(194, 65, 12, 0.14), transparent 66%),
    linear-gradient(158deg, #f7f3fc 0%, #ece5f4 100%);
  background-attachment: fixed;
}

/* The content column starts where the sidebar starts.
 *
 * The floating sidebar sits 12px below the header, from its own margin. The
 * content column sat 16px below it, from #dashaboard-inner-content's padding.
 * Two offsets chosen independently, so the sidebar's top-right corner and the
 * first panel's top-left corner sat 4px apart on every converted page. Small
 * enough to look like a rendering artefact and large enough to see. */
body.ts-glass-page #dashaboard-inner-content {
  padding-top: 12px;
}

/* One inset for every panel.
 *
 * Panel padding across the estate is 12px, 16px, 20/22px and 24px depending
 * on which component you landed in, and a source count finds 41 distinct
 * padding values on panel selectors. The visible consequence is that a card
 * heading starts a different distance from its corner on every page, which
 * reads as several designs sharing a screen.
 *
 * 16px is the value chosen: it is the most common of the four, it is a
 * standard step, and it clears the 14px signature radius so a heading never
 * sits inside the curve. Scoped to glass pages, so nothing unconverted
 * moves. */
body.ts-glass-page .card,
body.ts-glass-page .middle-content-block,
body.ts-glass-page .ts-card {
  padding: 16px;
}

/* .fieldset-wrapper is NOT a panel, which is why listing it above was wrong.
 *
 * It is a wrapper INSIDE a panel: the markup is fieldset.card wrapping
 * .fieldset-wrapper, so the card is already the glass surface and the wrapper
 * was painting solid white straight over it. That is why the membership page
 * read as unconverted even though its card had the glass treatment.
 *
 * So the fix is to stop it painting, not to glass it twice. Its padding stays
 * exactly as it was, because the 50px top is load-bearing: the legend is
 * absolutely positioned at top: 10px inside the card, and that space is what
 * stops the heading landing on the first field.
 *
 * Specificity note: the page rule this overrides is
 * `.sec-user-membership-main .user-form fieldset.card .fieldset-wrapper` at
 * 0,4,1. This selector is 0,4,2, so it wins on element count without naming
 * the page and without !important. */
body.ts-glass-page .user-form fieldset.card .fieldset-wrapper {
  background: transparent;
  border-radius: 0;
}

/* Which surfaces carry the signature, and which do not. TS-5243 items 9+11.
 *
 * QA reported the corner as inconsistent and was right: on the dashboard only
 * 5 surfaces carried it and 41 did not. A signature on one surface in nine is
 * not a signature, it is an exception.
 *
 * The rule, stated so it stops being decided case by case:
 *
 *   TOP-LEVEL PANELS carry it. A panel is a surface that sits directly on the
 *   page field and holds a section of the page.
 *
 *   NESTED TILES do not. The dashboard shortcut tiles are .card-single and
 *   there are 38 of them; a squared corner on each would be noise, and they
 *   already read as grouped because they sit inside a panel.
 *
 * These three are top-level panels that predate the rule and were missed
 * because the panel selector names classes and the estate keeps producing
 * new panel classes. That is the fourth time: .ts-card, .fieldset-wrapper,
 * and now these. */
body.ts-glass-page .ts-kim-panel,
body.ts-glass-page .ts-donext-compact,
body.ts-glass-page .ts-audit-preview {
  border-radius: 14px 14px 14px 3px;
}

/* Panels. The squared corner is the signature: 14px on three corners, 3px
   bottom-left. Glass on its own is a trend; glass with a silhouette is ours. */
body.ts-glass-page .card,
body.ts-glass-page .middle-content-block,
/* .ts-card is a newer component vocabulary (connected-accounts.css,
   social-composer.css) that does not use .card at all. Without it listed here
   the connected-accounts page kept solid white panels with a uniform 12px
   radius on a glass field, so it read as a page the conversion had missed.
   Worth knowing the estate has three button-and-card vocabularies: Bootstrap
   .btn/.card, Drupal core .button/.button--primary, and these ts-* classes. */
body.ts-glass-page .ts-card {
  background: rgba(255, 255, 255, 0.62);
  -webkit-backdrop-filter: blur(20px) saturate(1.5);
  backdrop-filter: blur(20px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: 14px 14px 14px 3px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 12px 28px -16px rgba(62, 33, 85, 0.40);
}

/* The eyebrow darkens on glass, and this is a real concession rather than a
   tweak. Measured against the darkest point the field puts under a panel, the
   house eyebrow lavender lands at 4.25 against a 4.5 threshold. Opaque enough
   to rescue it is 80% white, which is not glass. So on this surface, and only
   on this surface, the eyebrow takes the body ink. */
body.ts-glass-page .ts-toolbar-eyebrow,
body.ts-glass-page .ts-audit-headline__eyebrow,
body.ts-glass-page .card-block-title {
  color: #5b5872;
}

/* Small surfaces stay opaque. A 46px shortcut row gains nothing from a live
   blur and costs a compositing pass each, and there are dozens of them. Glass
   belongs on panels.

   .ts-fact-row is the same case on /dashboard/user-facts: list rows inside a
   panel. Listed here rather than left to its own stylesheet so it picks up the
   translucency the surrounding panel expects, which is the fifth time a new
   nested-row class has had to be added by name. */
body.ts-glass-page .toolbox-main--row,
body.ts-glass-page .ts-fact-row {
  background: rgba(255, 255, 255, 0.78);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-radius: 12px 12px 12px 3px;
}

/* Nested panels are not panels.
 *
 * .middle-content-block is a standalone panel on the dashboard, which is why
 * it is in the frosted list above. On the audit page it is a section INSIDE a
 * card, so it was drawing a second full panel within the first: its own blur,
 * border, radius and shadow, a box in a box. The nested rule only covered
 * .card .card and missed it.
 *
 * Inside a panel it is a content region and nothing more. Everything that
 * makes it look like a surface comes off, not just the blur, because a border
 * and a shadow read as an edge whether or not the fill is frosted. */
body.ts-glass-page .card .middle-content-block {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* A genuinely nested card keeps a faint fill so it still reads as a distinct
   block, but loses the frost and the edge for the same reasons. */
body.ts-glass-page .card .card {
  background: rgba(255, 255, 255, 0.5);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  box-shadow: none;
}

/* Where a browser cannot do backdrop-filter, the panels become plainly opaque
   rather than washed-out and unreadable. Contrast has to hold without the
   effect, not because of it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  body.ts-glass-page .card,
  body.ts-glass-page .middle-content-block {
    background: rgba(255, 255, 255, 0.92);
  }
}

/* The channel health headline was designed as a full-width panel on the audit
   page. On the dashboard it sits in a half-width card, where its two columns
   plus the meta column collapse into three cramped strips and the detail text
   wraps every three words. In this context it stacks: the measure above, the
   reading below, each with the full width of the card.

   Scoped to the dashboard preview rather than fixed with a viewport media
   query, because the panel's problem is the width of its CONTAINER, not the
   width of the screen. */
.ts-audit-preview .ts-audit-headline {
  grid-template-columns: 1fr;
  gap: 0.5rem 0;
}
.ts-audit-preview .ts-audit-headline__measure {
  flex-direction: row;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* The content wrapper no longer paints anything. The field moved up to body so
   it runs behind the sidebar as well, which is what gives a floating sidebar
   something to refract. Without it the sidebar would be frosting a flat white
   strip, which looks like a rendering fault rather than like glass. */
body.ts-glass-page .dashaboard-content {
  background: none;
}

/* The floating sidebar.
 *
 * It keeps its 250px footprint so the content beside it does not move; only
 * the visible panel is inset. Same 14/3 corner as every other panel, so it
 * reads as the largest piece of glass on the page rather than as chrome that
 * happens to be translucent.
 *
 * This reverses the position taken while the estate was flat, where the
 * sidebar stayed solid so it would not drift against unconverted pages. That
 * reasoning still holds for pages NOT on the list, and this rule cannot reach
 * them. */
body.ts-glass-page .app-sidebar {
  /* Inset by margin, not by setting top. The header is fixed and its offset
     varies with the viewer: 0 for a normal user, 40px with the admin toolbar,
     80px with its tray open. A hardcoded top clipped the first nav item under
     the header for admins only, which is a fault that never shows up in
     testing unless you happen to be logged in as one. Margins shift the panel
     by the same amount wherever the theme has already put it. */
  margin: 12px 0 0 12px;
  bottom: 12px;
  /* 226px left the longest label, "Feedback and support", 2px short of
     fitting on one line, so it wrapped. TS-5243 item 12. Widened rather than
     de-padded because the active band already uses a 22px inset and dropping
     the base below that would make an item jump sideways as you select it.
     10px of headroom rather than the 2px measured, so a different font
     rendering does not put it back. */
  width: 236px;
  height: auto;
  /* The base rule sets min-height: calc(100vh - 56px) to pair with its own
     top: 56px; bottom: 0 (style.css:335). This rule insets the panel by a
     12px top margin and bottom: 12px, so the slot it actually occupies is
     100vh - 80px, and the un-overridden minimum forced the box 24px taller
     than that. The overspill fell below the fold, which took the 12px bottom
     gap and the rounded bottom corner off-screen and left the panel reading
     as square and flush to the edge, and it made the content column 24px
     taller than the visible area so .sidebar-list-item began scrolling before
     the menu was genuinely too long. Larger still for an admin, whose header
     offset grows by the toolbar while the minimum does not. Zero lets the
     margin and bottom govern the height on their own. */
  min-height: 0;
  background: rgba(255, 255, 255, 0.62);
  -webkit-backdrop-filter: blur(20px) saturate(1.5);
  backdrop-filter: blur(20px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: 14px 14px 14px 3px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 16px 34px -20px rgba(62, 33, 85, 0.45);
  /* Clip, do not scroll.
   *
   * This was overflow-y: auto, to stop a long menu spilling past the rounded
   * corner. But .sidebar-list-item inside it has scrolled on its own since
   * style.css:334, so that made the panel a scroller wrapped around a
   * scroller: on any page carrying extra sidebar blocks (the help-support
   * menu, the editor thumbnail panel) the shell overflowed too and drew a
   * second bar beside the inner one.
   *
   * hidden gets the clipping the radius needs without a second scroller, and
   * min-height: 0 lets the list absorb the shrink so .sidebar-footer stays
   * reachable rather than being clipped away. Verified down to a 400px
   * viewport with every submenu open. */
  overflow: hidden;
}

body.ts-glass-page .app-sidebar .sidebar-list-item {
  min-height: 0;
  /* This list is the one thing inside the panel that is allowed to scroll, so
     on a short viewport its scrollbar is drawn against the glass. Left at the
     platform default it renders as an opaque full-width bar hard against the
     rounded edge. Same thin purple treatment the Kim log uses
     (custom-style.css:3837) so the two agree. */
  scrollbar-width: thin;
  scrollbar-color: rgba(62, 33, 85, .25) transparent;
}

body.ts-glass-page .app-sidebar .sidebar-list-item::-webkit-scrollbar {
  width: 6px;
}

body.ts-glass-page .app-sidebar .sidebar-list-item::-webkit-scrollbar-track {
  background: transparent;
}

body.ts-glass-page .app-sidebar .sidebar-list-item::-webkit-scrollbar-thumb {
  background: rgba(62, 33, 85, .25);
  border-radius: 3px;
}

/* The active item.
 *
 * It was a pale well: rgba(255,255,255,.72) over the frosted panel, which
 * separates from that panel by 1.17:1. The threshold for a non-text state
 * indicator is 3:1, so it was not merely plain, it was barely perceivable as
 * a state at all. A brand tint does not fix that either, because any wash
 * over frosted glass barely shifts the surface: 10% measured 1.17 and 16%
 * measured 1.29. Only a solid fill clears it, at 7.09.
 *
 * So the fill is solid brand, which is not a new idea here: the next-steps
 * panel already uses a filled brand block with white text for its dominant
 * state. White on #712F79 holds 8.81.
 *
 * It bleeds to the left edge of the panel rather than floating inside it. A
 * detached rectangle in the middle of a list says "this one is different"; a
 * band anchored to the frame says "this is where you are", which is the thing
 * a nav state is actually for. The lost left margin comes back as padding so
 * the label does not move.
 *
 * Square on the left, rounded on the right. That is a relative of the house
 * 14/3 corner rather than the same shape, and it is deliberate: the signature
 * belongs to cards and panels, and this is a band welded to an edge.
 *
 * What this replaces: style.css gives every sidebar link
 * `border-left: 3px solid transparent` and turns it purple when active, which
 * is the left accent rail this direction rejected. The transparent border
 * stays so nothing shifts by three pixels when an item becomes active. */
body.ts-glass-page .app-sidebar .list-unstyled li.active a,
body.ts-glass-page .app-sidebar .sidebar-list-item a.is-active {
  background: var(--primary-color, #672c72);
  color: #fff;
  font-weight: 600;
  border-left-color: transparent;
  border-radius: 0 12px 12px 0 !important;
  margin-left: 0;
  margin-right: 10px;
  padding-left: 10px;
  /* The link computes an explicit 224px width, the full inner width of the
     panel, so a right margin alone pushed it past the edge instead of
     insetting it and the band touched both sides. */
  width: auto;
}

/* Child items reach the edge too.
 *
 * Nested lists indent by 20px on the ul, so a child band stopped 21px short of
 * the panel and its square left edge floated in mid air. Square reads as
 * deliberate only when it is welded to something; detached it just looks
 * cropped, which is exactly how it was described.
 *
 * The negative margin cancels the ul's left padding so the band starts on the
 * panel edge, giving every band in the list one silhouette, and the whole label
 * indent moves into padding-left, kept a step wider than the parent rule's so
 * children still read as children.
 *
 * Retune the two together, never the margin alone: it is what holds the band on
 * the edge. */
body.ts-glass-page .app-sidebar .btn-toggle-nav li.active a {
  margin-left: -20px;
  padding-left: 30px;
}

/* The icon rides the fill.
 *
 * These are <img> files, not inline SVG: each item ships deselect, sm and
 * active variants and toggles which one is displayed. The active variant is
 * the brand purple, because it was built for a pale well, and on a brand fill
 * it would have been invisible. Nothing about a stroke or fill rule can reach
 * inside an <img>.
 *
 * brightness(0) turns the artwork solid black whatever colour it started,
 * then invert(1) makes it white. It works on any of the three variants, so it
 * cannot be broken by the artwork changing. */
body.ts-glass-page .app-sidebar .list-unstyled li.active a .menuicon img {
  filter: brightness(0) invert(1);
}

/* The sidebar footer card sits inside an already-frosted panel, so it is not
   frosted again for the same reason nested cards are not. */
body.ts-glass-page .app-sidebar .sidebar-footer .card {
  background: rgba(255, 255, 255, 0.5);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  body.ts-glass-page .app-sidebar {
    background: rgba(255, 255, 255, 0.94);
  }
}

/* Sidebar text, re-inked for the surface.
 *
 * This is cost three from the style guide arriving in practice. Measured
 * against the darkest point the field puts under the panel, which composites
 * to rgb(236,228,238) at 62% white, the sidebar inks that were fine on solid
 * white no longer hold:
 *
 *   nav link      #6b6880 at 16px   4.31:1   fails 4.5
 *   section label #858b98 at 14px   2.75:1   fails badly
 *   footer text   #858b98 at 14px   2.75:1   fails badly
 *
 * 16px is not large text, so 4.5 is the bar for all three. #5b5872 measures
 * 5.48:1 on the same composite and clears it.
 *
 * The labels take the same ink as the links rather than a lighter one, because
 * a lighter ink is what failed. They still read as secondary: they are smaller,
 * uppercase and letterspaced already, so the hierarchy was never carried by
 * colour alone and does not need to be. */
body.ts-glass-page .app-sidebar,
body.ts-glass-page .app-sidebar .sidebar-list-item a,
body.ts-glass-page .app-sidebar .sidebar-footer {
  color: #5b5872;
}

/* Muted text, re-stepped for this surface.
 *
 * The house muted ink is --ts-color-muted (#6b6880), calibrated at 5.36:1 on
 * white and 4.69:1 on the page tint. Glass is a darker backdrop than either:
 * against the darkest point the field puts under a panel, compositing to
 * rgb(236,228,238) at 62% white, the same ink measures 4.31:1 and fails.
 *
 * That is not a fault in the token. It is the third cost of this surface
 * arriving as a general rule rather than one page at a time: every page we
 * convert would otherwise need its own contrast patch, and the audit page
 * already got one.
 *
 * One step darker for converted pages only. #5b5872 measures 5.48:1 on the
 * same composite. Unconverted pages keep the calibrated token, which is
 * correct for the surface they are actually on.
 *
 * The three declarations cover the three ways this grey reaches the page: the
 * theme variable, the design token, and Bootstrap's .text-muted utility, which
 * needs !important because the utilities are !important. Literal #858b98 is
 * still scattered through custom-style.css and calendar-custom.css, which is
 * TS-5170 and not fixable from here. */
body.ts-glass-page {
  --paragraph: #5b5872;
  --ts-color-muted: #5b5872;
}

body.ts-glass-page .text-muted {
  color: #5b5872 !important;
}

/* The literal, where it lands on body copy inside a panel.
 *
 * `.card p` was too broad and it broke the shortcut rows. Those rows ARE
 * cards, their label is a <p>, and they fill with brand purple on hover and
 * inherit white text from it. Setting the colour on the <p> directly beat that
 * inherited white, because inheritance loses to any rule that names the
 * element, so the label stayed #5b5872 on a #712F79 fill: 1.29:1, which is
 * what "impossible to read on hover" was.
 *
 * The rule now excludes anything that fills on interaction. Body copy in a
 * content card is a different thing from a label on a control, and only the
 * first one wanted re-inking. */
body.ts-glass-page .card:not(.toolbox-main) p,
body.ts-glass-page .card:not(.toolbox-main) .card-text {
  color: #5b5872;
}

/* Belt and braces on the row that exposed it: whatever the muted rule does
   later, a label on a filled control takes the fill's ink. 8.81:1. */
body.ts-glass-page .sec-toolbar-main .toolbox-main:hover p {
  color: var(--bs-white, #fff);
}

/* Mobile: the floating panel has to clear the viewport by its margin too.
 *
 * TS-5243 item 10. responsive.css hides the sidebar below 768px with
 * translateX(-100%), which shifts an element by its OWN WIDTH. That is exactly
 * right for a sidebar flush against the edge, which is what it was before this
 * conversion. The floating panel adds a 12px left margin, so -100% leaves
 * precisely that 12px on screen as a sliver of frosted glass down the side of
 * every mobile page.
 *
 * Worth recording how nearly this was misdiagnosed, and the real reason.
 *
 * Driving the page through automation, the transform appeared stuck at its
 * old value whenever a class was toggled, which looks exactly like the
 * off-canvas rule failing. It is not. CSS TRANSITIONS DO NOT ADVANCE IN THAT
 * CONTEXT AT ALL: a plain opacity 1 to 0 over 200ms still reads 1.0 after
 * 900ms. Any transitioned property therefore reports its PREVIOUS value
 * forever, and every "the state indicator is stuck" reading taken that way is
 * an artefact rather than a defect.
 *
 * Two consequences worth carrying:
 *
 *   Measure a transitioned property either on a FRESH LOAD, where there is no
 *   animation in flight, or with transition: none injected first. Both were
 *   used here.
 *
 *   The 12px sliver below is real, because it was measured on a fresh load
 *   where the transform already sat at its final value. The open state is
 *   correct too, confirmed with transitions disabled: the panel returns to
 *   left 12, right 248.
 */
@media screen and (max-width: 767px) {
  body.ts-glass-page .app-sidebar {
    transform: translateX(calc(-100% - 12px));
  }
  /* The open state still comes all the way back in. */
  body.ts-glass-page.open-menu .app-sidebar,
  .open-menu body.ts-glass-page .app-sidebar,
  body.open-menu.ts-glass-page .app-sidebar {
    transform: translateX(0);
  }
}

/* ---------------------------------------------------------------------------
 * Event form fields, matched to the social post form.
 *
 * Scoped on the form's own classes, not a body class. Drupal emits
 * node-event-form on the add route and node-event-edit-form on the edit route
 * (both alongside node-form), and those are the only classes that reliably
 * appear on each.
 *
 * The social post equivalents in custom-style.css are each scoped
 * `.node-add-social_post X, .page-node-type-social-post X`. Only the second
 * half of those ever matches. Nothing in this codebase emits
 * node-add-social_post, and page-node-type-* comes from the node module's
 * preprocess_html, which reads a node off the route and so cannot fire on any
 * add form. Those rules are therefore live on the social post EDIT form and
 * dead on its create form. Copying that scoping would have copied the bug, so
 * what is reproduced here is the intent.
 *
 * Not carried over, for want of anything to style: the card-social-bio,
 * #field-preview-text-wrapper and #tsSocialPreviewTab rules are that form's
 * bespoke preview UI, and the image-widget rule needs an image field, which
 * event has not got.
 * ------------------------------------------------------------------------- */
.node-event-form,
.node-event-edit-form {
  border: 0;
  margin: 0 !important;
}

/* Fieldset legends read as field labels. field_link renders as a fieldset, and
   each datetime field wraps its date and time inputs in one. */
.node-event-form .fieldset-legend,
.node-event-edit-form .fieldset-legend {
  font-size: 14px;
  font-weight: 700;
  color: var(--title);
  margin-bottom: 4px;
}

/* Date and time share one row instead of stacking. field_event_start_date and
 * field_event_end_date are both datetime, so this is the visible difference.
 *
 * gap, not the container margin: 0 -10px / item padding: 0 10px pattern this
 * was copied from. That pattern only lines up if both halves apply, and the
 * description is a sibling div in the same wrapper, so it kept landing 10px
 * off the inputs however the compensating value was tuned. With gap nothing
 * needs compensating: the inputs and the description both sit flat on the
 * wrapper's left edge, so they align by construction rather than arithmetic.
 *
 * :not(.description) so the help text is not made a flex row of its words. */
.node-event-form .field--type-datetime fieldset .fieldset-wrapper > div:not(.description),
.node-event-edit-form .field--type-datetime fieldset .fieldset-wrapper > div:not(.description) {
  display: flex;
  gap: 20px;
  margin: 0;
}

.node-event-form .field--type-datetime fieldset .fieldset-wrapper > div:not(.description) .js-form-item,
.node-event-edit-form .field--type-datetime fieldset .fieldset-wrapper > div:not(.description) .js-form-item {
  flex: 1;
  padding: 0;
  margin-left: 0;
  margin-right: 0;
}

/* Field descriptions have to be un-hidden before they can be aligned.
 *
 * calendar-custom.css:448 carries a bare `.description { display: none; }`.
 * It sits under a "script modal" comment, so it reads as a rule that was meant
 * to be scoped and was not, and it hides every Drupal field description on the
 * site. The datetime help text only ever appeared because the #add-event rule
 * set display:flex on the same element and happened to outrank it; excluding
 * the description from that rule, which is what stopped it being dragged 10px
 * left, also took away the thing that was accidentally revealing it.
 *
 * So this says display outright rather than depending on another rule's side
 * effect. Matched on data-drupal-field-elements, which Drupal puts on FIELD
 * descriptions only: a bare .description would also reveal core's widget help,
 * such as the link widget's paragraph about /node/add and <front>, which has
 * been hidden all along and is not ours to surface. Scoped to the event form:
 * un-hiding descriptions site-wide is a much larger change and wants a ticket. */
.node-event-form .description,
.node-event-edit-form .description {
  display: block;
}

/* Except core's link-widget help.
 *
 * data-drupal-field-elements looked like the way to tell our field
 * descriptions from core's widget ones, but it does not: checked against the
 * rendered form, start date, end date and link carry it, while the colour and
 * public descriptions - also ours - do not, because a single-property widget
 * hangs the field description on the widget element instead of a field
 * wrapper. Selecting on that attribute would have re-hidden two of the six.
 *
 * So un-hide the lot and name the exception instead. This is the link widget's
 * own paragraph about /node/add, <front> and <nolink>, which has been hidden by
 * calendar-custom.css all along, is longer than the field it explains, and is
 * redundant now the field describes itself. */
.node-event-form .field--type-link [id$="-uri--description"],
.node-event-edit-form .field--type-link [id$="-uri--description"] {
  display: none;
}

/* The link field's description must not become a third column.
 *
 * Unlike the datetime fields, where the flex container is a div INSIDE
 * .fieldset-wrapper, #add-event makes .fieldset-wrapper itself the flex
 * container for a link field. The description is a child of that wrapper, so
 * un-hiding it put it in the row beside URL and Link text. Given a full-width
 * basis it wraps onto its own line under both, which needs the flex-wrap added
 * to that rule in custom-style.css. */
.node-event-form .field--type-link .fieldset-wrapper > .description,
.node-event-edit-form .field--type-link .fieldset-wrapper > .description {
  flex: 0 0 100%;
  /* 10px, not 0: this wrapper still uses the compensating pattern from
     custom-style.css, margin: 0 -10px on the container against padding: 0 10px
     on each form item. The description needs the same 10px to land on the same
     left edge as the URL input; at 0 it sat 10px further left. */
  padding-left: 10px;
  margin-left: 0;
}

.node-event-form .field--type-datetime fieldset .fieldset-wrapper > div.description,
.node-event-edit-form .field--type-datetime fieldset .fieldset-wrapper > div.description {
  margin-left: 0;
  padding-left: 0;
}

/* field_public and field_google_api are booleans; same breathing room the
   social post checkboxes get. */
.node-event-form .js-form-item.form-item.js-form-type-checkbox,
.node-event-edit-form .js-form-item.form-item.js-form-type-checkbox {
  margin-right: 25px;
  margin-bottom: 10px;
}

/* Start and end date share one row.
 *
 * The two fields are adjacent siblings among the form's block-level field
 * wrappers (weights 2 and 3 in core.entity_form_display.node.event.default),
 * with no container of their own, so they are set inline-block rather than
 * flexed: flexing would need a wrapper that does not exist, and adding one
 * from a form alter would change markup other CSS already targets.
 *
 * The widths leave 10px of slack (2 x 15px removed, 20px given back as the
 * gutter). That slack absorbs the whitespace text node browsers render between
 * two inline-block siblings, which is font-size dependent and cannot be sized
 * exactly. Without it the pair wraps at some font sizes and not others.
 *
 * If the field order changes, check this still pairs the intended two. */
.node-event-form .field--name-field-event-start-date,
.node-event-form .field--name-field-event-end-date,
.node-event-edit-form .field--name-field-event-start-date,
.node-event-edit-form .field--name-field-event-end-date {
  display: inline-block;
  vertical-align: top;
  width: calc(50% - 15px);
}

.node-event-form .field--name-field-event-start-date,
.node-event-edit-form .field--name-field-event-start-date {
  margin-right: 20px;
}

/* Two date fields side by side stop fitting well before the sidebar collapses,
   so they stack on their own breakpoint rather than the layout's. */
@media (max-width: 991px) {
  .node-event-form .field--name-field-event-start-date,
  .node-event-form .field--name-field-event-end-date,
  .node-event-edit-form .field--name-field-event-start-date,
  .node-event-edit-form .field--name-field-event-end-date {
    display: block;
    width: 100%;
    margin-right: 0;
  }
}

/* The body field's description is not a .description.
 *
 * text_format renders its help text as
 * <div class="js-text-full text-full" id="edit-body-0--description">, with no
 * .description class at all, so every rule scoped to .description misses it and
 * it was the one description not sharing the field's left edge.
 *
 * Matched on the id suffix, which Drupal builds from the element id and is the
 * only stable handle the element offers. Scoped to the body field so it cannot
 * reach the datetime descriptions, which are already handled above. */
.node-event-form .field--name-body [id$="--description"],
.node-event-edit-form .field--name-body [id$="--description"] {
  display: block;
  margin-left: 0;
  padding-left: 0;
}
