/* The builder's output.
   Every color and measurement comes from the theme variables, so a page built
   here inherits the site's palette rather than carrying its own. */

.ds-row{ width:100% }
/* Three separate measurements, three separate variables.

   The gap between one column and the next is Between columns. The space
   outside the columns, holding the page off the edge of the screen, is the
   page's Gutter. They were one variable, so closing the space between two
   cards meant dragging the whole page inwards.

   The space under a stacked element is a third thing again, and lives on
   .ds-el rather than here. */
.ds-row-in{ display:grid; grid-template-columns:repeat(12,1fr);
  /* Space between columns, and only between them.
   *
   * This was a margin on every column, which put the figure on the outside of
   * the first and last ones as well - so the content of a row no longer lined
   * up with the heading above it, and three separate rules had to subtract it
   * back out of the row's gutter to hide that. A grid gap is internal by
   * definition: it exists between tracks and cannot appear at the ends, which
   * is what the setting was always for. */
  column-gap:var(--ds-col-mx,0px);
  row-gap:var(--ds-el-gap,20px);
  width:100%; max-width:var(--ds-max,1200px); margin-inline:auto;
  /* The column margin taken back off the outside.
   *
   * Every column carries its own left and right margin, which is what puts
   * space between two of them. On the first and last column that margin lands
   * on the outside of the row, where it stacks on top of the page gutter - so
   * a gutter of 50 with a column margin of 20 started the content at 70 and
   * nothing on the page was flush with anything else.
   *
   * The same answer WPBakery has always used: the gutter is the space the
   * columns live inside, so the row gives back exactly what the columns take.
   * Never negative - a column margin wider than the gutter would otherwise
   * pull the content off the side of the page. */
  padding-inline:var(--ds-gutter,24px) }

/* No margin of its own.
   The space between columns is the row's column-gap. A margin puts the figure
   on the outside of the first and last column too, which is exactly what it
   should not do - the setting is for the space between them. */
.ds-col{ grid-column:span 12; min-width:0 }
/* Every width a split can name, not just the ones that were needed the day
   this was written. Six of the twelve existed, so any split using a 2 or a 10 -
   six even columns, a narrow rail, three weighted - had no width rule at all
   and its columns fell back to one track each. */
.ds-col-12{ grid-column:span 12 }
.ds-col-11{ grid-column:span 11 }
.ds-col-10{ grid-column:span 10 }
.ds-col-9 { grid-column:span 9 }
.ds-col-8 { grid-column:span 8 }
.ds-col-7 { grid-column:span 7 }
.ds-col-6 { grid-column:span 6 }
.ds-col-5 { grid-column:span 5 }
.ds-col-4 { grid-column:span 4 }
.ds-col-3 { grid-column:span 3 }
.ds-col-2 { grid-column:span 2 }
.ds-col-1 { grid-column:span 1 }

/* One breakpoint, and everything becomes a single column. Two-thirds of a
   phone screen is not a column, it is a line of two words. */
@media (max-width:781px){
  .ds-row-in{ grid-template-columns:1fr }
  .ds-col,[class*="ds-col-"]{ grid-column:1 / -1 }
}

/* Row widths.
   normal - the content width from Theme Settings, centered
   full   - the whole viewport, keeping the page's side gutter
   bleed  - the whole viewport, no gutter, right to the edges */
.ds-row-normal > .ds-row-in{ max-width:var(--ds-max,1200px); margin-inline:auto;
  padding-inline:var(--ds-gutter,24px) }

/* The gutter, once.
 *
 * A normal row inside the page container is inside that container's gutter
 * already, and adding its own put every row one gutter further in than the
 * page title above it - three left edges on a page that has one. A row still
 * carries the gutter where there is no container to give it one, which is what
 * a full width row climbing out to the viewport needs. */
.ds-shell .ds-row-normal > .ds-row-in{ padding-inline:0 }

/* Full width means the viewport, not the container the theme happens to put
   the content in - so the row has to climb back out of it. The negative
   margin is measured from the row's own center, which lands on the viewport
   edges whatever the container width is.
   overflow-x:clip on the body, not hidden: clip stops the sideways scrollbar
   that 100vw causes when a vertical scrollbar is present, without turning the
   body into a scroll container and breaking every sticky header on the site. */
/* On the root as well as the body.
   Clipping only the body left the scroll on the document element, so a page
   with a full width row still slid about 8px sideways - the difference
   between 100vw and the width left once the vertical scrollbar has taken
   its 15. Measured rather than assumed: scrollTo(400,y) came back at 7.6.
   clip rather than hidden on both, so neither becomes a scroll container and
   position:sticky keeps working. */
html:has(.ds-row-full),html:has(.ds-row-bleed),html:has(.ds-row-band),
body:has(.ds-row-full),body:has(.ds-row-bleed),body:has(.ds-row-band){ overflow-x:clip }

/* A band is a full width row holding a normal one.
 * The row climbs out to the viewport so whatever is painted on it - a
 * color, a picture, a gradient - reaches both edges of the window. What
 * is inside goes back to the page width and centers, so the words line up
 * with every other row on the page while the background does not.
 *
 * The inner rule is the same one a normal row uses, deliberately: two
 * ideas of "page width" would drift apart the first time --ds-max moved. */
.ds-row-full,.ds-row-bleed,.ds-row-band{ width:100vw; max-width:100vw;
  margin-inline:calc(50% - 50vw) }

.ds-row-band > .ds-row-in{ max-width:var(--ds-max,1200px); margin-inline:auto;
  padding-inline:var(--ds-gutter,24px) }
.ds-row-full > .ds-row-in{ max-width:none;
  padding-inline:var(--ds-gutter,24px) }
/* A bleed row touches the sides, and now needs nothing to make it.
   The columns carry no margin, so there is none to pull back off - the row is
   simply full width with no gutter. This used to widen the track by two column
   margins and shift it left by one to cancel them. */
.ds-row-bleed > .ds-row-in{ max-width:none; padding-inline:0 }

/* Rows carry no padding of their own. Space between sections is a gap between
   rows, not padding inside them, so the first row starts where the page's top
   margin says it does and a row's own background is exactly the row.

   The gap is on every row but the last, so the space before the footer comes
   from the page's bottom margin rather than from both at once. A row can still
   override its own padding from the builder. */
.ds-row{ padding-block:0 }

/* A row with a shader behind it. isolation so the canvas cannot slide under a
   neighboring row's background, and the content is lifted above it rather
   than the canvas being pushed down - a negative z-index would put it behind
   the row's own background color as well. */
.ds-row-gl-on{ position:relative; isolation:isolate }
/* Above the background media, below the content.
   A transition shader is a sheet that melts away to reveal what the row has
   behind it, so it has to sit over that. At the same z-index as .ds-row-bg it
   lost, because the media layer comes later in the markup - the shader ran,
   drew every frame, and was painted over by the picture it was supposed to be
   revealing. */
.ds-row-gl{ position:absolute; inset:0; display:block;
  width:100%; height:100%; z-index:1; pointer-events:none }
.ds-row-gl-on > .ds-row-in{ position:relative; z-index:2 }
/* A row's own margins.

   Bottom is on every row, the last one included - rows stack, so this is
   also the distance from one row to the next and there is no second
   setting for that.

   Left and right are not given to a full width or a bleed row: those two
   climb out of the container with a negative margin of their own, and
   being full width is the entire point of them. */
.ds-row{ margin-bottom:var(--ds-row-mb,20px) }
.ds-row:not(.ds-row-full):not(.ds-row-bleed):not(.ds-row-band){ margin-inline:var(--ds-row-mx,0px) }

/* A row that arrives over the one before it.
   The clip is written by rowenter.js from the scroll; this only has to make
   sure the row is its own layer and sits above what it is covering. Without
   JavaScript the row is simply whole, which is the right thing to fall back to. */
.ds-enter{ position:relative; z-index:1; will-change:clip-path }

/* A row that fills the window.
   svh rather than vh: on a phone vh is the height with the browser chrome
   retracted, so a "full screen" row is taller than the screen and pushes the
   next one into view the moment the address bar reappears.
   The content centers itself, because a hero that fills the window and then
   sits its words at the top is not what anybody means by full screen. */
.ds-row-screen{ min-height:100svh; display:flex; flex-direction:column }
.ds-row-screen > .ds-row-in{ width:100% }

/* The page margin is for content, not for a picture that is meant to reach the
   top of the window. A full screen first row was starting the page margin below
   the header, so a hero began with a band of page background above it. */
/* The row is wrapped in the post article, so both depths are matched - a
   child combinator alone silently misses every real page. */
/* Not when there is a title band above it. The margin is what separates the
   page from the band, so dropping it on a page with one puts a full screen row
   flush against the title - which reads as the row sitting on top of it. The
   band is the top of the page there, and this rule is only about a hero that is
   meant to be. */
/* The hero's own top margin is dropped in the theme, by zeroing --ds-main-pt
   where the first row is a screen row - and what sits above it after that is
   the masthead, or nothing at all where the header overlays. Zeroing the
   padding outright put an opaque header on top of the first eighty pixels of
   the hero. */

/* An inline header takes its own height out of the window, so a first row
   asking for the whole window is taller than what is left and the bottom of it
   can never be on screen at once. Only the first row: a full screen row further
   down the page has the whole window to itself. */


/* And the top bar above it.

   Its own rule rather than another term in the one above, because the two
   are not the same question: a transparent header takes no height out of the
   window and the bar over it takes its height whatever the header is doing.
   Only the rule for a bar applied, and only when the header was inline, so a
   hero on a site with a bar was always the bar's height too tall and its
   bottom edge could never be on screen.

   The border counts. It is a real line under a real strip, and at three or
   four pixels it is the difference between the row ending at the fold and
   the page offering a scrollbar for nothing. */




/* What is really above it, rather than the sum of the things that might be.
 *
 * Every rule above names the parts and takes them off the window, which is only
 * right while those parts are stacked above the row. They are not always: the
 * page margin is dropped for a hero, so the row starts at the very top of the
 * window and the sticky header is drawn over it - and the arithmetic then took
 * a header and a bar out of a row that had the whole window, leaving it short
 * by about their height with the next row showing underneath.
 *
 * --ds-screenrow-top is the measured distance from the top of the document to
 * the row's own top, published by bar-heights.js. It is the same answer in
 * every arrangement because it is measured rather than assumed, so it is last
 * and it wins. Falls back to the rules above where the script has not run. */
/* A full screen row is the height of the window.
 *
 * Except the first one on a page, which has the masthead and the page's own top
 * margin above it - so it is the window less those, and the two together come to
 * exactly one screen.
 *
 * Both figures are already known: --ds-header-space is what the content leaves
 * for the masthead, bar included, and --ds-main-pt is the page margin. Nothing
 * is measured, so nothing depends on where the page happens to be scrolled.
 *
 * It used to subtract --ds-screenrow-top, a distance bar-heights.js measured
 * from the top of the document as rect.top + window.scrollY. ScrollSmoother moves
 * the content with a transform, and window.scrollY is not that transform - so the
 * figure was only right while the page sat at the very top, and a hero sized
 * anywhere else came out over or under the window. A row's height should not
 * depend on where the reader has scrolled to. */
.ds-row-screen{ min-height:100svh }

/* --ds-header-space is the wrong figure to take off here, and it is wrong in
 * both directions.
 *
 * It is written by the theme as 0px when the masthead is in the flow, and as
 * the masthead's full height when it overlays. So subtracting it did nothing
 * in the one case that needs a subtraction, and took a masthead's worth off
 * the one case that must not have one - a hero under a transparent header
 * came out short by the height of the header and its bar, and the row that
 * was meant to fill the window ended two thirds of the way down it.
 *
 * What actually sits above the row is what should come off:
 *   overlaying header - nothing sits above it, so nothing comes off
 *   header in the flow - the bar and the masthead do, so they come off
 * The page's own top margin comes off either way. */
.ds-main > .ds-row:first-child.ds-row-screen,
.ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  min-height:calc(100svh - var(--ds-main-pt, 48px));
}

body.ds-header-inline .ds-main > .ds-row:first-child.ds-row-screen,
body.ds-header-inline .ds-main > :first-child > .ds-row:first-child.ds-row-screen{
  min-height:calc(100svh
    - var(--ds-bar-h, 0px)
    - var(--ds-header-h, 80px)
    - var(--ds-header-pt, 0px)
    - var(--ds-header-pb, 0px)
    - var(--ds-main-pt, 48px));
}

/* Where the content sits in a row taller than its content. Only means anything
   on a full screen row, which is the only kind with room to spare. */
.ds-row-screen.ds-row-vtop   { justify-content:flex-start }
.ds-row-screen.ds-row-vmiddle{ justify-content:center }
.ds-row-screen.ds-row-vbottom{ justify-content:flex-end }

/* While a row is pinned, its words stay in the middle of the window.
   A pinned row is held still and its effect plays across it, so text sitting at
   the top of the row ends up wherever the pin happened to freeze it - usually
   half off the screen. Anchoring to the viewport rather than to the row keeps
   the label readable for the whole time the effect is running. */
.ds-row-screen.ds-pinned > .ds-row-in{
  position:sticky; top:50%; transform:translateY(-50%) }

/* ---- one row arriving over another ----
   The overlap is layout rather than something re-created every frame. The row
   being covered sticks to the top of the window; the row arriving is drawn one
   row higher so it lies over it. Neither leaves the flow, so nothing below
   moves and nothing collapses when the effect finishes.
   The pull-up itself is written inline by rowenter.js from the measured height
   of the row being covered - a row carries its own margin as an inline style,
   so a rule here would lose to it every time. */
/* Nothing sticks and nothing is pulled up any more - a row is revealed inside
   its own space. Sticking the previous row and pulling this one over it took a
   row's height out of the document, so the page came up short and the last row
   ran under the footer. */
.ds-enter{ position:relative; z-index:1 }

/* And how the columns line up against each other. Stretch is the grid default,
   so it is written as the absence of the others rather than as a rule. */
.ds-cols-top    > .ds-row-in{ align-items:start }
.ds-cols-middle > .ds-row-in{ align-items:center }
.ds-cols-bottom > .ds-row-in{ align-items:end }

/* A first row under a transparent header starts beneath it, so the header sits
   over the row rather than above it - which is the whole point of transparent.
   Only the first row, and only when the header is actually transparent. */
body.ds-header-transparent .ds-row:first-child.ds-row-screen{ margin-top:0 }

/* Backgrounds that are not a flat color.
   The picture and the video share one layer class, so a fade, a dim and the
   stacking order are written once and cannot disagree between the two. */
.ds-row-hasmedia{ position:relative; isolation:isolate; overflow:hidden }
.ds-row-bg{ position:absolute; inset:0; width:100%; height:100%;
  z-index:0; pointer-events:none }
.ds-row-vid{ object-fit:cover }
.ds-row-hasmedia > .ds-row-in{ position:relative; z-index:2 }

/* One overlay for both the image and the video case, drawn between the
   background and the content rather than as a second element to position. */
.ds-row-dimmed{ position:relative; isolation:isolate }

/* The wash over the background media.

   Its own layer rather than ::after on the row, so it can carry the same
   fade the picture carries - see the note in the renderer. */
.ds-row-dim{ position:absolute; inset:0; z-index:1;
  background:#000; opacity:var(--ds-row-dim,0); pointer-events:none }
/* Above the wash and above the words behind it - the content is the thing
   being read, and everything else in the row is behind it. */
.ds-row-dimmed > .ds-row-in{ position:relative; z-index:3 }

/* The space between elements is a margin BELOW each one, and there is no top
   margin anywhere.
 *
 * It used to be a top margin on the element that followed, which had two faults.
 * An element carries no space above it by choice - the first element in a column
 * had a margin the designer never asked for the moment anything preceded it. And
 * two vertical margins that meet collapse to the larger of the pair, so an
 * element set to 8px below still sat 20px away from the next thing and one set
 * to 0 still sat 20: the figure was written, emitted, and then lost to its
 * neighbor. Anything larger than the default worked, which is what made it look
 * like it worked sometimes.
 *
 * Below-only has nothing to collapse against, so the element's own margin simply
 * wins - and the marker class the renderer used to add to work around it is gone
 * with the problem.
 *
 * The figure is Theme Settings, General, Element spacing. */
.ds-el{ margin-top:0; margin-bottom:var(--ds-el-gap,50px) }

/* The site's shadow, on an element that asked for one.
   The figure is General, Layout, Box shadows - one shadow for the whole site,
   so an element saying yes sits at the same height as everything else that
   did rather than at a depth of its own. */
.ds-el-shadow{ box-shadow:var(--ds-shadow, none) }

/* Shadow: Off, against a site wide tick that says every card has one.
   Two classes deep so it outranks the one class the global rule uses. */
.ds-el-noshadow,
.ds-el-noshadow .ds-card,
.ds-el-noshadow .ds-post-card,
.ds-el-noshadow .dst-card,
.ds-el-noshadow .ds-box,
.ds-el-noshadow img,
.ds-el-noshadow video{ box-shadow:none }

/* Frosted, and edged in two colors.
 *
 * The blur only shows where the fill lets it: a solid background has nothing
 * behind it to soften, which is why this is a length and not a switch - the
 * color above it is the other half of the decision.
 *
 * The gradient edge is two backgrounds rather than a border color, because a
 * border cannot hold one. The first fills the padding box with whatever the
 * element already sits on; the second fills the border box with the gradient,
 * and the clip between them leaves the gradient showing in the edge alone. */
.ds-box, .ds-card, .ds-post-card{
  backdrop-filter:blur(var(--ds-box-blur, 0));
  -webkit-backdrop-filter:blur(var(--ds-box-blur, 0)) }
.ds-card, .ds-post-card{
  backdrop-filter:blur(var(--ds-card-blur, 0));
  -webkit-backdrop-filter:blur(var(--ds-card-blur, 0)) }

.ds-box:where([style*="--ds-box-grad"]), .ds-box{ border-color:var(--ds-box-line) }

@supports (background-clip: padding-box){
  .ds-box{
    background-image:var(--ds-box-grad, none);
    background-origin:border-box;
    background-clip:padding-box, border-box }
  .ds-card, .ds-post-card{
    background-image:var(--ds-card-grad, none);
    background-origin:border-box;
    background-clip:padding-box, border-box }
}

/* A rule in the flow of the page.
 *
 * The line is a top border rather than a colored box with a height: one
 * property carries both the thickness and the color, and a thickness of zero
 * then draws nothing without needing to be special-cased.
 *
 * The color is the site's divider color, which is the whole reason this
 * element exists - the setting was there with nothing to draw. An element given
 * its own color writes it inline and that wins, which is what "overrides"
 * means here. The last fallback follows the text, faintly, for a site that has
 * never opened the Dividers card. */
.ds-line{
  border-top:1px solid
    var(--ds-rule-color, var(--ds-line, color-mix(in srgb, currentColor 14%, transparent)));
  width:100% }

/* A heading belongs to what comes after it.
 *
 * Every element carries the same gap below itself, which is right between two
 * separate things and wrong under a heading - it left a 50px hole between
 * "Sign in" and the form it names, so the words read as belonging to whatever
 * was above rather than below.
 *
 * Sized against the heading rather than in pixels, so an h2 at 44px gets more
 * room than an h5 at 18px without either being set by hand, and capped at the
 * element gap so it can never exceed the space between two unrelated things.
 * The last element in a column keeps the full gap: there is nothing under it to
 * belong to. */
/* Unless Typography, Performance has been given a figure - that is somebody
   asking for the same gap under every heading, and it has to beat this rule or
   the setting does nothing on any page built here, which is all of them. */
.ds-el.ds-heading:not(:last-child){
  margin-bottom:var(--ds-h-mb, min(var(--ds-el-gap,50px), .55em));
}


/* Blend modes, from the Blend mode field on the text elements.
 *
 * Classes rather than an inline style: the mode is one of a known seven, and
 * seven rules in the stylesheet beat a style attribute on every heading on the
 * page. A blend only reaches as far as its nearest stacking context, so nothing
 * here creates one of its own. */
.ds-blend-multiply  { mix-blend-mode:multiply }
.ds-blend-screen    { mix-blend-mode:screen }
.ds-blend-overlay   { mix-blend-mode:overlay }
.ds-blend-difference{ mix-blend-mode:difference }
.ds-blend-exclusion { mix-blend-mode:exclusion }
.ds-blend-luminosity{ mix-blend-mode:luminosity }


/* The icon box.
 *
 * A grid rather than a flex line, because the icon and the words swap places
 * and change flow direction between the four positions, and a grid says that
 * in one property each time instead of three.
 *
 * It carries .ds-box too, so the fill, the edge, the blur and the border
 * gradient set on Theme Settings, Elements, Box element all reach it. Only
 * what is particular to this element is written here. */
.ds-iconbox{
  display:grid; gap:var(--ds-iconbox-gap,18px);
  align-items:start;
}
/* A whole card that is a link.

   The anchor covers the card rather than containing it - see the note in
   render_iconbox. Above the words so the whole face is clickable, and under
   nothing, because there is nothing in a card to click but the card. */
.ds-iconbox.has-link{ position:relative }
.ds-iconbox-hit{
  position:absolute; inset:0; z-index:2;
  border-radius:inherit; text-decoration:none;
}
.ds-iconbox-hit:focus-visible{
  outline:2px solid var(--ds-field-focus, var(--ds-link));
  outline-offset:2px;
}

.ds-iconbox-mark{ display:flex; line-height:0;
  color:var(--ds-iconbox-ink, var(--ds-icon-ink, currentColor)) }
.ds-iconbox-mark svg{ width:auto; height:auto; display:block }
.ds-iconbox-head{ margin:0 0 .35em }
.ds-iconbox-words > :last-child{ margin-bottom:0 }

/* Above the words, aligned to whichever edge the text is. */
.ds-iconbox.is-top{ grid-template-columns:minmax(0,1fr) }

/* Above and centered, everything under it centered with it. */
.ds-iconbox.is-middle{ grid-template-columns:minmax(0,1fr); justify-items:center; text-align:center }

/* Beside the words. The icon column is as wide as the mark and no wider, so a
   long heading takes the rest of the row rather than being squared off against
   it. */
.ds-iconbox.is-left{ grid-template-columns:auto minmax(0,1fr) }
.ds-iconbox.is-right{ grid-template-columns:minmax(0,1fr) auto }
.ds-iconbox.is-right .ds-iconbox-mark{ order:2 }
.ds-iconbox.is-right{ text-align:right }
.ds-iconbox.is-right .ds-iconbox-mark{ justify-self:end }

/* The three shadows the Box offers, and the site's own. */
.ds-iconbox.ds-boxsh-soft{ box-shadow:0 6px 24px rgba(0,0,0,.28) }
.ds-iconbox.ds-boxsh-deep{ box-shadow:0 16px 48px rgba(0,0,0,.6) }
.ds-iconbox.ds-boxsh-theme{ box-shadow:var(--ds-shadow,none) }

@media (max-width:640px){
  /* Beside becomes above on a phone: an icon and a paragraph side by side in a
     three hundred pixel column leaves the words about eight characters wide. */
  .ds-iconbox.is-left,
  .ds-iconbox.is-right{ grid-template-columns:minmax(0,1fr); text-align:left }
  .ds-iconbox.is-right .ds-iconbox-mark{ order:0; justify-self:start }
}

.ds-align-left  { text-align:left }
.ds-align-center{ text-align:center }
.ds-align-right { text-align:right }

/* Justified, and made to work on one line.
 *
 * text-align:justify on its own deliberately leaves the last line alone, and on
 * a three word heading every line is the last line - so it looks like nothing
 * happened. That is why this never works when it is reached for. text-align-last
 * is the half that spreads that line, and with both, three words sit one at each
 * edge and one in the middle, held there as the column changes width.
 *
 * The two are stated separately rather than relying on the shorthand, because
 * text-align-last is not part of text-align. */
.ds-align-justify{
  text-align: justify;
  -moz-text-align-last: justify;
  text-align-last: justify;
  /* A single word longer than the column has nothing to space against and would
     otherwise push the line wider than the tile. */
  overflow-wrap: break-word;
}

/* A heading is an element like any other and takes the same gap below it.
   Only the top margin is zeroed here, and only because the theme's own h1-h6
   rule carries one - the gap below comes from the element rule above, the same
   as every other element, and an element that sets its own margin beats both. */
.ds-heading{ margin-top:0 }

/* The kicker: one line of type over a heading.
 *
 * Its own size, weight, case and tracking, all from Typography, and its own
 * color from Elements - falling back to the heading color, which is what it
 * looked like before the color existed.
 *
 * The gap below is the heading's, not the element gap: a kicker belongs to the
 * heading under it, and 50px of air between the two reads as two unrelated
 * pieces of text rather than one. */
.ds-kicker{
  font-family:var(--ds-font-heading);
  color:var(--ds-kicker-ink, var(--ds-heading));
  font-size:var(--ds-kicker-size,14px);
  font-weight:var(--ds-kicker-weight,600);
  text-transform:var(--ds-kicker-case,uppercase);
  line-height:var(--ds-kicker-lh,1.3em);
  letter-spacing:var(--ds-kicker-track,.12em);
  margin:0;
}
.ds-el.ds-kicker:not(:last-child){
  margin-bottom:min(var(--ds-el-gap,50px), .8em);
}
.ds-text > :first-child{ margin-top:0 }
.ds-text > :last-child{ margin-bottom:0 }

/* The three body sizes, each set in Typography. Applied to the block and
   inherited by everything inside it, so a list or a blockquote in a large
   paragraph is large too rather than dropping back to the default. Medium falls
   back to the paragraph settings, which is what it was before this existed. */
.ds-text-large{
  font-size:var(--ds-text-large-size,22px);
  font-weight:var(--ds-text-large-weight,400);
  line-height:var(--ds-text-large-lh,1.45em);
  letter-spacing:var(--ds-text-large-track,0em);
  text-transform:var(--ds-text-large-case,none);
}
.ds-text-medium{
  font-size:var(--ds-text-medium-size, var(--ds-p-size,17px));
  font-weight:var(--ds-text-medium-weight, var(--ds-p-weight,400));
  line-height:var(--ds-text-medium-lh, var(--ds-p-lh,1.4em));
  letter-spacing:var(--ds-text-medium-track, var(--ds-p-track,0em));
  text-transform:var(--ds-text-medium-case,none);
}
.ds-text-small{
  font-size:var(--ds-text-small-size,14px);
  font-weight:var(--ds-text-small-weight,400);
  line-height:var(--ds-text-small-lh,1.5em);
  letter-spacing:var(--ds-text-small-track,0em);
  text-transform:var(--ds-text-small-case,none);
}

.ds-figure{ margin:0 }
.ds-img{ max-width:100%; height:auto; display:block }
.ds-corner-soft { border-radius:8px }
.ds-corner-round{ border-radius:22px }

/* Alignment is text-align on the figure, so an image narrower than its column
   sits where it was asked to. inline-block rather than a margin trick: the
   figure may hold a button or a link, and those have to move with it. */
.ds-figure > *{ display:inline-block; max-width:100% }
.ds-align-center .ds-img,.ds-align-right .ds-img{ margin-inline:auto }
.ds-align-right  .ds-img{ margin-right:0 }

/* The clickable picture is a button, and a button brings a look with it. */
.ds-lb-open{ padding:0; border:0; background:none; cursor:pointer; display:block;
  line-height:0; font:inherit; color:inherit }
.ds-lb-open img{ transition:transform .5s cubic-bezier(.22,1,.36,1),
  filter .5s cubic-bezier(.22,1,.36,1) }
.ds-lb-open:hover img{ transform:scale(1.02); filter:brightness(1.06) }
.ds-lb-open:focus-visible{ outline:2px solid currentColor; outline-offset:4px }

/* ---------------------------------------------------------------------
 * Arriving on screen
 *
 * The starting state is CSS, not JavaScript - the picture must be hidden
 * before the script runs or it flashes at full size and then animates in.
 * .is-in is added the moment it is seen, and is also what a browser with no
 * IntersectionObserver gets straight away.
 * ------------------------------------------------------------------ */
[data-ds-anim]{ will-change:opacity,transform }
[data-ds-anim="fade"],[data-ds-anim="rise"],[data-ds-anim="zoom"]{ opacity:0 }
[data-ds-anim="rise"]{ transform:translateY(40px) }
[data-ds-anim="zoom"]{ transform:scale(.92) }
/* The clip goes on what is inside, never on the observed element itself.
   An element with clip-path:inset(0 0 100%) reports an empty intersection
   rectangle, so IntersectionObserver never fires, so the class that would
   remove the clip is never added - and the picture stays invisible for good.
   Found by testing: a "reveal" image sat there clipped with nothing in the
   console to say why. */
[data-ds-anim="reveal"] > *{ clip-path:inset(0 0 100% 0);
  transition:clip-path 1.1s cubic-bezier(.22,1,.36,1) }
[data-ds-anim="reveal"].is-in > *{ clip-path:inset(0 0 0% 0) }
[data-ds-anim="parallax"]{ overflow:hidden }

[data-ds-anim].is-in{ opacity:1; transform:none;
  transition:opacity .8s ease,transform .8s cubic-bezier(.22,1,.36,1) }

/* GSAP drives the values itself once it is there, so the transition has to get
   out of the way or the two fight over the same properties. */
.ds-gsap [data-ds-anim].is-in{ transition:none }

@media (prefers-reduced-motion: reduce){
  [data-ds-anim]{ opacity:1 !important; transform:none !important }
  [data-ds-anim] > *{ clip-path:none !important }
  .ds-lb-open img{ transition:none }
}

/* ---------------------------------------------------------------------
 * The lightbox
 * ------------------------------------------------------------------ */
/* The page held still while the lightbox is over it.
 *
 * overflow:hidden takes the scrollbar away, and the page widens by exactly its
 * width the moment it goes - so a centered layout slides sideways on open and
 * back on close. Its width is given back as padding, so the content keeps the
 * room it had. --ds-sbw is published by the builder and is zero on an overlay
 * scrollbar, which is the right answer there. The lightbox itself is fixed to
 * the viewport and does not move with this. */
html.ds-lb-on{ overflow:hidden; padding-right:var(--ds-sbw,0px) }

.ds-lb{ position:fixed; inset:0; z-index:99999; display:grid; place-items:center;
  padding:clamp(16px,4vw,56px) }
.ds-lb[hidden]{ display:none }
/* The dimming behind a Modal element, and behind the lightbox it shares its
   viewer with. The modal's own setting wins where one is chosen; the lightbox
   setting is what it falls back to, so a site that only ever set one still
   gets the look it asked for in both places. */
.ds-lb-back{ position:absolute; inset:0;
  background-color:var(--ds-lb-overlay,#080809);
  background-image:var(--ds-lb-image,none);
  background-size:cover; background-position:50% 50%;
  opacity:var(--ds-lb-opacity,.94);
  backdrop-filter:blur(var(--ds-lb-blur,14px));
  -webkit-backdrop-filter:blur(var(--ds-lb-blur,14px)) }

/* The modal's own dimming.
 *
 * Scoped to the modal rather than read on the shared rule above. Both are drawn
 * by the same viewer and the settings are written on the root, so a modal scrim
 * stated up there was changing the lightbox's overlay as well - one setting
 * quietly editing two things.
 *
 * Unset, this falls back to a figure of its own rather than to the lightbox's.
 * They are two elements: one is a picture viewer, the other is a panel of
 * content, they are set on two different screens, and a modal has no reason to
 * take its dimming from how somebody dressed a gallery. */
/* The opacity on the rule above is the gallery's, set on its own screen. A modal
   states its dimming as a color, alpha and all, so being faded a second time
   here both darkened a color somebody picked exactly and left the blur behind
   it with almost nothing to show through. */
.ds-lb-modal .ds-lb-back{
  background-color:var(--ds-modal-scrim, rgba(0,0,0,.72));
  opacity:1;
  backdrop-filter:blur(var(--ds-modal-blur, 14px));
  -webkit-backdrop-filter:blur(var(--ds-modal-blur, 14px)) }

.ds-lb-stage{ position:relative; max-width:min(1400px,100%); max-height:100%;
  display:grid; place-items:center }
/* Centered on both axes, not only across.
 *
 * justify-items was set and align-items was not, so the second kept its default
 * of stretch - and a grid item told to stretch with height:auto is pulled to
 * the height of its row rather than keeping its own. It only shows on a gallery
 * whose photographs are different shapes, which is why it survived: this
 * catalog has 296x640 pictures next to a 480x640, and the wide one came out
 * narrow. */
.ds-lb-figure{ margin:0; display:grid; gap:14px; place-items:center }
.ds-lb-figure img{ display:block; max-width:100%;
  max-height:calc(100svh - clamp(96px,16vw,190px)); width:auto; height:auto;
  /* Its own shape, whatever a box around it decides.
     Both axes are auto here, so this changes nothing today - it is the
     guarantee that a picture is scaled rather than distorted if anything ever
     does constrain both, which is exactly how the stretch above happened. */
  object-fit:contain;
  align-self:center;
  border-radius:4px; box-shadow:0 40px 120px rgba(0,0,0,.6) }
.ds-lb-figure figcaption{ color:var(--ds-lb-caption,rgba(255,255,255,.62));
  font-size:var(--ds-lb-caption-size,14px);
  letter-spacing:.01em; text-align:center; max-width:62ch }
.ds-lb-figure figcaption[hidden]{ display:none }
/* Captions turned off on the Lightbox screen. The same shape as ds-lb-nocount
   above it, because it is the same decision about a different part. */
.ds-lb-nocap .ds-lb-figure figcaption{ display:none }
/* And no close button, for a viewer closed by clicking away or by Escape. */
.ds-lb-noclose .ds-lb-x{ display:none }

/* Magnifying. The cursor is the only thing that says a picture can be looked at
   more closely, so it has to be right before the first click rather than after
   it. The stage stops clipping while magnified - the picture is meant to run
   past its own frame, that being the point of magnifying it. */
.ds-lb.can-zoom .ds-lb-figure img{ cursor:zoom-in; touch-action:none }
.ds-lb.is-zoomed .ds-lb-figure img{ cursor:grab; will-change:transform }
.ds-lb.is-zoomed .ds-lb-figure img:active{ cursor:grabbing }
.ds-lb.is-zoomed .ds-lb-stage{ max-width:100%; overflow:visible }

/* Snaps back rather than easing, because the drag has to keep up with the
   finger. The step in and out is the only part that is animated. */
.ds-lb-figure img{ transition:transform .18s cubic-bezier(.22,1,.36,1) }
.ds-lb.is-zoomed .ds-lb-figure img:active{ transition:none }

@media (prefers-reduced-motion: reduce){
  .ds-lb-figure img{ transition:none }
}

.ds-lb-x,.ds-lb-nav{ position:absolute; z-index:2; display:grid; place-items:center;
  width:var(--ds-lb-icon,52px); height:var(--ds-lb-icon,52px);
  border:1px solid var(--ds-lb-icon-edge,rgba(255,255,255,.16));
  border-radius:var(--ds-lb-radius,50%);
  background:var(--ds-lb-icon-bg,rgba(255,255,255,.04));
  color:var(--ds-lb-ink,#fff); cursor:pointer; padding:0;
  transition:background var(--ds-lb-speed,.35s) ease,
    border-color var(--ds-lb-speed,.35s) ease,
    transform var(--ds-lb-speed,.35s) ease }
/* The hover colors from Lightbox > Controls. Each falls back to the hardcoded
   figure the control used before it could be set, so a site that has left them
   blank behaves exactly as it did. */
.ds-lb-x:hover,.ds-lb-nav:hover{
  background:var(--ds-lb-icon-bg-h, rgba(255,255,255,.12));
  border-color:var(--ds-lb-icon-edge-h, rgba(255,255,255,.4));
  color:var(--ds-lb-ink-h, var(--ds-lb-ink,#fff)) }
.ds-lb-x:focus-visible,.ds-lb-nav:focus-visible{ outline:2px solid #fff; outline-offset:3px }
.ds-lb-x svg,.ds-lb-nav svg{ width:20px; height:20px; fill:none; stroke:currentColor;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round }

.ds-lb-x{ top:clamp(14px,3vw,28px); right:clamp(14px,3vw,28px) }
.ds-lb-nav{ top:50%; margin-top:calc(var(--ds-lb-icon,52px) / -2) }
.ds-lb-nav.prev{ left:clamp(10px,2.5vw,30px) }
.ds-lb-nav.next{ right:clamp(10px,2.5vw,30px) }
.ds-lb-nav[hidden]{ display:none }

.ds-lb-count{ position:absolute; bottom:clamp(14px,3vw,26px); left:50%;
  transform:translateX(-50%); color:rgba(255,255,255,.5); font-size:13px;
  letter-spacing:.14em; font-variant-numeric:tabular-nums }
.ds-lb-nocount .ds-lb-count{ display:none }

/* ---- modals ----
   The lightbox viewer with a panel where the picture goes, so the animation and
   the focus trap are the ones already set. Everything the modal LOOKS like is
   its own: it is a separate element from the lightbox, set on its own screen,
   and unset it falls back to the theme rather than to the picture viewer. */
.ds-modal-panel{ position:relative; z-index:2; overflow-y:auto;
  max-height:calc(100svh - clamp(96px,16vw,190px));
  /* Unset, the padding stays fluid and the colors are the theme's. */
  padding:var(--ds-modal-pad, clamp(24px,4vw,48px));
  background:var(--ds-modal-bg, var(--ds-bg,#0b0b0c));
  color:var(--ds-modal-ink, var(--ds-text,#f5f5f5));
  border:var(--ds-modal-bw, 1px) solid
    var(--ds-modal-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius:var(--ds-field-radius,4px) }

/* Headings inside the panel answer to their own color.
 *
 * They took the panel's text color, so a modal wanting a pale heading over
 * quieter body copy had one control for two jobs. Unset, they inherit exactly
 * as before. */
.ds-modal-panel :is(h1,h2,h3,h4,h5,h6),
.ds-modal-head{ color:var(--ds-modal-head, inherit) }

.ds-modal-small { width:min(420px,100%) }
.ds-modal-medium{ width:min(680px,100%) }
.ds-modal-large { width:min(960px,100%) }
.ds-modal-full  { width:100%; max-height:100svh; border:0; border-radius:0 }

.ds-modal-panel > :first-child{ margin-top:0 }
.ds-modal-panel > :last-child{ margin-bottom:0 }
.ds-modal-head{ margin:0 0 14px }

/* Where the panel sits. The viewer is a grid, so this is one line each. */
.ds-lb-modal.ds-modal-at-top    { align-items:start }
.ds-lb-modal.ds-modal-at-bottom { align-items:end }
.ds-lb-modal.ds-modal-at-left   { justify-items:start }
.ds-lb-modal.ds-modal-at-right  { justify-items:end }

/* The modal's close button.
 *
 * The viewer draws one button and both elements appear in it, so this is where
 * the modal says what its own should look like. Dressing the lightbox on the
 * Lightbox tab used to dress this as well, which is one control over two things
 * that are set on two screens for two reasons.
 *
 * Every fallback here is the theme's, not the viewer's. */
.ds-lb-modal .ds-lb-x{
  color:var(--ds-modalx-ink, var(--ds-text,#fff));
  background:var(--ds-modalx-bg, rgba(255,255,255,.04));
  border-color:var(--ds-modalx-edge, var(--ds-line, rgba(255,255,255,.16))) }
/* Given a color, hover keeps it and lifts it rather than jumping to the
   viewer's white - a chosen background that turns pale on hover reads as a
   fault rather than as a state. */
.ds-lb-modal .ds-lb-x:hover{
  /* The mark comes forward on the way to being clicked. White unless the
     Elements screen says otherwise - it sits on a scrim, and the one thing
     it has to do on hover is read. */
  color:var(--ds-modalx-ink-hover, #fff);
  background:var(--ds-modalx-bg, rgba(255,255,255,.12));
  border-color:var(--ds-modalx-edge, rgba(255,255,255,.4));
  filter:brightness(1.25) }

/* Which corner it sits in. All four stated, so each one clears the two edges
   the default rule set rather than only the ones it names. */
body.ds-modalx-at-tr .ds-lb-modal .ds-lb-x{
  inset:clamp(14px,3vw,28px) clamp(14px,3vw,28px) auto auto }
body.ds-modalx-at-tl .ds-lb-modal .ds-lb-x{
  inset:clamp(14px,3vw,28px) auto auto clamp(14px,3vw,28px) }
body.ds-modalx-at-br .ds-lb-modal .ds-lb-x{
  inset:auto clamp(14px,3vw,28px) clamp(14px,3vw,28px) auto }
body.ds-modalx-at-bl .ds-lb-modal .ds-lb-x{
  inset:auto auto clamp(14px,3vw,28px) clamp(14px,3vw,28px) }

/* The same four corners, of the panel.
 *
 * The button is moved inside the panel for these - a rule out here cannot hold
 * it against a box it is not in - and inset by the panel's own padding, so it
 * keeps the margin the content keeps and lands on the line the words start on
 * rather than in the corner of a box they never reach.
 *
 * --ds-modal-pad is only written when it has been set, so the fallback here is
 * the panel's own, stated the same way. */
body.ds-modalx-at-ptr .ds-lb-modal .ds-lb-x{
  inset:var(--ds-modal-pad, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) auto auto }
body.ds-modalx-at-ptl .ds-lb-modal .ds-lb-x{
  inset:var(--ds-modal-pad, clamp(24px,4vw,48px)) auto auto
        var(--ds-modal-pad, clamp(24px,4vw,48px)) }
body.ds-modalx-at-pbr .ds-lb-modal .ds-lb-x{
  inset:auto var(--ds-modal-pad, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) auto }
body.ds-modalx-at-pbl .ds-lb-modal .ds-lb-x{
  inset:auto auto var(--ds-modal-pad, clamp(24px,4vw,48px))
        var(--ds-modal-pad, clamp(24px,4vw,48px)) }

/* A word instead of a cross needs a pill, the same as the lightbox's does. */
body.ds-modalx-word .ds-lb-modal .ds-lb-x{
  width:auto; padding:0 16px; border-radius:999px }

/* A modal opened from a link rather than a button. */
.ds-modal-link{ padding:0; border:0; background:none; font:inherit;
  color:var(--ds-link); cursor:pointer; text-decoration:underline;
  text-underline-offset:.18em }
.ds-modal-link:hover{ color:var(--ds-link-hover) }

/* The close control as a word rather than a glyph. A circle sized to an icon
   cannot hold text, so it becomes a pill. */
.ds-lb-close-word .ds-lb-x{ width:auto; padding:0 16px; border-radius:999px }
/* The word itself is a setting now - it was hard coded to Close, so a site that
   wanted Back or Done had nowhere to say so. Drawn by CSS rather than written
   into the markup, so the button keeps the accessible name it was given. */
.ds-lb-x .ds-lb-word{ font-size:13px; letter-spacing:.12em; text-transform:uppercase }
.ds-lb-x .ds-lb-word::after{ content:var(--ds-lb-close-word,'Close') }

/* On a phone the arrows sit under the picture, where a thumb can reach them. */
@media (max-width:640px){
  .ds-lb-nav{ top:auto; bottom:clamp(14px,4vw,24px); margin-top:0 }
  .ds-lb-nav.prev{ left:calc(50% - 74px) }
  .ds-lb-nav.next{ right:calc(50% - 74px) }
  .ds-lb-count{ bottom:calc(clamp(14px,4vw,24px) + 66px) }
}

/* Play and stop, and nothing else.
   One control over a piece of design, rather than the browser's full bar. */
.ds-video-simple{ position:relative }
.ds-video-btn{ position:absolute; inset:0; display:grid; place-items:center;
  width:100%; height:100%; padding:0; border:0; background:none; cursor:pointer;
  color:#fff }
.ds-video-btn i{ display:block; width:0; height:0; margin-left:6px;
  border-style:solid; border-width:14px 0 14px 22px;
  border-color:transparent transparent transparent currentColor;
  filter:drop-shadow(0 2px 12px rgba(0,0,0,.6));
  transition:opacity .2s ease }
/* Playing: the triangle becomes two bars, drawn from the same element so there
   is no second icon to keep in step. */
.ds-video-simple.is-playing .ds-video-btn i{ opacity:0 }
.ds-video-simple.is-playing .ds-video-btn:hover i{ opacity:1;
  width:18px; height:26px; margin:0; border:0;
  background:linear-gradient(90deg,currentColor 0 6px,transparent 6px 12px,currentColor 12px 18px) }

/* Scrubbed by the scroll: the film is the page, so it fills its box. */
.ds-video-scrub video{ width:100%; height:100%; object-fit:cover }

/* ---- blog posts ---- */
/* Cards follow Column gap on General - Layout - Rows, falling back to the
   page gutter while that is left at zero. The gutter is the space at the sides
   of the page, and using it here put fifty pixels between every post. */
.ds-posts{ display:grid; gap:var(--ds-card-gap, var(--ds-gutter,24px)) }
.ds-posts-grid,
.ds-posts-card{ grid-template-columns:repeat(var(--ds-post-cols,3),minmax(0,1fr)) }

/* Grid and masonry take the same room and the same corner as the rest.

   Every layout but these two had a rule of its own carrying the padding,
   so the Blog post cards card reached four of the six and looked broken on
   the other two. Nought and square unless asked for, which is what a bare
   grid has always been. */
/* A grid of posts is a wall of cards, so it is dressed like one - the same
   background, corner and room inside that Cards, Bento and Horizontal
   already take from Theme Settings, Elements, Blog post cards. It used to
   read the corner and the padding and not the background, so the only
   thing telling a card from the page behind it was the site wide shadow. */
.ds-posts-grid .ds-post-card,
.ds-posts-masonry .ds-post-card{
  /* background-color, never the shorthand: the shorthand resets
     background-image, and background-image is the layer a gradient border is
     painted on. A card with a gradient chosen drew nothing at all. */
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px)
          var(--ds-card-pb,14px) var(--ds-card-pl,14px);
  border-radius:var(--ds-card-radius,12px);
  /* A corner is only a corner if what is inside it is cut to match. */
  overflow:hidden }
.ds-posts-grid .ds-post-card-img,
.ds-posts-masonry .ds-post-card-img{
  margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 14px }
.ds-posts-grid .ds-post-card-img img,
.ds-posts-masonry .ds-post-card-img img{
  border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Cards: the same grid, with each post standing on something. A grid of bare
   text on a dark page has nothing to say where one post ends and the next
   begins, which is the whole reason somebody asks for cards. */
.ds-posts-card .ds-post-card{
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, color-mix(in srgb, var(--ds-text,#f5f5f5) 12%, transparent));
  border-radius:var(--ds-card-radius,12px); overflow:hidden;
  /* Four variables rather than one, because the picture below is pulled
     back out by exactly these and a shorthand cannot be negated. */
  padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px);
  transition:border-color .25s ease, transform .25s ease }
.ds-posts-card .ds-post-card:hover{
  border-color:var(--ds-card-line-hover, currentColor);
  transform:translateY(var(--ds-card-lift,-3px)) }
.ds-posts-card .ds-post-card-img{ margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 14px }
.ds-posts-card .ds-post-card-img img{ border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Bento: the same cards, unevenly. The first is given two columns and two
   rows, so the block opens on one picture rather than on a tidy row of
   identical ones - which is the only thing a bento is for. */
.ds-posts-bento{ grid-template-columns:repeat(var(--ds-post-cols,3),minmax(0,1fr));
  grid-auto-rows:minmax(160px,auto) }
.ds-posts-bento .ds-post-card{
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  border-radius:var(--ds-card-radius,12px); overflow:hidden; padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px) }
.ds-posts-bento .ds-post-card:first-child{ grid-column:span 2; grid-row:span 2 }
.ds-posts-bento .ds-post-card:nth-child(4){ grid-column:span 2 }

/* The picture takes whatever the words do not.
 *
 * A bento card is taller than its own content by design - that is what makes
 * the grid uneven - so anything that only sizes to its content leaves the rest
 * as bare card. Pushing the content to one end just moves the bare part to the
 * other. The picture grows into it instead, which is what the tall cell was
 * for. */
.ds-posts-bento .ds-post-card-img{
  order:-1; margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 12px;
  flex:1 1 auto; min-height:120px; overflow:hidden }
.ds-posts-bento .ds-post-card-img img{
  width:100%; height:100%; object-fit:cover;
  border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Content height. The card stops where its words do rather than being
   stretched to the tallest in its row, and the picture goes back to its own
   shape instead of growing into slack that is no longer there.

   Written for the three layouts that lay cards out in a grid rather than for
   the bento alone: they all stretch, so a post with a two line headline used to
   set the height of every card beside it. */
.ds-posts-bento.is-cardh-fit,
.ds-posts-grid.is-cardh-fit,
.ds-posts-card.is-cardh-fit{ align-items:start }

.ds-posts-bento.is-cardh-fit .ds-post-card-img,
.ds-posts-grid.is-cardh-fit .ds-post-card-img,
.ds-posts-card.is-cardh-fit .ds-post-card-img{ flex:0 0 auto; min-height:0 }

.ds-posts-bento.is-cardh-fit .ds-post-card-img img,
.ds-posts-grid.is-cardh-fit .ds-post-card-img img,
.ds-posts-card.is-cardh-fit .ds-post-card-img img{ height:auto }

/* Horizontal: one row that scrolls sideways, snapping card to card. Every
   card keeps a readable width rather than sharing the row between however
   many posts were asked for. */
/* The frame the track slides inside. It is what gets pinned, so it clips - and
   nothing scrolls it by hand, which is why there is no scrollbar to look at.

   As tall as the window, because a pinned frame is held against the top of the
   screen and everything below it is the pin-spacer showing through. A frame
   400px tall left most of a screen empty, which reads as a gigantic gap; a
   frame the height of the window leaves none. The cards sit in the middle of
   it. */
.ds-posts-rail{ overflow:hidden;
  min-height:100svh; display:flex; align-items:center }

/* The track. As wide as the cards make it, moved by the page rather than by a
   scroll position, so no overflow and no snapping: both belonged to a scroll
   container this stopped being. */
/* Where a rail's first card lines up.

   The frame is the full width of the window, so left alone a rail begins
   hard against the edge of the screen. That suits a rail meant to read as
   though it carries on past the window, and reads as a mistake when the
   rest of the page is held inside a measure.

   Written against the track - anything hgallery slides - rather than
   against one plugin's wall, because the shop's products and the blog's
   posts are the same effect carrying different cards.

   Left of the page is the default, and is two parts: the page's own left
   edge, then the half scrollbar the bleed row has already shifted this
   frame by. A bleed row is 100vw wide and 100vw counts the scrollbar. */
.ds-fx-hgallery [data-track].is-railstart-page{
  padding-left:calc(
    max(
      var(--ds-gutter,24px),
      (100vw - var(--ds-sbw,0px) - var(--ds-max,1200px)) / 2 + var(--ds-gutter,24px)
    )
    + var(--ds-sbw,0px) / 2
  );
}

/* Edge of the screen: against the glass, not half a scrollbar behind it. */
.ds-fx-hgallery [data-track].is-railstart-edge{ padding-left:calc(var(--ds-sbw,0px) / 2) }

/* Already going. The first card is cut by the edge of the screen, which says
   the row began before the page did. A third of a card, because half reads
   as a card somebody forgot to finish. */
.ds-fx-hgallery [data-track].is-railstart-off{
  padding-left:0;
  margin-left:calc(var(--ds-sbw,0px) / 2 - var(--ds-rail-card, 320px) / 3);
}

.ds-posts-horizontal{ display:flex; width:max-content;
  --ds-rail-card:min(78vw,340px) }
.ds-posts-horizontal .ds-post-card{ flex:0 0 min(78vw,340px);
  display:flex; flex-direction:column;
  background-color:var(--ds-card-bg,#363636);
  border:var(--ds-card-bw,1px) solid var(--ds-card-line, transparent);
  border-radius:var(--ds-card-radius,12px); overflow:hidden; padding:var(--ds-card-pt,14px) var(--ds-card-pr,14px) var(--ds-card-pb,14px) var(--ds-card-pl,14px) }
.ds-posts-horizontal .ds-post-card-img{ margin:calc(var(--ds-card-pt,14px) * -1) calc(var(--ds-card-pr,14px) * -1) 12px }
.ds-posts-horizontal .ds-post-card-img img{ border-radius:var(--ds-card-radius,12px) var(--ds-card-radius,12px) 0 0 }

/* Masonry as CSS columns rather than a layout library - see the note in
   render_posts(). display:block because a grid container has no columns. */
.ds-posts-masonry{ display:block; column-count:var(--ds-post-cols,3);
  column-gap:var(--ds-card-gap, var(--ds-gutter,24px)) }
.ds-posts-masonry .ds-post-card{ break-inside:avoid;
  margin-bottom:var(--ds-gutter,24px) }

/* This said the same thing as the rule further up, twice, and said it with the
   older figures - so whichever set of defaults was written up there, these
   noughts came later and won. Grid and masonry are dressed once, at the top,
   with the rest of the card layouts. */

/* A plain list is plain. No background, no room inside it and no shadow -
   not even the site wide one, which is ticked for cards and reaches this
   through the same class. A list of posts is a list, and the moment it has
   a panel behind each line it is a wall of cards in a single column. */
.ds-posts-list .ds-post-card{ display:grid; gap:var(--ds-gutter,24px);
  grid-template-columns:minmax(0,1fr);
  background:none; padding:0; border-radius:0; box-shadow:none }
@media (min-width:700px){
  /* The picture takes a third, so a list of posts reads as a list rather than
     as a stack of full width images with captions. */
  .ds-posts-list .ds-post-card:has(.ds-post-card-img){
    grid-template-columns:minmax(0,1fr) minmax(0,2fr); align-items:center }
}

/* The frame the picture moves inside.

   overflow here rather than on the card: the list layout has no overflow of
   its own, so a picture told to zoom grew out over the words beside it. */
.ds-post-card-img{ overflow:hidden }

.ds-post-card-img img{ width:100%; height:auto; display:block;
  border-radius:var(--ds-field-radius,4px);
  /* No transition until one is asked for, so a card set to Nothing carries
     no animation at all rather than a zero length one. */
  transition:transform var(--ds-card-img-time,0s) ease }

.ds-post-card:hover .ds-post-card-img img{
  transform:translateY(var(--ds-card-img-y,0)) scale(var(--ds-card-img-scale,1)) }

/* Somebody who has asked their machine to stop moving things has asked for
   this too. */
@media (prefers-reduced-motion: reduce){
  .ds-post-card-img img,
  .ds-post-card:hover .ds-post-card-img img{ transition:none; transform:none }
}
/* The heading on a post card takes its type from H4, all of it.
 *
 * The size and the weight were read from the settings and the line height was
 * a flat 1.2 written here - so Theme Settings, Type, H4 moved two of the three
 * and the spacing between the lines of a two line post title could not be
 * changed from anywhere at all. The theme's own .ds-card-title has read the
 * token all along, which left two card headings on one site answering to
 * different things. */
/* The size comes from the level, and nothing else does.
 *
 * These four were pinned to H4 whatever tag the card was written as, so Title
 * level changed the tag and the type never moved - the setting looked broken
 * because the stylesheet was answering a question the setting had been given.
 *
 * font-size is deliberately absent, so the level decides it. The rest is put
 * back to the card's own: a card title wraps, and a heading's face, case and
 * leading are chosen for one line across a page. The same reason the shop's
 * card name needed it. */
.ds-post-card-title{
  margin:0;
  /* Everything comes from the level - font, size, case, tracking and leading.
     Choosing a heading level means the card title is that heading. */
  /* A card is a narrow column and these are display sizes - "Motorcycle" at
     70px is wider than the card it is in. Long words break rather than
     running out over the edge. */
  overflow-wrap:break-word;
}
/* The heading on a card, and the words under it.

   inherit rather than a color of their own where nothing is set, so a card
   left alone reads exactly as it did - the heading takes the page's heading
   color through the h3 and the text takes --ds-text. */
.ds-post-card-title,
.ds-post-card-title a{ color:var(--ds-card-head, inherit) }
.ds-post-card-title a{ text-decoration:none }

/* And on the way to being clicked. The whole card is the link - the anchor
   is stretched across it - so the hover is read off the card rather than
   off the heading, or only the words themselves would answer. */
.ds-post-card:hover .ds-post-card-title,
.ds-post-card:hover .ds-post-card-title a{
  color:var(--ds-card-head-hover, var(--ds-card-head, inherit)) }
.ds-post-card:hover .ds-post-card-text{
  color:var(--ds-card-text-hover, var(--ds-card-text, var(--ds-text))) }
.ds-post-card-meta{ margin:0 0 .4em; font-size:13px; color:var(--ds-meta, var(--ds-accent));
  letter-spacing:.04em }
.ds-post-card-text{ margin:.6em 0 0; color:var(--ds-card-text, var(--ds-text)) }

/* A ratio box, so the page does not jump when the video loads. */
.ds-video{ position:relative; width:100%; overflow:hidden; border-radius:8px }
.ds-video video,.ds-video iframe{ position:absolute; inset:0; width:100%; height:100%; border:0 }
.ds-ratio-16-9{ aspect-ratio:16/9 }
.ds-ratio-4-3 { aspect-ratio:4/3 }
.ds-ratio-1-1 { aspect-ratio:1/1 }
.ds-ratio-9-16{ aspect-ratio:9/16 }

.ds-btn-wrap{ line-height:1 }

/* Two buttons together are a pair, not a stack.
 *
 * Each button is its own element and each element is a block, so a Get started
 * beside a Talk to us came out one under the other however they were aligned -
 * the alignment was obeyed and both were still on their own line. Neighbors
 * that agree about alignment go inline so they sit side by side and wrap only
 * when there is no room.
 *
 * Full width is left alone: a button asked to fill the column means it, and a
 * second one beside it is not what was asked for. */
.ds-btn-wrap:not(.ds-align-full):has(+ .ds-btn-wrap:not(.ds-align-full)),
.ds-btn-wrap:not(.ds-align-full) + .ds-btn-wrap:not(.ds-align-full){
  display:inline-flex;
  vertical-align:top;
}

.ds-btn-wrap + .ds-btn-wrap{ margin-left:var(--ds-btn-gap,12px) }

/* An inline pair takes its line from whatever holds it, so that has to be told
 * what the buttons already say. Left needs nothing - it is where a line starts.
 *
 * Every container an element can be dropped into, not just a column. A pair of
 * centered buttons inside a Box, a Modal or a tab panel sat hard left: the wrap
 * is inline-flex, so it shrinks to the button and an inline box is placed by
 * its parent's text-align, not by its own - and .ds-align-center on the wrap
 * only ever centered the label inside the button. The setting said center, the
 * button was centered, and the pair was in the corner. */
.ds-col:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-inrow-col:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-box:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-modal-body:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center),
.ds-tab-in:has(> .ds-btn-wrap.ds-align-center + .ds-btn-wrap.ds-align-center){ text-align:center }

.ds-col:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-inrow-col:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-box:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-modal-body:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right),
.ds-tab-in:has(> .ds-btn-wrap.ds-align-right + .ds-btn-wrap.ds-align-right){ text-align:right }

/* Filling the column. justify-content because .ds-btn is an inline-flex that
   centers its own label and icon - width alone would leave them at the left. */
.ds-btn-full .ds-btn{ display:flex; width:100%; justify-content:center; text-align:center }
/* A flex line, not an inline block.
 *
 * The theme sets inline-flex with align-items:center and this rule loads after
 * it, so every button on the site came out inline-block - and align-items does
 * nothing to one. The label and the arrow then sat on the text baseline, and
 * because the arrow's box is 20px against the label's 18, the words were
 * pushed down: measured 21px above the label and 15 below it in a 53px button.
 *
 * The gap is what puts the space between a label and its mark, and the theme's
 * icon rules are written expecting it - see the note beside .ds-btn-mask. */
.ds-btn{ display:inline-flex; align-items:center; justify-content:center;
  gap:var(--ds-btnicon-gap,.6em);
  padding:.85em 1.9em; border-radius:2px;
  font-size:14px; text-decoration:none; border:1px solid var(--ds-btn-bg,#fff);
  background:var(--ds-btn-bg,#fff); color:var(--ds-btn-text,#000);
  transition:opacity .15s ease }
/* No opacity here.

   Fade is one of the hovers on the Buttons tab and it is written against
   body.ds-hover-fade, which is how a site picks it. This rule faded every
   button on every site whatever they had chosen - so a solid label went
   see-through on the way to being clicked, and the site's own hover was
   drawn on top of a fade it never asked for. */
.ds-btn:hover{ color:var(--ds-btn-text,#000) }
/* Scoped to buttons, because this is also a body class.
 *
 * The site's button preset is written onto <body> as ds-btn-<preset>, so a bare
 * .ds-btn-outline rule was not styling outline buttons - it was styling the
 * whole document. On a site whose preset is Outline every paragraph, heading
 * and menu item inherited the button's color, which is why the page text came
 * out red. The theme's own copy of this rule has always named .ds-btn; this one
 * never did. */
.ds-btn-outline .ds-btn:not(.ds-btn-solid):not(.ds-btn-secondary),
.ds-btn.ds-btn-outline{ background:transparent; color:var(--ds-btn-bg,#fff) }
.ds-btn-outline .ds-btn:not(.ds-btn-solid):not(.ds-btn-secondary):hover,
.ds-btn.ds-btn-outline:hover{ background:var(--ds-btn-bg,#fff); color:var(--ds-btn-text,#000); opacity:1 }

/* The height is written on the element now, as the figure the designer typed.
   The four named sizes are gone: a gap is a distance, not a t-shirt size. */

/* Marquee.
   The track holds four copies and travels exactly one copy's width, so the
   loop has no seam. Moving a single copy off-screen and restarting it is the
   usual approach and it visibly stutters. */
.ds-marquee{ overflow:hidden; width:100%; -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  /* The band. Every one of these is left to the theme until the element is
     given a figure of its own - see the marquee colors on Elements. */
  background:var(--ds-mq-bg, transparent);
  color:var(--ds-mq-ink, inherit);
  padding-top:var(--ds-mq-pt, 0);
  padding-bottom:var(--ds-mq-pb, 0);
  /* The theme's marquee shadow until the element chooses one of its own -
     which is what "Theme" means on the element's Shadow list. */
  box-shadow:var(--ds-shadow, none) }

/* Tilted, with the ends still off the screen.
 *
 * Rotating a strip pulls its corners inward: at ten degrees a full width band
 * shows both of its ends, and a marquee whose ends are visible has stopped
 * being endless. The width is divided by the cosine of the angle, which is
 * exactly the amount that puts them back outside, plus a screen height's worth
 * of tangent for the extra reach the band's own height adds as it leans.
 *
 * Centered with a translate rather than a margin so the widening stays
 * symmetrical whatever the angle is.
 *
 * overflow-x:clip on the wrapper rather than hidden: clip does not create a
 * scroll container, so the vertical overhang of the tilt is still drawn while
 * the horizontal ends are cut. hidden would have clipped both and sliced the
 * top and bottom off the letters. */
.ds-marquee-tilt{
  overflow:visible;
  transform:translateX(-50%) rotate(var(--ds-mq-rot, 0deg));
  margin-left:50%;
  width:calc(100% / cos(var(--ds-mq-rot, 0deg)) + 30vh * tan(abs(var(--ds-mq-rot, 0deg)))) }

/* The clip belongs to the page, not to the band - a band that clipped itself
   would cut off its own tilt. */
body:has(.ds-marquee-tilt){ overflow-x:clip }

@supports not (width: calc(1px / cos(10deg))){
  /* No trigonometry: widen by a flat third, which covers every angle the
     control allows without needing to know which one was chosen. */
  .ds-marquee-tilt{ width:134% }
}

/* Under the band. Presets rather than six numbers, because a shadow on a strip
   is a depth and not a geometry. */
.ds-marquee-sh-none  { box-shadow:none }
.ds-marquee-sh-soft  { box-shadow:0 6px 24px rgba(0,0,0,.28) }
.ds-marquee-sh-medium{ box-shadow:0 10px 34px rgba(0,0,0,.42) }
.ds-marquee-sh-strong{ box-shadow:0 16px 48px rgba(0,0,0,.6) }
.ds-marquee-track{ display:flex; width:max-content; will-change:transform;
  /* Words and logos both sit on the middle of the band. A line of type and a
     row of pictures are rarely the same height, and left to the top of the row
     the shorter of the two hangs off the top edge. */
  align-items:center;
  animation:ds-marquee 9s linear infinite }

/* The repeats are wrapped so they can be hidden from a screen reader in one
   go. display:contents takes the wrapper out of the layout again, so the track
   is the same row of items it was before the wrapper existed. */
.ds-marquee-track > [aria-hidden="true"]{ display:contents }
/* The spacing lives on the separator, not on the text.
   With padding on the item, every join carried two paddings while the joins
   written inside the text carried one space, so the point where the loop
   restarted was visibly wider than every other join - the gap the strip
   appeared to have after its last word. */
/* Leading of its own.
 *
 * The size here is a heading size and the line height was whatever the page
 * around it had - a body line box of twenty four pixels holding forty four
 * pixel letters, with the strip clipping everything that did not fit. So the
 * tops of the words were cut off. Proportional, so it holds whatever size the
 * heading scale is set to. */
.ds-marquee-item,.ds-marquee-sep{ display:block; white-space:nowrap;
  /* Its own face, size and case, all from the Marquee card on Elements.
     Each falls through to what a running line looked like before it had a
     setting: the heading font, the H2 size, and the words as typed. */
  font-family:var(--ds-mq-font, var(--ds-font-heading,inherit));
  font-size:var(--ds-mq-size, var(--ds-h2-size,44px));
  text-transform:var(--ds-mq-case, none);
  /* The line box is the type and nothing else.
   *
   * At 1.2 the extra fifth of a line is leading, and a face whose capitals sit
   * high in the em does not get it back evenly - so a band of capitals came out
   * with a strip of background under the letters and almost none above, reading
   * as text that had slipped down inside its own band. One flat line makes the
   * box the type's own height and the track centers it. */
  line-height:1;
  /* And the last of it. Even at 1 an em box carries room above the capitals for
     accents and below the baseline for descenders, and a line of capitals uses
     neither. Trimming to the cap height and the baseline fits the band to the
     letters rather than to the font's metrics. Ignored where it is not
     understood, which leaves the line at 1 and still even. */
  text-box:trim-both cap alphabetic;
  font-weight:var(--ds-h2-weight,600);
  /* The marquee's own ink first.
     The band sets `color` on the strip, but this rule sets it again on the
     words - and a color declared on an element beats one it would have
     inherited, so the Text swatch on Elements and the element's own color
     both did nothing. Read here as well, ahead of the heading color these
     fall back to. */
  color:var(--ds-mq-ink, var(--ds-heading,inherit)) }
/* Air on both sides of the separator, from the field beside it. Left unset it
   is the .35em it has always been.

   And its own color, which the mark inside it inherits. After the rule that
   colors the words rather than fighting it: both are (0,1,0), so this one
   wins by coming later. Blank falls all the way back to the text color, which
   is what the separator took before it could be colored on its own - so a
   band with a black line and a white mark between the words is now something
   that can be asked for. */
.ds-marquee-sep{ padding-inline:var(--ds-mq-sepgap, .35em);
  color:var(--ds-mq-sepink, var(--ds-mq-ink, var(--ds-heading, inherit)));
  /* And the mark takes that color too.
   *
   * .ds-icon reads --ds-icon-ink, which Theme Settings sets once for the whole
   * site - an inherited custom property, so it reaches in here past every
   * color this band has set and paints the mark with it. A white icon ink and
   * a black separator gave a white wheel on a band that had been told twice to
   * be black, with nothing on the element able to change it. Pointing the
   * token at currentColor hands the mark back to the separator's own color,
   * which is the control that exists for exactly this. */
  --ds-icon-ink:currentColor;
  /* Centered on the line rather than sitting on it.
   *
   * The separator is a block sharing a row with the words, and what is inside
   * it was inline - so an icon sat with its foot on the text baseline, which is
   * nowhere near the middle of a line of capitals. A character was no better,
   * because an asterisk is drawn high in its own em. Centering here puts the
   * mark on the middle of the band whatever it is. */
  display:flex; align-items:center; justify-content:center }

/* A mark instead of a character.
 *
 * Sized in em so it follows the marquee's own type size rather than needing a
 * figure of its own, and it takes the line's color like the words do. */
/* Sized in em so it follows the marquee's own type size, and left to take its
   color the way the words do.
   No fill here: DS_Icons writes fill and stroke as attributes, and an outline
   set says fill="none". A fill in the stylesheet beats a presentation
   attribute, so declaring one filled every outline icon in solid and turned a
   drawn mark into a blob. */
.ds-marquee-sepicon{ width:1em; height:1em; display:block }

/* The separator turning on the spot.
 *
 * The turn is on the inner span, not on .ds-marquee-sep itself: that one
 * carries the padding holding the gap open, and rotating a padded box swings
 * its padding round with it, which shoves the words either side. This turns
 * only what sits in the middle. A typed character is wrapped in the same span
 * by the renderer, so a character and a mark turn identically. */
.ds-marquee-sepin{ display:inline-block; line-height:1 }

.ds-marquee-sepspin .ds-marquee-sepin{
  animation:ds-mq-septurn var(--ds-mq-sepspin, 6s) linear infinite }

@keyframes ds-mq-septurn{ to{ transform:rotate(var(--ds-mq-septurn, 360deg)) } }

/* Turned by the scroll rather than by a clock.
 *
 * One full turn as the band crosses the window, so the mark answers the page
 * instead of running to its own time - and it stops dead when the reader does,
 * which a looping animation never does.
 *
 * Inside @supports because the declaration that makes this scroll driven is
 * animation-timeline: without it the browser keeps the animation and drops
 * only the timeline, which would spin the mark once on load and then stop -
 * the one behavior nobody asked for. No timeline, no animation. */
@supports (animation-timeline: view()){
  .ds-marquee-sepscroll .ds-marquee-sepin{
    animation:ds-mq-septurn 1s linear;
    animation-timeline:view();
  }
}

@media (prefers-reduced-motion: reduce){
  .ds-marquee-sepspin .ds-marquee-sepin,
  .ds-marquee-sepscroll .ds-marquee-sepin{ animation:none }
}
/* An empty separator still has to hold the gap open, or the words touch - but
   only until somebody says otherwise. This is (0,2,0) against the (0,1,0) of
   the rule above, so a flat .45em here beat the setting every time: a marquee
   with no separator and its gap set to zero still showed the same gap, most
   visibly at the seam where the last repeat meets the first. */
.ds-marquee-sep:empty{ padding-inline:var(--ds-mq-sepgap, .45em) }

/* The same pace as the directional marquee, which is the other running line in
   this theme and the one these were being judged against.
 *
 * That one covers half its track in 22s at medium - eleven seconds a quarter -
 * while this covered a quarter in 28, so the same word at the same setting
 * crawled here and moved there. The figures below are its durations doubled,
 * because this keyframe travels a quarter where that tween travels a half. */
.ds-marquee-slow   .ds-marquee-track{ animation-duration:16s }
.ds-marquee-medium .ds-marquee-track{ animation-duration:9s }
.ds-marquee-fast   .ds-marquee-track{ animation-duration:5s }
.ds-marquee-right  .ds-marquee-track{ animation-direction:reverse }

/* Hard edges: the line is cut off by the box rather than fading into it. The
   fade is a mask, so turning it off is removing the mask - there is nothing to
   paint over. */
.ds-marquee-hard{ -webkit-mask-image:none; mask-image:none }

/* Driven by the scroll: nothing runs on its own, so the animation goes. Left
   on, the track would be positioned by one thing and moved by another. */
.ds-marquee-scrolled .ds-marquee-track{ animation:none }

@keyframes ds-marquee{ from{ transform:translateX(0) } to{ transform:translateX(-25%) } }

/* A strip that never stops moving is a real accessibility problem, not a
   preference - so it stops, rather than merely slowing down. */
@media (prefers-reduced-motion:reduce){
  .ds-marquee-track{ animation:none; transform:none }
  .ds-marquee{ overflow-x:auto }
}

/* ---- row clipping ---- */

/* The row is cut, not covered. A wedge drawn behind it only works over a flat
   background - clipping the row itself means the shape applies to whatever it
   holds: a photograph, a running shader, text. --ds-clip is how deep the cut
   goes, set per row. */
.ds-row-clip{ --ds-clip:60px }

/* Down: the bottom edge falls left to right. Up: it rises. */
.ds-clip-diag-down{ clip-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-diag-up{ clip-path:polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - var(--ds-clip))) }

/* Both edges lean the same way, so a run of rows reads as one continuous
   diagonal rather than a stack of wedges. */
.ds-clip-diag-both{ clip-path:polygon(0 var(--ds-clip), 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }

/* A point in the middle of the bottom edge, or the top. */
.ds-clip-point-down{ clip-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-clip)), 50% 100%, 0 calc(100% - var(--ds-clip))) }
.ds-clip-point-up{ clip-path:polygon(0 var(--ds-clip), 50% 0, 100% var(--ds-clip), 100% 100%, 0 100%) }

/* A notch taken out rather than a point added. */
.ds-clip-notch-down{ clip-path:polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-notch-up{ clip-path:polygon(0 0, 50% var(--ds-clip), 100% 0, 100% 100%, 0 100%) }

/* Both edges slanted the same direction, for a row that leans. */
.ds-clip-slant-left{ clip-path:polygon(0 var(--ds-clip), 100% 0, 100% calc(100% - var(--ds-clip)), 0 100%) }
.ds-clip-slant-right{ clip-path:polygon(0 0, 100% var(--ds-clip), 100% 100%, 0 calc(100% - var(--ds-clip))) }

/* A clipped row must not leave a gap under it - the cut removes height the
   next row should occupy, so it is pulled up by the depth of the cut. */
.ds-clip-diag-down + .ds-row,
.ds-clip-diag-up + .ds-row,
.ds-clip-point-down + .ds-row,
.ds-clip-notch-down + .ds-row{ margin-top:calc(var(--ds-clip,60px) * -1) }

/* Nothing is laid out into the part being cut off.
 *
 * A clip removes pixels from an edge and nothing was keeping the content out of
 * them, so whatever landed in that band was simply cut in half. It went unnoticed
 * while everything sat at the top of its row, and appeared the moment content was
 * aligned to the bottom - which puts it precisely where the cut is.
 *
 * The row keeps its own padding and gains the depth of the cut on the edges that
 * are actually cut, so the shape eats space rather than content. Written per
 * shape because each one cuts a different edge: reserving both edges everywhere
 * would leave a band of nothing above every row whose shape only cuts below. */
/* On the inner box, which has no padding of its own, so this adds to whatever
   the row is padded by rather than replacing it - the row's own padding is
   written inline on the row itself and would win over anything here. */
.ds-row-clip.ds-clip-diag-down > .ds-row-in,
.ds-row-clip.ds-clip-diag-up > .ds-row-in,
.ds-row-clip.ds-clip-point-down > .ds-row-in,
.ds-row-clip.ds-clip-notch-down > .ds-row-in{ padding-bottom:var(--ds-clip,60px) }

.ds-row-clip.ds-clip-point-up > .ds-row-in,
.ds-row-clip.ds-clip-notch-up > .ds-row-in{ padding-top:var(--ds-clip,60px) }

/* These three lean, so both edges lose a wedge. */
.ds-row-clip.ds-clip-diag-both > .ds-row-in,
.ds-row-clip.ds-clip-slant-left > .ds-row-in,
.ds-row-clip.ds-clip-slant-right > .ds-row-in{
  padding-top:var(--ds-clip,60px);
  padding-bottom:var(--ds-clip,60px);
}

/* ---- row shape ----

   The same edges as the clip above, cut into what is behind the row instead of
   into the row. Clipping the row takes the content with it and removes height
   the next row then has to be pulled up into, which is why a diagonal there
   both sliced the words and dragged the following row over this one. A
   background layer can be cut without either happening: the row keeps its
   height, the content keeps its place, and the shape is only a shape.

   Every layer a row can be wearing is cut by the one rule - the color or
   gradient layer, the picture, the video, and the shader's canvas - so a shape
   looks the same whatever the background happens to be. */
.ds-rowshape{ --ds-shape:60px }

.ds-rowshape > .ds-row-bg,
.ds-rowshape > .ds-row-gl{ clip-path:var(--ds-shape-path, none) }

.ds-rowshape-diag-down { --ds-shape-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-diag-up   { --ds-shape-path:polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - var(--ds-shape))) }
.ds-rowshape-diag-both { --ds-shape-path:polygon(0 var(--ds-shape), 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-point-down{ --ds-shape-path:polygon(0 0, 100% 0, 100% calc(100% - var(--ds-shape)), 50% 100%, 0 calc(100% - var(--ds-shape))) }
.ds-rowshape-point-up  { --ds-shape-path:polygon(0 var(--ds-shape), 50% 0, 100% var(--ds-shape), 100% 100%, 0 100%) }
.ds-rowshape-notch-down{ --ds-shape-path:polygon(0 0, 100% 0, 100% 100%, 50% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-notch-up  { --ds-shape-path:polygon(0 0, 50% var(--ds-shape), 100% 0, 100% 100%, 0 100%) }
.ds-rowshape-slant-left{ --ds-shape-path:polygon(0 var(--ds-shape), 100% 0, 100% calc(100% - var(--ds-shape)), 0 100%) }
.ds-rowshape-slant-right{ --ds-shape-path:polygon(0 0, 100% var(--ds-shape), 100% 100%, 0 calc(100% - var(--ds-shape))) }

/* ---- background text ----
   A word set large behind the content. It sits after the picture and the
   shader canvas in the markup so it paints over them, and before .ds-row-in so
   everything in the columns stays on top of it. Deliberately unstyled beyond
   position and size - the class is shown in the builder so it can be styled in
   CSS rather than through a settings screen nobody wants to fill in. */
/* The row has to be the thing it is positioned against.
   A row is only position:relative when it carries background media or a
   shader - a plain row is static, so an absolutely positioned child skipped it
   and measured against the page instead. That put the text hundreds of pixels
   outside its own row and at the page's width rather than the row's, which is
   both of the ways this looked broken. */
.ds-row:has(> .ds-rowbgtext){ position:relative; isolation:isolate }

/* Over the media and its dim, under the content.

   z-index 1 put these level with .ds-row-dimmed::after, which is the last
   child of the row - so with nothing between them the wash painted second
   and took the lettering down with the picture. Two is above the wash and
   still below .ds-row-in. */
.ds-rowbgtext{
  position:absolute; inset:0; z-index:2;
  /* Set from the row's own Dim on the background text, and only written
     when it is asked for. */
  opacity:var(--ds-rowbgtext-dim,1);
  display:flex; pointer-events:none;
  overflow:hidden;
  /* Held to the same measure as the row's own content.

     inset:0 covers the whole row element, which for a normal width row is the
     full viewport - so the text ignored the content width entirely and ran
     edge to edge whatever the row was set to. This reproduces where
     .ds-row-in actually puts its first column.

     Which is the page margin AND the gutter. .ds-row-in is capped at --ds-max
     and then pads a gutter inside that cap, so its content starts one gutter
     further in than the cap does. The old formula stopped at the cap, so on
     any window wider than the content width the words hung a full gutter past
     the columns at both ends - and under the cap it read as the text simply
     being wider than the page.

     max with 0 rather than with the gutter, because the gutter is added
     afterwards either way - a narrow window is nought margin and one gutter,
     which is exactly what the row does. */
  padding-inline:calc(max(0px, (100% - var(--ds-max,1200px)) / 2) + var(--ds-gutter,24px));
  /* The text color the theme settings set, and nothing else.
     It used to be mixed down to a fraction of that color on the reasoning that
     a word this large wants to sit back - which is a design decision, and not
     one this stylesheet gets to make on a site whose colors are set in the
     panel. The row option below writes --ds-rowbgtext when somebody wants a
     different color; with no override it is simply the text color. */
  color:var(--ds-rowbgtext, currentColor);
  /* The full width font from Typography, which is the setting made for this.
   *
   * The theme writes --ds-font-fullwidth from that setting and falls back to
   * the heading font when it is left blank, so naming the heading font here
   * meant the setting existed, was filled in, and changed nothing: a word set
   * in a condensed display face and blown up to fill a row reads as a pattern
   * of vertical strokes rather than as a word. */
  font-family:var(--ds-font-fullwidth, var(--ds-font-heading));
  line-height:.9; font-weight:700;
  /* Room for the parts of the letters that fall outside the line box.

     A line height of .9 is shorter than the glyphs it holds - which is the
     point of it for a word used as a graphic - so the tail of a p and the
     overshoot on an o hang past the box. This one clips, and the word is
     pinned to one edge of it, so whichever edge that is takes the letters
     off: at the bottom "products" lost the bottom of every round letter and
     the whole tail of the p.

     In em of the word, not of this box, so it scales with the type - which
     is why it is on the span rather than here. */
  text-transform:var(--ds-rowbgtext-case, none);
  user-select:none;
}

/* Heading sized, the same fluid scale a hero uses. */
.ds-rowbgtext span{ padding-block:.16em }

.ds-rowbgtext-heading span{ font-size:var(--ds-rowbgtext-size, clamp(3rem,12vw,10rem)) }

/* Full width: the word stretches to fill the row whatever its length, which is
   what makes a single word read as a graphic rather than as a heading. */
.ds-rowbgtext-full span{
  display:block; width:100%;
  font-size:var(--ds-rowbgtext-size, 18vw);
  text-align:justify;
  white-space:nowrap;
}

/* A full width row keeps only its gutter, and a bleed row not even that -
   matching what .ds-row-in is given at each width. */
.ds-row-full > .ds-rowbgtext { padding-inline:var(--ds-gutter,24px) }

/* Inside a shell the row pads nothing, because the shell already has. The
   words follow it, or they sit a gutter in from columns that are not. */
.ds-shell .ds-row-normal > .ds-rowbgtext{
  padding-inline:max(0px, calc((100% - var(--ds-max,1200px)) / 2)) }
.ds-row-bleed > .ds-rowbgtext{ padding-inline:0 }

.ds-rowbgtext-hleft  { justify-content:flex-start }
.ds-rowbgtext-hcenter{ justify-content:center }
.ds-rowbgtext-hright { justify-content:flex-end }
.ds-rowbgtext-vtop   { align-items:flex-start }
.ds-rowbgtext-vmiddle{ align-items:center }
.ds-rowbgtext-vbottom{ align-items:flex-end }

/* Held against the window rather than the row, so the row scrolls past it.
 *
 * This was position:fixed, which is held against the window and nothing else:
 * the words stayed on screen for the whole page, riding over every section
 * after the row - and over the footer, which is where anybody would notice it.
 * A fixed element cannot be clipped by an ancestor that scrolls, so there was
 * no way to keep it inside its own row.
 *
 * It is a normal layer in the row now, and the words are pushed back by exactly
 * as much as the row has scrolled. They look held against the window, and they
 * are cut off with the row because that is what they are inside. */
.ds-rowbgtext-fixed span,
.ds-rowbgtext-parallax span{
  will-change:transform;
  transform:translate3d(0, var(--ds-bgtext-y, 0), 0);
}

/* Both offsets are written by bgtext.js, so with no JavaScript the words sit
   still rather than disappearing. */
@media (prefers-reduced-motion:reduce){
  .ds-rowbgtext-fixed span,
  .ds-rowbgtext-parallax span{ transform:none }
}

/* The glowing cut edge.
   A bare layer filling the row, behind everything, carrying only the glow. It
   is clipped with the row, so its shadow traces the cut - and because there is
   nothing inside it, no heading or picture gets outlined the way it did when
   the filter sat on the row itself. */
.ds-enter-glow{
  position:absolute; inset:0; z-index:0;
  pointer-events:none; display:block;
}
/* Above it, so the glow never washes over the content it is behind. */
.ds-enter > .ds-row-in{ position:relative; z-index:1 }

/* ---- quotations ----
   A blockquote with either a rule down one side or the marks themselves. The
   marks are drawn here rather than typed into the text: written into the words
   they cannot be turned off, and a screen reader reads them out.

   Sizes come from the type scale the rest of the site is set in - a pull quote
   picked out of thin air is the one thing on the page that belongs to nothing. */
/* A blockquote carries 40px of margin on both sides from the browser's own
   stylesheet, which nothing here was undoing - so a quotation sat inset from
   the column while every other element started at its edge. The element's own
   margin fields still apply when they are set; this is only the browser's. */
.ds-quote{ margin-top:0; margin-inline:0; color:var(--ds-heading) }
.ds-quote .ds-quote-text > :first-child{ margin-top:0 }
.ds-quote .ds-quote-text > :last-child{ margin-bottom:0 }

.ds-qsize-h1 .ds-quote-text{ font-size:var(--ds-h1-size); line-height:var(--ds-h1-lh,1em) }
.ds-qsize-h2 .ds-quote-text{ font-size:var(--ds-h2-size); line-height:var(--ds-h2-lh,1em) }
.ds-qsize-h3 .ds-quote-text{ font-size:var(--ds-h3-size); line-height:var(--ds-h3-lh,1em) }
.ds-qsize-h4 .ds-quote-text{ font-size:var(--ds-h4-size); line-height:var(--ds-h4-lh,1em) }
.ds-qsize-p  .ds-quote-text{ font-size:var(--ds-p-size);  line-height:var(--ds-p-lh,1.4em) }
.ds-quote .ds-quote-text{ font-family:var(--ds-font-heading) }
.ds-qsize-p .ds-quote-text{ font-family:var(--ds-font-text) }

/* Who said it. Always at reading size - a name set as large as the quotation
   competes with it. */
/* Nothing hangs below the last line. The rule down the side is exactly as tall
   as the quotation and the name it belongs to. */
.ds-quote > :last-child{ margin-bottom:0 }
/* One color for the whole line, and the quotation may set it.
 *
 * The name was the heading color and the title the accent, which is two
 * colors for one line about one person - a distinction nobody is making, and
 * the reason they never looked like they belonged together. --ds-quote-by is
 * written on the blockquote where a color has been chosen for it. */
.ds-quote-by{ display:block; margin:16px 0 0; font-style:normal;
  font-family:var(--ds-font-text); font-size:var(--ds-p-size);
  line-height:var(--ds-p-lh,1.4em); color:var(--ds-quote-by, var(--ds-accent)) }
.ds-quote-by cite{ font-style:normal }
.ds-quote-who{ color:inherit }
/* A dash between the name and the role, in the role's own color.
 *
 * On the role rather than after the name, so it takes the color the role is
 * set in - a separator that matches the name it follows reads as part of it. */
.ds-quote-role::before{ content:" - " }
.ds-quote-role:first-child::before{ content:"" }

/* 1. A rule down one side.
   2px, in the site's accent, and the gap scales with the text so it looks the
   same at every size rather than tight on a large quote and loose on a small
   one. */
.ds-quote-line{ display:flex; align-items:center; gap:0 }
.ds-quote-line.ds-qline-left { border-left:2px solid var(--ds-accent); padding-left:.7em }
.ds-quote-line.ds-qline-right{ border-right:2px solid var(--ds-accent); padding-right:.7em;
  text-align:right }
.ds-quote-line{ flex-direction:column; align-items:stretch }

/* 2. The marks themselves.
   Drawn as content on the wrapper, sized off the type scale, and set in the
   accent so they read as a mark rather than as more words. */
.ds-quote-marks{ position:relative }
/* The marks are set in the text font, not the heading font the quotation uses.
   A display face is drawn for its letters and its quote glyph is often an
   afterthought - a condensed heading font rendered the mark as a speck at the
   size it was asked for. The text font is the one face a site is guaranteed to
   have a proper mark in. */
.ds-quote-marks .ds-quote-text::before,
.ds-quote-marks .ds-quote-text::after{
  font-family:var(--ds-font-text); color:var(--ds-accent);
  line-height:.8; display:block }

/* The gap between a mark and the words, as a figure rather than as a guess.
 *
 * The eye measures ink to ink, and neither box is anywhere near its ink. A
 * quotation mark's ink sits high: at line-height .8 the box carries 0.51em of
 * empty space below the glyph. The words carry their own, and theirs changes
 * with the line-height the size class uses.
 *
 * So the margins are not the gap - they are the gap minus everything the boxes
 * already contribute, which is why they come out negative at the top. Measured
 * on Manrope at a 150px mark over 60px words: the marks sat 93px above and 81px
 * below the ink, for margins of 7.5px and 81.6px.
 *
 * Every term scales with its own font size, so this holds at any mark size and
 * any size class. It assumes the line-heights below, which are the defaults; a
 * theme that overrides --ds-h2-lh and the rest moves the words' ink and the gap
 * drifts by the difference. */
.ds-quote{ --ds-qgap:50px }

/* How far the words' ink sits inside their own line box, top and bottom.
 *
 * Two parts: the ink of the face itself, which is .15em from each edge of a
 * line box set solid, and half the leading, which is whatever the line-height
 * adds beyond the font size. One rule covers every size class because both
 * parts are read from the words themselves - em is their size and lh is their
 * line-height - rather than restated per class against a variable that only
 * says what the size was meant to be.
 *
 * Declared with @property so they are worked out here, on the words, and
 * inherit as plain pixels. An ordinary custom property is substituted as text,
 * so an em inside one would be resolved wherever it was used - and it is used
 * inside the mark, where an em is the mark's 150px and not the words' size at
 * all. That is exactly the fault this replaces: the figures were written per
 * size class against --ds-h3-size and --ds-h4-size, both of which are set in a
 * line-height of their own, so those two came out 5px loose. */
@property --ds-qink-top{ syntax:'<length>'; inherits:true; initial-value:0px }
@property --ds-qink-bottom{ syntax:'<length>'; inherits:true; initial-value:0px }

.ds-quote .ds-quote-text{
  --ds-qink-top:calc(.15em + (1lh - 1em) / 2);
  --ds-qink-bottom:calc(-.15em + (1lh - 1em) / 2);
}

.ds-quote-marks .ds-quote-text::before{
  content:var(--ds-qopen,'\201C');
  margin-bottom:calc(var(--ds-qgap,50px) - .51em - var(--ds-qink-top,0px));
}
/* Both marks follow the quotation's own alignment. The closing one was forced
   to the right, which on a centered quote threw it to the far edge of the column
   while the words sat in the middle - two things that plainly belong together,
   as far apart as the row allowed. */
.ds-qmarks-both .ds-quote-text::after{
  content:var(--ds-qclose,'\201D');
  margin-top:calc(var(--ds-qgap,50px) - var(--ds-qink-bottom,0px) - .0567em);
}

/* No guillemets. They are quotation marks in French and read as chevrons to
   everybody else, which is the one thing a quotation mark cannot afford to do. */
.ds-qglyph-double  { --ds-qopen:'\201C'; --ds-qclose:'\201D' }
.ds-qglyph-single  { --ds-qopen:'\2018'; --ds-qclose:'\2019' }
.ds-qglyph-straight{ --ds-qopen:'"';     --ds-qclose:'"' }
.ds-qglyph-block   { --ds-qopen:'\275D'; --ds-qclose:'\275E' }

/* The mark is the size it was asked for, in pixels.
 *
 * These were four rules borrowing the heading sizes, and a heading size is the
 * wrong number for a mark: a quotation glyph fills perhaps a third of its em
 * box, so the largest heading on a site drew a mark a third of that. The size
 * is its own figure now, written on the quotation. */
.ds-quote-marks .ds-quote-text::before,
.ds-quote-marks .ds-quote-text::after{ font-size:var(--ds-qmark-size,150px) }

/* And where they sit, when that is not where the words sit.
 *
 * Only written when an answer was given: with no class the marks go on taking
 * the quotation's own alignment, which is the behavior every quote on the site
 * already has. The pseudo-elements are display:block, so aligning them is
 * aligning their one glyph inside the full width of the quotation. */
.ds-qma-left   .ds-quote-text::before, .ds-qma-left   .ds-quote-text::after{ text-align:left }
.ds-qma-center .ds-quote-text::before, .ds-qma-center .ds-quote-text::after{ text-align:center }
.ds-qma-right  .ds-quote-text::before, .ds-qma-right  .ds-quote-text::after{ text-align:right }


/* ---- the Box element ---- */

/* A container with a look of its own. Everything it draws comes from the
   element's own settings as inline style, so this only has to give it a shape
   to hold and the two shadows the dropdown offers.

   No background, border or padding here on purpose: a box with nothing set is
   an invisible wrapper that groups its contents, and imposing a card on that
   would make the plain case impossible. */
/* A row inside a column.
 *
 * Twelve columns like the page's own rows, so a split written as 8-4 means the
 * same proportion here as it does out there. It is only ever one deep, so there
 * is no nesting to reason about: this grid never contains another one.
 *
 * One column on a phone. A third of a third of a screen is not a column. */
.ds-inrow{ display:grid; grid-template-columns:repeat(12,1fr);
  gap:var(--ds-gutter,24px); width:100% }
.ds-inrow-top   { align-items:start }
.ds-inrow-middle{ align-items:center }
.ds-inrow-bottom{ align-items:end }

/* An in-row laid over a scene lets the pointer through to it.

   A scene3d is dragged, and the in-row is its sibling drawn after it, so it
   covers the whole column and takes every press before the canvas sees one -
   the bike simply would not turn anywhere the words were, which is most of
   the hero. The words themselves have nothing to do with a press.

   Only where a scene is actually present, so an ordinary in-row still
   selects and hovers as it did, and only the boxes themselves - anything in
   there that can be clicked, typed in or focused takes its presses back, so
   a button over a scene still works. */
.ds-col:has(> .ds-scene3d) > .ds-inrow{ pointer-events:none }
.ds-col:has(> .ds-scene3d) > .ds-inrow :is(a, button, input, select, textarea, label, summary, [tabindex], [data-ds-fx], .ds-btn){ pointer-events:auto }
.ds-inrow-col{ min-width:0 }
.ds-inrow-col > .ds-el:last-child{ margin-bottom:0 }

@media (max-width:640px){
  .ds-inrow{ grid-template-columns:1fr }
  .ds-inrow-col{ grid-column:1 / -1 !important }
}

/* The Box element.
   Nothing is written unless a color was chosen, so a box with no settings is
   still the invisible wrapper it has always been - the variables only exist
   when the Elements tab has something in them, and an element given its own
   color in the page builder writes it inline, which beats both. */
.ds-box{
  box-sizing:border-box;
  /* Nought unless the Elements screen says otherwise, which is the
     invisible wrapper a box with no settings has always been. A box given
     an edge takes the card padding instead - the rule below, which comes
     after this one and so wins where both apply. */
  padding:var(--ds-box-pt,0) var(--ds-box-pr,0)
          var(--ds-box-pb,0) var(--ds-box-pl,0);
  background:var(--ds-box-bg, transparent);
  color:var(--ds-box-text, inherit);
  border-color:var(--ds-box-line, currentColor);
}

/* A heading inside a box takes the box's own heading color.
 *
 * Set nothing and it keeps the site's, exactly as before. Give a box a dark
 * background though and the headings in it stayed the page's heading color
 * with no way to answer that short of writing CSS - the words below them had a
 * control and the headings did not. */
.ds-box :is(h1,h2,h3,h4,h5,h6){ color:var(--ds-box-head, inherit); }

/* A box that has been given an edge gets room inside it.
 *
 * Nothing set is still an invisible wrapper - that stays true. But a border or
 * a background is somebody saying "this is a card", and a card whose words
 * start on its own border is not a style anybody chose, it is a setting they
 * were expected to find. This is the theme's answer to a card, and typing a
 * padding on the element still beats it. */
.ds-box-card{
  padding:var(--ds-box-pt,26px) var(--ds-box-pr,28px)
          var(--ds-box-pb,26px) var(--ds-box-pl,28px) }

/* The last element inside a box has nothing following it, so the margin every
   element carries below itself would show as padding the box did not ask for. */
.ds-box > .ds-el:last-child{ margin-bottom:0 }

.ds-box-soft{ box-shadow:0 2px 10px rgba(0,0,0,.10) }
.ds-box-deep{ box-shadow:0 18px 50px rgba(0,0,0,.28) }

/* The two named colors, on words.
 *
 * One class per color and the tag decides what it paints: on a span it is the
 * text, on a mark it is the highlight behind it. Both come from the theme
 * settings, so changing a color there moves every word that uses it - and the
 * secondary color, which was written into every page and used by nothing until
 * now, finally does something.
 */
.ds-accent   { color:var(--ds-accent) }
.ds-secondary{ color:var(--ds-secondary) }

mark.ds-accent,
mark.ds-secondary{
  color:inherit;
  padding:.05em .2em;
}

mark.ds-accent   { background:var(--ds-accent, mark) }
mark.ds-secondary{ background:var(--ds-secondary, mark) }

/* The whole card is the link.
   The anchor sits around the title and is stretched over the card, so the
   picture and the empty parts of the box are clickable too - and there is still
   exactly one link per card, named by its title. */
.ds-post-card{ position:relative }

.ds-post-card-link::after{
  content:''; position:absolute; inset:0; z-index:1;
  border-radius:inherit;
}

/* Anything else in a card that is meant to be clicked sits over that layer. */
.ds-post-card a:not(.ds-post-card-link),
.ds-post-card button{ position:relative; z-index:2 }

/* Text stays selectable: the overlay swallows a drag, so the pointer says what
   it is doing. */
.ds-post-card{ cursor:pointer }
.ds-post-card:hover .ds-post-card-title{ color:var(--ds-link-hover, var(--ds-link)) }

/* Code, shown rather than run.
   Sunk into the page the way code is everywhere else, and left to scroll
   sideways by default: wrapping a line of markup mid-attribute makes it harder
   to read than a scrollbar does. */
/* Room under it, like every other element has.

   margin:0 meant whatever came next sat hard against the box - and what
   comes after a code block is nearly always the thing that code produces, so
   on the shortcodes page every live example was touching its own example. The
   element gap, so it matches the text and the headings around it rather than
   being a number picked for this one place.

   Not on the last one: there is nothing under it to be apart from. */
.ds-code{
  margin:0;
  padding:14px 16px;
  overflow-x:auto;
  background:var(--ds-code-bg, color-mix(in srgb, var(--ds-text,#f5f5f5) 7%, transparent));
  border:1px solid var(--ds-code-line, color-mix(in srgb, var(--ds-text,#f5f5f5) 14%, transparent));
  border-radius:8px;
  color:var(--ds-code-ink, inherit);
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:.875em; line-height:1.6;
  tab-size:2;
}

.ds-code code{
  display:block;
  background:none; padding:0; border:0;
  font:inherit; color:inherit;
  white-space:pre;
}

.ds-el.ds-code:not(:last-child){ margin-bottom:var(--ds-el-gap,20px) }

.ds-code-wrap code{ white-space:pre-wrap; overflow-wrap:anywhere }

/* ----------------------------------------------------------------- list ---
   Markers drawn by the list rather than by the browser's own bullet, so the
   gap between the mark and the words is a measurement rather than whatever
   the user agent happens to use - and so a marker can be an icon. */
/* ul.ds-list, not .ds-list.
   The theme lays prose lists out as a grid, and that rule is a tag plus a
   class - so a single class here lost to it and every item of a list sat in a
   row. Matched at the same weight, this wins by being later. */
ul.ds-list,
ol.ds-list{ display:block }

.ds-list{
  margin:0; padding:0;
  list-style:none;
  column-gap:var(--ds-gutter,24px);
}

.ds-list li{
  position:relative;
  display:flex; align-items:baseline; gap:.6em;
  padding-left:0;
  margin-bottom:var(--ds-list-gap, .5em);
  /* A list in columns must not break an item across the gap. */
  break-inside:avoid;
}

.ds-list li:last-child{ margin-bottom:0 }

/* The bullet and the number are drawn as their own box, so a wrapped line
   indents under the words rather than under the mark. */
.ds-list-bullet li::before{
  content:'';
  flex:0 0 auto;
  width:.36em; height:.36em; margin-top:.55em;
  border-radius:50%;
  background:var(--ds-list-mark, var(--ds-counter-num, currentColor));
}

.ds-list-number{ counter-reset:ds-list }
.ds-list-number li{ counter-increment:ds-list }
/* The number takes the Number swatch on Elements, Cards, Text and marks -
   the same one the counter element uses, because they are the same thing
   drawn twice. A color set on the list itself still wins over it, and a
   list on a site that has set neither is the text color it stands in.

   No opacity. Seven tenths of a color somebody chose is a color they did
   not choose and cannot reach - a number meant to sit back can be given one
   that sits back. */
.ds-list-number li::before{
  content:counter(ds-list) '.';
  flex:0 0 auto;
  color:var(--ds-list-mark, var(--ds-counter-num, currentColor));
  font-variant-numeric:tabular-nums;
}

/* The icon keeps its own color where the Elements tab has set one, and takes
   the text color where it has not - the same rule every icon here follows. */
.ds-list-icon .ds-list-icon,
.ds-list .ds-list-icon{
  flex:0 0 auto;
  width:1em; height:1em;
  margin-top:.2em;
  color:var(--ds-list-mark, var(--ds-icon-ink, currentColor));
}

.ds-list-none li{ gap:0 }

/* The divider.
 *
 * A rule under each item and none under the last, so the list is separated
 * rather than boxed - "between items" means between them. The gap becomes
 * padding as well as margin, or the line would sit hard against the words
 * above it and float away from the ones below.
 *
 * Its color is the Elements tab's list mark where one is set, and otherwise
 * the same faint rule everything else on the page uses. */
.ds-list-ruled li{
  padding-bottom:var(--ds-list-gap, .5em);
  border-bottom:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
}

.ds-list-ruled li:last-child{ border-bottom:0; padding-bottom:0 }

/* Above and below as well: the last item keeps its rule and the list takes one
   over the top of it. */
.ds-list-ruled-all{
  border-top:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
  padding-top:var(--ds-list-gap, .5em);
}

.ds-list-ruled-all li:last-child{
  border-bottom:1px solid var(--ds-list-rule, var(--ds-line, color-mix(in srgb, currentColor 18%, transparent)));
  padding-bottom:var(--ds-list-gap, .5em);
}

/* In columns the rule belongs to the item, so each column carries its own -
   which is right, and is why the last item of a column keeps its line unless
   it is the last of the list. */

/* Centered and right aligned lists put the mark on the same side as the words,
   which is the only arrangement that reads as deliberate. */
.ds-list.ds-align-center li{ justify-content:center }
.ds-list.ds-align-right li{ justify-content:flex-end }

@media (max-width:700px){
  /* Two or three columns of list on a phone is one word per line. */
  .ds-list{ columns:1 !important }
}

/* Category badges and tags on the builder's own post card. The theme styles
   both already; the card only has to give the badges something to sit on and
   keep the body a column so Read more lands under the excerpt. */
.ds-post-card-img { position: relative }
.ds-post-card-body { display: flex; flex-direction: column }
.ds-post-card-cats { margin: 4px 0 0; opacity: .8 }
.ds-post-card-body .ds-card-more { margin-top: auto; align-self: flex-start }

/* ---------------------------------------------------------------------
 * A row running a slideshow
 *
 * The slides stack in one box and only the showing one is drawn. Everything
 * here is written so that a browser with no JavaScript, or a reader who has
 * asked for less movement, still sees the first slide whole - the words are
 * in the markup and are laid out by CSS, not placed by a script.
 * ------------------------------------------------------------------ */
.ds-row-hasshow{ position:relative; overflow:hidden }

.ds-slideshow{
  position:absolute; inset:0; z-index:0;
  /* How far through the current slide's own screen of scrolling, nought to
     one. Written by the script; used by the wipe and the push. */
  --ds-slide-at:0;
}

.ds-slide{
  position:absolute; inset:0;
  /* Stated rather than left to inset:0, so nothing else can take it.
     The slider effect used to declare a bare .ds-slide of its own, and
     effects.css loads after this file, so its clamp won and every slide here
     drew 380px wide. That rule is scoped to its own track now; this says the
     width outright so the next component to reach for the name cannot do it
     again. */
  width:100%; height:100%;
  display:grid;
  opacity:0; visibility:hidden;
  transition:opacity .8s cubic-bezier(.2,0,.2,1);
}
.ds-slide.is-on{ opacity:1; visibility:visible }

/* The picture or the video behind one slide. */
.ds-slide-media{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  display:block;
}

/* The row's Dim, over one slide's picture and under its words. Opacity is
   written on the element, because it is the row's figure rather than a
   constant. */
.ds-slide-dim{
  position:absolute; inset:0; z-index:0;
  background:#000; pointer-events:none;
}

/* Words over a wash are white, whatever the page's own text color is. A dim
   is only ever asked for to make words readable over a picture, so the two
   go together - and on a light theme the words were the page's near black
   over a black wash.

   The heading is named as well as the wrapper. It carries the theme's own
   heading color rather than inheriting, so setting the wrapper turned the
   paragraph white and left the headline near black on black. */
.ds-slide:has(.ds-slide-dim) .ds-slide-words,
.ds-slide:has(.ds-slide-dim) .ds-slide-title,
.ds-slide:has(.ds-slide-dim) .ds-slide-sub,
.ds-slide:has(.ds-slide-dim) .ds-slide-btn{ color:#fff }

/* ---- where the words sit ---- */

.ds-slide-words{
  position:relative; z-index:1;
  display:grid; gap:clamp(12px,1.6vw,22px);
  align-content:center;
  padding:clamp(24px,6vw,90px);
  max-width:min(46rem, 90%);
}

.ds-slides-hleft   .ds-slide-words{ justify-self:start;  text-align:left }
.ds-slides-hcenter .ds-slide-words{ justify-self:center; text-align:center }
.ds-slides-hright  .ds-slide-words{ justify-self:end;    text-align:right }

.ds-slides-vtop    .ds-slide-words{ align-self:start }
.ds-slides-vmiddle .ds-slide-words{ align-self:center }
.ds-slides-vbottom .ds-slide-words{ align-self:end }

/* A button is not a block, so it does not stretch the width of the words. */
.ds-slides-hleft   .ds-slide-btn{ justify-self:start }
.ds-slides-hcenter .ds-slide-btn{ justify-self:center }
.ds-slides-hright  .ds-slide-btn{ justify-self:end }

/* ---- the parts ---- */

/* What a masked part moves inside. Clipping is the whole effect: the line is
   uncovered rather than flown in, so it needs a box with an edge. */
.ds-slide-mask{ display:block; overflow:hidden }

.ds-slide-title{ margin:0; line-height:1.05 }
.ds-slide-sub{ margin:0; opacity:.86 }

/* ---- how one background becomes the next ---- */

/* Crossfade is the plain one and is already what .ds-slide does. */

/* Pushed: the new slide arrives from the edge and the old one leaves. Driven
   by the same figure the script writes, so it follows the pointer. */
.ds-slideshow-push .ds-slide{ transition:none; transform:translate3d(0,100%,0) }
.ds-slideshow-push .ds-slide.is-on{ transform:translate3d(0,0,0) }

/* Wiped: the incoming slide is revealed by a moving edge rather than faded.
   clip-path rather than a mask image, so there is nothing to load. */
.ds-slideshow-wipe .ds-slide{ transition:none; clip-path:inset(100% 0 0 0) }
.ds-slideshow-wipe .ds-slide.is-on{
  clip-path:inset(calc((1 - var(--ds-slide-at,1)) * 0%) 0 0 0);
}

/* Zoomed: the outgoing picture grows and the incoming one settles back. */
.ds-slideshow-zoom .ds-slide{ transform:scale(1.12) }
.ds-slideshow-zoom .ds-slide.is-on{
  transform:scale(1);
  transition:opacity .8s cubic-bezier(.2,0,.2,1), transform 1.2s cubic-bezier(.2,0,.2,1);
}

/* ---- the dots ---- */

/* Where the markers sit, across and down - set on the row, the same two
   answers the words take. Inset from the edge by the same figure whichever
   corner they are in, so moving them never changes how far in they are. */
.ds-slide-dots{
  position:absolute; z-index:2;
  --ds-pip-inset:clamp(16px,3vw,34px);
  display:flex; gap:10px;
}

.ds-pips-hleft   { left:var(--ds-pip-inset); justify-content:flex-start }
.ds-pips-hcenter { left:0; right:0; justify-content:center }
.ds-pips-hright  { right:var(--ds-pip-inset); justify-content:flex-end }

.ds-pips-vtop    { top:var(--ds-pip-inset) }
.ds-pips-vbottom { bottom:var(--ds-pip-inset) }
/* Down the middle, and up the side rather than across it - a vertical strip
   is what a marker set in the middle of an edge wants to be. */
.ds-pips-vmiddle { top:50%; transform:translateY(-50%); flex-direction:column }

.ds-slide-dot{
  width:9px; height:9px; padding:0;
  border:0; border-radius:999px;
  background:rgba(255,255,255,.4);
  cursor:pointer;
  transition:background .3s, transform .3s;
}
.ds-slide-dot.is-on{ background:#fff; transform:scale(1.35) }

/* A slideshow is a picture with words over it, and a phone turns the picture
   into a tall crop - so the words need room rather than the middle of it. */
@media (max-width:700px){
  .ds-slide-words{ max-width:100%; padding:clamp(20px,7vw,40px) }
}

/* Asked to keep still: the first slide, and nothing moving. */
@media (prefers-reduced-motion: reduce){
  .ds-slide{ transition:none }
  .ds-slideshow-push .ds-slide,
  .ds-slideshow-zoom .ds-slide{ transform:none }
  .ds-slideshow-wipe .ds-slide{ clip-path:none }
}

/* ---------------------------------------------------------------------
 * Scrubbed: the scroll is the playhead
 *
 * Every slide is stacked and visible, and a GSAP timeline decides what is
 * seen at each position. The stylesheet must not move the same properties -
 * a CSS transition and a scrubbed tween on one property is the two of them
 * fighting frame by frame, which reads as a stutter.
 * ------------------------------------------------------------------ */
.ds-slideshow.is-scrub .ds-slide{
  transition:none;
  visibility:visible;
}

/* Each way of changing, handed over to the timeline. */
.ds-slideshow.is-scrub.ds-slideshow-push .ds-slide,
.ds-slideshow.is-scrub.ds-slideshow-zoom .ds-slide{ transform:none }
.ds-slideshow.is-scrub.ds-slideshow-wipe .ds-slide,
.ds-slideshow.is-scrub.ds-slideshow-wipe .ds-slide.is-on{ clip-path:none }

/* ---- what marks where you are ---- */

/* Numbers say how many there are, which a row of identical dots never does. */
.ds-slide-dots-numbers .ds-slide-dot{
  width:auto; height:auto;
  min-width:26px; padding:3px 7px;
  border-radius:4px;
  font:600 12px/1.4 inherit;
  color:rgba(255,255,255,.7);
  background:rgba(255,255,255,.14);
}
.ds-slide-dots-numbers .ds-slide-dot.is-on{
  color:#000; background:#fff; transform:none;
}

.ds-slide-dots-squares .ds-slide-dot{ border-radius:2px; width:10px; height:10px }

/* A strip that reads as progress rather than as a set of buttons. */
.ds-slide-dots-bars .ds-slide-dot{
  width:34px; height:3px; border-radius:0;
}
.ds-slide-dots-bars .ds-slide-dot.is-on{ transform:none }

/* Blinds: bands that grow rather than one edge that travels. No single
   clip-path can describe a row of separate slats, so this is a repeating mask
   whose black bands widen from nothing to the full slat - driven by one custom
   property the timeline animates. */
.ds-slideshow-blinds .ds-slide{
  --ds-blind:0%;
  -webkit-mask-image:repeating-linear-gradient(to right,
    #000 0, #000 var(--ds-blind), transparent var(--ds-blind), transparent 12.5%);
  mask-image:repeating-linear-gradient(to right,
    #000 0, #000 var(--ds-blind), transparent var(--ds-blind), transparent 12.5%);
}
.ds-slideshow-blinds .ds-slide:first-child{ --ds-blind:12.5% }

/* ---------------------------------------------------------------------
 * Tabs and accordions
 *
 * One set of rules for both. They are the same thing shown two ways: named
 * panels, one or more of them open. Everything a site can set arrives as a
 * variable, so this file states shapes and never colors.
 * ------------------------------------------------------------------ */
.ds-panels{ --ds-tab-radius:0px; --ds-tab-bw:1px }

/* Two edges, drawn from two figures. Where a site sets only one color the
   other falls back to it, so one border is one setting and two is two. */
.ds-tabs-look-filled .ds-tab{
  border-color:var(--ds-tab-line, transparent);
}
.ds-tab-bodies{
  border:var(--ds-tab-bw) solid var(--ds-tab-bline, var(--ds-tab-line, transparent));
}
/* A plain set has no box round its body - the rule under the strip is the
   whole device, and a border round the content turns it into a card. */
.ds-tabs-look-plain .ds-tab-bodies{ border:0 }
.ds-panels-accordion .ds-tab-bodies{ border:0 }
.ds-panels-accordion .ds-tab{ border:0 }

.ds-tab-strip{ display:flex; flex-wrap:wrap; gap:2px }

.ds-tab{
  display:inline-flex; align-items:center; gap:.5em;
  /* No side padding by default - the rule under a tab runs the full width
     of the element, so 18px at each end set every question in from its own
     line. Vertical air only; the sides are the row's gutter. */
  padding:var(--ds-tab-pad, 12px 0);
  /* The width comes from the element; the color is set per look below. */
  border:var(--ds-tab-bw) solid transparent; background:none; cursor:pointer;
  font:inherit; text-align:left;
  color:var(--ds-tab-ink, inherit);
  border-radius:var(--ds-tab-radius) var(--ds-tab-radius) 0 0;
}
.ds-tab.is-on{ color:var(--ds-tab-ink-on, inherit) }

/* The chosen text style. A class rather than a real heading tag - a strip of
   six h2s is a document outline nobody meant to write. */
.ds-tab-h1 span{ font-size:var(--ds-h1-size, 2.4em) }
.ds-tab-h2 span{ font-size:var(--ds-h2-size, 2em) }
.ds-tab-h3 span{ font-size:var(--ds-h3-size, 1.6em) }
.ds-tab-h4 span{ font-size:var(--ds-h4-size, 1.3em) }
.ds-tab-h5 span{ font-size:var(--ds-h5-size, 1.1em) }
.ds-tab-h6 span{ font-size:var(--ds-h6-size, 1em) }

/* Plain: a rule under the strip and a mark under the open one. */
.ds-tabs-look-plain .ds-tab-strip{
  border-bottom:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}
/* The mark under the open tab. A strip only - an accordion has no strip, and
   the same rule there drew a bright line under whichever header was open,
   in currentColor, which on an open header is the text color. */
.ds-tabs-look-plain:not(.ds-panels-accordion) .ds-tab{ margin-bottom:-1px; border-bottom:2px solid transparent }
.ds-tabs-look-plain:not(.ds-panels-accordion) .ds-tab.is-on{ border-bottom-color:var(--ds-tab-ink-on, currentColor) }

/* Filled: the open tab and the body are one shape, which is what makes a tab
   read as a folder rather than as a row of links. */
.ds-tabs-look-filled .ds-tab{ background:var(--ds-tab-bg, rgba(128,128,128,.12)) }
.ds-tabs-look-filled .ds-tab.is-on{ background:var(--ds-tab-bg-on, var(--ds-card-bg, #222)) }
.ds-tabs-fill .ds-tab-bodies{ background:var(--ds-tab-bg-on, var(--ds-card-bg, #222)) }

.ds-tab-body{ padding:var(--ds-tab-bodypad, 20px 0) }
.ds-tab-body[hidden]{ display:none }

/* ---- where the strip sits ---- */

.ds-tabs-at-left,
.ds-tabs-at-right{ display:flex; align-items:flex-start; gap:0 }
.ds-tabs-at-left .ds-tab-strip,
.ds-tabs-at-right .ds-tab-strip{ flex-direction:column; flex:0 0 auto }
.ds-tabs-at-left .ds-tab-bodies,
.ds-tabs-at-right .ds-tab-bodies{ flex:1 1 auto; min-width:0 }
.ds-tabs-at-right{ flex-direction:row-reverse }
.ds-tabs-at-bottom{ display:flex; flex-direction:column-reverse }

/* Down one side the tab's corners and its mark turn with it. */
.ds-tabs-at-left .ds-tab,
.ds-tabs-at-right .ds-tab{ border-radius:var(--ds-tab-radius) }
.ds-tabs-look-plain.ds-tabs-at-left .ds-tab-strip,
.ds-tabs-look-plain.ds-tabs-at-right .ds-tab-strip{ border-bottom:0 }
.ds-tabs-look-plain.ds-tabs-at-left .ds-tab-strip{
  border-right:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}
.ds-tabs-look-plain.ds-tabs-at-right .ds-tab-strip{
  border-left:1px solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}

/* ---- how the tabs line up ---- */

.ds-tabs-align-center .ds-tab-strip{ justify-content:center }
.ds-tabs-align-right  .ds-tab-strip{ justify-content:flex-end }
.ds-tabs-align-stretch .ds-tab-strip .ds-tab{ flex:1 1 0; justify-content:center }

/* ---- how a panel arrives ---- */

.ds-tabs-anim-fade .ds-tab-body.is-on{ animation:ds-tab-fade .35s cubic-bezier(.2,0,.2,1) both }
@keyframes ds-tab-fade{ from{ opacity:0 } to{ opacity:1 } }

.ds-tabs-anim-slide .ds-tab-body.is-on{ animation:ds-tab-slide .4s cubic-bezier(.2,0,.2,1) both }
@keyframes ds-tab-slide{ from{ opacity:0; transform:translateY(10px) } to{ opacity:1; transform:none } }

@media (prefers-reduced-motion: reduce){
  .ds-tab-body.is-on{ animation:none }
}

/* ---- an accordion ---- */

/* The rule between one fold and the next - the accordion's only line, and
   what Tab border sets. */
.ds-panels-accordion .ds-tab-fold{
  border-bottom:var(--ds-tab-bw) solid var(--ds-tab-line, var(--ds-line, rgba(128,128,128,.3)));
}

/* Nothing under the last one: a list of folds should not end in a rule with
   nothing below it. */
.ds-panels-accordion .ds-tab-fold:last-child{ border-bottom:0 }
.ds-panels-accordion .ds-tab{ width:100%; justify-content:space-between }

/* The marker, drawn rather than loaded - a chevron is two lines and a plus is
   two more, and neither is worth a font. */
.ds-panels-accordion .ds-tab::after{
  content:'';
  width:.6em; height:.6em; flex:0 0 auto;
  transition:transform .3s cubic-bezier(.2,0,.2,1);
}
.ds-acc-mark-chevron .ds-tab::after{
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(45deg); margin-top:-.2em;
}
.ds-acc-mark-chevron .ds-tab.is-on::after{ transform:rotate(-135deg); margin-top:.2em }
.ds-acc-mark-arrow .ds-tab::after{
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
  transform:rotate(-45deg);
}
.ds-acc-mark-arrow .ds-tab.is-on::after{ transform:rotate(45deg) }
.ds-acc-mark-plus .ds-tab::after{
  background:
    linear-gradient(currentColor,currentColor) center/100% 2px no-repeat,
    linear-gradient(currentColor,currentColor) center/2px 100% no-repeat;
}
.ds-acc-mark-plus .ds-tab.is-on::after{ background-size:100% 2px, 0 100% }
.ds-acc-mark-none .ds-tab::after{ display:none }

/* On the left it goes first, and the name takes the room instead. */
.ds-acc-at-left .ds-tab{ flex-direction:row-reverse; justify-content:flex-end }
.ds-acc-at-left .ds-tab span{ margin-right:auto }

/* ---- a folded set of tabs ---- */

/* Below the phone width the strip is put away and each name sits on its own
   panel. The markup does not change; this is the same set, folded. */
.ds-panels.is-folded .ds-tab-strip{ display:none }
.ds-panels.is-folded .ds-tab-bodies{ display:block }

/* ---- where you are ---- */

.ds-tab-pips{ display:flex; gap:8px; justify-content:center; margin-top:14px }
.ds-tab-pip{
  width:10px; height:10px; padding:0; border:0; cursor:pointer;
  background:var(--ds-tab-line, rgba(128,128,128,.4));
  border-radius:999px;
  transition:background .3s, transform .3s;
}
.ds-tab-pips-square .ds-tab-pip{ border-radius:2px }
.ds-tab-pips-lines  .ds-tab-pip{ width:28px; height:3px; border-radius:0 }
.ds-tab-pips-number .ds-tab-pip{
  width:auto; height:auto; min-width:24px; padding:2px 6px;
  border-radius:3px; font:600 12px/1.4 inherit;
  color:var(--ds-tab-ink, inherit);
}
.ds-tab-pip.is-on{ background:var(--ds-tab-ink-on, currentColor); transform:scale(1.2) }
.ds-tab-pips-lines .ds-tab-pip.is-on,
.ds-tab-pips-number .ds-tab-pip.is-on{ transform:none }

/* Where a name sits inside its own tab.
 *
 * A different question from where the tabs sit in the strip: a stretched tab
 * is wide, and what its label does with that width is the thing people are
 * actually looking at. An accordion header is always full width, so this is
 * the only control it has.
 */
.ds-tablabel-left   .ds-tab{ justify-content:flex-start; text-align:left }
.ds-tablabel-center .ds-tab{ justify-content:center; text-align:center }
.ds-tablabel-right  .ds-tab{ justify-content:flex-end; text-align:right }

/* The marker keeps its own edge whatever the words do - it belongs to the row,
   not to the label, and a plus sliding about with the text reads as broken. */
.ds-panels-accordion .ds-tab{ position:relative; padding-right:2em }
.ds-panels-accordion.ds-acc-at-left .ds-tab{ padding:0 0 0 2em }
.ds-panels-accordion .ds-tab::after{ position:absolute; right:0; top:50% }
.ds-acc-at-left .ds-tab::after{ right:auto; left:0 }
.ds-acc-mark-chevron .ds-tab::after{ transform:translateY(-70%) rotate(45deg) }
.ds-acc-mark-chevron .ds-tab.is-on::after{ transform:translateY(-30%) rotate(-135deg) }
.ds-acc-mark-arrow .ds-tab::after{ transform:translateY(-50%) rotate(-45deg) }
.ds-acc-mark-arrow .ds-tab.is-on::after{ transform:translateY(-50%) rotate(45deg) }
.ds-acc-mark-plus .ds-tab::after{ transform:translateY(-50%) }

/* The drawing sits on the text baseline rather than floating above it. */
.ds-tab-icon{ display:inline-flex; align-items:center; flex:0 0 auto }
.ds-tab-icon svg{ display:block }

/* A question wrapped in a real heading.
 *
 * The heading is the place in the document; the button inside it is the
 * control. It must add no space of its own or the accordion grows a gap per
 * row that no setting accounts for. */
/* The heading keeps its own type.

   This set font-family, font-size and font-weight to inherit, and the class
   is on the heading itself - so choosing h6 for a tab gave the tag without
   any of the typography, and the question rendered as body text. The button
   inside still inherits, which is what keeps a button from looking like one.
 */
/* No margin, and no opinion about type - see the note above the element. */
.ds-tab-head{ margin:0 }

/* The button takes its type from the heading it sits in, so a tab styled
   large or h6 looks that way while still being a control. */
.ds-tab-head .ds-tab{ width:100%; font:inherit; color:inherit; letter-spacing:inherit; text-transform:inherit }

/* The Sticky element.
 *
 * One property does all of it. The two things worth knowing are written here
 * rather than left to be discovered:
 *
 * align-self:start - a sticky child of a grid or flex column is stretched to
 * the full height of that column by default, and something already as tall as
 * the space it could travel through never appears to move at all. This is the
 * single most common reason sticky "does not work".
 *
 * It is released where its parent ends. That is how sticky is defined, and it
 * is also the behavior wanted: a summary beside an article should stop when
 * the article does rather than follow into the footer.
 */
.ds-sticky{
  position:sticky;
  top:var(--ds-stick-top,0px);
  align-self:start;
}

/* Where the script has taken over.
 *
 * The script holds the element's line itself, so the resting offset above is
 * its answer to a question already answered - and worse, top on a relative
 * element is not an offset from the screen but a shove down the page, which is
 * what put a header's height of empty space above every sticky element. This is
 * the only place either of them is decided: sticky on its own where there is no
 * GSAP, and the script wherever there is. */
.ds-sticky.is-pinned{ position:relative; top:auto }

/* Off where a column is the full width of the screen: a thing that stays put
   there covers what is being read.

   1024, not 640. The setting is called "Disable on tablet / mobile" and 640 is
   a phone - so a tablet, which is 768 to 1024, stayed above the line and the
   element carried on sticking on exactly the screens the setting names. Same
   figure sticky.js uses, or the two would disagree about what a tablet is. */
@media (max-width:1024px){
  .ds-sticky-desk,
  .ds-sticky-desk.is-pinned{ position:static }
}


/* Tabs and accordions, dressed from Elements > Tabs & Accordions.
   Each falls back to what the element did before, so a site that has set
   none of them is unchanged. */
.ds-tab{ background:var(--ds-tab-bg, none) }
.ds-tab.is-on{ background:var(--ds-tab-bg-on, var(--ds-tab-bg, none)) }
.ds-tab span{ font-size:var(--ds-tab-size, inherit) }
.ds-tab-body{
  background:var(--ds-tab-body-bg, transparent);
  color:var(--ds-tab-body-ink, inherit);
  font-size:var(--ds-tab-body-size, inherit);
}

/* ==============================================================
 * Bento rows
 *
 * A column layout gives every column the same height, which is what makes it a
 * row of columns. A bento gives each tile a width AND a height in the same
 * twelfths, so the row becomes a packed set of tiles of different sizes.
 *
 * The grid underneath is the one the rows already use, so nothing here fights
 * it: twelve tracks across, and rows of a set height added as needed.
 * ============================================================== */
.ds-row-bento > .ds-row-in{
  /* A height for a row to be a multiple of. Without one, "two rows tall" has
     nothing to be twice as tall as - grid sizes rows to their contents and two
     tiles of different content give two different answers. minmax lets a tile
     grow past it rather than clip, which matters the day somebody puts four
     paragraphs in one. */
  grid-auto-rows: minmax(var(--ds-bento-unit, 220px), auto);

  /* The same gutter both ways. A bento reads as a bento because the space
     between tiles is even; a column gap alone leaves the rows touching. */
  gap: var(--ds-col-mx, 24px);
}

/* Tiles fill their cell rather than sitting at the top of it - but only while
   nothing has asked them to sit somewhere.
 *
 * height:100% was written on every tile unconditionally, and a box with an
 * explicit height fills its cell whatever align-items says. So choosing Top,
 * Middle or Bottom did nothing at all: the control was there, it saved, and the
 * tiles stayed full height with nothing to say why.
 *
 * Vertical alignment is the control that places a tile in its cell on a bento,
 * so the full height only applies while that is left at the default. */
.ds-row-bento > .ds-row-in > .ds-col{ min-width: 0 }
.ds-row-bento.ds-cols-stretch.ds-row-vmiddle > .ds-row-in > .ds-col{ height: 100% }

/* Where a tile sits in its cell.
 *
 * The row's Vertical alignment, which on a full screen row moves the whole block
 * of columns within the window. A bento has the same question one level down: a
 * tile shorter than its grid cell can sit at the top of it, in the middle, or at
 * the bottom. Same control, same words, answered where it means something.
 *
 * Middle is the stored default for rows that never set it, so it keeps the old
 * behavior of tiles filling their cells - only an explicit Top or Bottom
 * changes what a page already looked like. */
.ds-row-bento.ds-row-vtop    > .ds-row-in{ align-items: start }
.ds-row-bento.ds-row-vbottom > .ds-row-in{ align-items: end }
.ds-row-bento.ds-row-vtop    > .ds-row-in > .ds-col,
.ds-row-bento.ds-row-vbottom > .ds-row-in > .ds-col{ height: auto }

/* How many rows a tile stands. Written out rather than generated, because the
   shapes only ever ask for these. */
.ds-row-bento > .ds-row-in > .ds-col-t2{ grid-row: span 2 }
.ds-row-bento > .ds-row-in > .ds-col-t3{ grid-row: span 3 }
.ds-row-bento > .ds-row-in > .ds-col-t4{ grid-row: span 4 }

/* Where a Swiss tile sits.
 *
 * The one thing this grid could not do before: a tile that starts at a named
 * column rather than packing against the one before it. The empty columns
 * either side are not columns, they are the grid showing through, and that is
 * the whole of the style - structural white space, placed on purpose.
 *
 * The figures arrive as variables on the element so this stays a class rule
 * that a media query can still overrule. Written inline as placement it could
 * not be, and all eighteen would hold their desktop shape on a phone.
 *
 * Both fall back to auto, so a tile naming no start simply flows and a Swiss
 * row half rebuilt by hand behaves like any other row. */
.ds-row-swiss > .ds-row-in > .ds-col-at{
  grid-column: var(--ds-at, auto) / span var(--ds-span, 12);
}
.ds-row-swiss > .ds-row-in > .ds-col-at[style*="--ds-atrow"]{
  grid-row: var(--ds-atrow, auto) / span var(--ds-down, 1);
}

/* The rail that holds is marked .ds-sticky by the renderer and driven by
   sticky.js, the same as every other sticky thing on a page. It needs only one
   thing here: a grid item stretches to its cell by default, so a sticky one is
   already as tall as the space it could travel through and never appears to
   move. */
.ds-row-swiss-rail > .ds-row-in > .ds-col-at.ds-sticky{ align-self: start }

/* Below the tablet breakpoint the row is already one column wide, so a tile
   that stands two rows would be two screens tall for no reason. Heights are
   given up with the widths. */
@media (max-width: 1024px){
  .ds-row-bento > .ds-row-in{ grid-auto-rows: auto }
  .ds-row-bento > .ds-row-in > .ds-col,
  .ds-row-bento > .ds-row-in > .ds-col-t2,
  .ds-row-bento > .ds-row-in > .ds-col-t3,
  .ds-row-bento > .ds-row-in > .ds-col-t4{
    grid-row: auto;
    height: auto;
  }

  /* A placed tile gives up its placement along with everything else. The void
     it held open has nowhere to go in one column, and a tile still starting at
     column nine on a grid one column wide does not draw at all. */
  .ds-row-swiss > .ds-row-in > .ds-col-at,
  .ds-row-swiss > .ds-row-in > .ds-col-at[style*="--ds-atrow"]{
    grid-column: 1 / -1;
    grid-row: auto;
  }

  /* And the rail stops holding. On one column it would pin a block over the
     content it was meant to sit beside. sticky.js reads the same breakpoint
     from the desk flag on the element; this covers the CSS fallback. */
  .ds-row-swiss-rail > .ds-row-in > .ds-col-at.ds-sticky{
    position: static;
  }

  /* Except where there is something behind to see.
   *
   * Giving the heights up leaves a tile exactly as tall as the three words in
   * it, and a photograph in a band that thin is a stripe rather than a picture.
   * A floor rather than a height, so a tile with more in it still grows.
   *
   * Any tile carrying a background of its own, not only the ones holding a
   * picture. A tile is a shape somebody chose - the color, the gradient, the
   * card - and three words on a colored band a hundred and forty pixels tall
   * has stopped being a tile and become a stripe, the same way the photograph
   * did. A tile with no background at all is not drawing a shape and loses
   * nothing by being the size of its contents, which is what it wants to be,
   * so it is the one case left out. */
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-pic),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-vid),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-model),
  .ds-row-bento > .ds-row-in > .ds-col:has(> .ds-col-fill),
  .ds-row-bento > .ds-row-in > .ds-col.ds-col-bg,
  .ds-row-bento > .ds-row-in > .ds-col.ds-col-grad{
    min-height: min(var(--ds-bento-unit, 220px), 56vw);
  }
}

/* ==============================================================
 * A column as a surface
 *
 * A column was a div with a width. It can now carry a color, a gradient, a
 * picture, footage or a turnable model, with its own padding and its own idea
 * of where its contents sit - which is what a bento tile has to be, and what a
 * plain column benefits from just as much.
 *
 * The corner is --ds-card-radius, set once in Theme Settings. Nothing here
 * chooses one: a tile turning a different corner from every other card on the
 * site is not a decision anybody makes on purpose.
 * ============================================================== */
.ds-col-bg,
.ds-col-grad,
.ds-col-surface,
.ds-col-line,
.ds-col-pad,
.ds-col:has(> .ds-col-pic),
.ds-col:has(> .ds-col-vid),
.ds-col:has(> .ds-col-model){
  position: relative;
  border-radius: var(--ds-card-radius, 12px);
}

/* The tile clips what is behind, never what is in it.
 *
 * A radius clips an element's own background on its own - overflow is only ever
 * needed for children, and the only children that need it are the layers. Put
 * on the column instead, it also cut off everything the author had put in the
 * tile, which is what made a picture poking out of the top impossible.
 *
 * So each layer clips itself and the column is left open, and a breakout is
 * then a negative margin on the picture and nothing more. */
.ds-col-pic,
.ds-col-vid,
.ds-col-model{
  overflow: hidden;
  border-radius: inherit;
}

/* Except when the tile is asked to hold its contents in.
 *
 * Wanted where a tile is a frame around a photograph rather than a shape a
 * design breaks out of - and it has to be said, because the two are opposite
 * and nothing about a column tells you which one it is. */
.ds-col-clip{ overflow: hidden }

.ds-col-bg{ background-color: var(--ds-colbg) }
.ds-col-grad{ background-image: var(--ds-colgrad) }

/* What is written on the tile, whatever that is - a heading, the words, a link,
   a list marker. Headings and links each carry their own color from the theme,
   so they are named: inheriting alone left a heading black on a black tile. */
.ds-col-ink,
.ds-col-ink :is(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, strong, em, small){
  color: var(--ds-colink);
}
/* A link keeps being a link - underlined, and still its own color on hover -
   but it is legible against the tile it is on. */
.ds-col-ink a:not(.ds-btn){ color: var(--ds-colink) }

/* A surface with nothing else asked of it still reads as a tile. Taken from the
   card color so it follows the palette rather than being a gray of its own. */
.ds-col-surface{ background-color: var(--ds-card-bg, rgba(127,127,127,.08)) }
.ds-col-line{ border: var(--ds-card-bw, 1px) solid var(--ds-line, rgba(127,127,127,.28)) }

/* What a column is padded by when it has not been told.
 *
 * Nothing, for an ordinary column - it usually has no background and sits in a
 * row that already has gutters. But a bento tile is a surface with a color or a
 * picture running to its own edge, so it has to be inset or the words sit on the
 * seam. There was no default at all, which meant setting the same figure by hand
 * on every tile of every bento - and since a cut-out inherits the tile's
 * padding, a tile that had never been given one cut nothing.
 *
 * Written as a fallback rather than as a competing rule. --ds-colpad is only
 * emitted when a tile has its own padding, so this resolves to the tile's figure
 * where there is one and to the Elements, Bento figure where there is not. No
 * specificity to get wrong: the override wins by existing. */
.ds-col-in{ --ds-pad-base: 0px }
.ds-row-bento > .ds-row-in > .ds-col > .ds-col-in{ --ds-pad-base: var(--ds-bento-pad, 20px) }
.ds-col-in{ padding: var(--ds-colpad, var(--ds-pad-base)) }
.ds-col-pad > .ds-col-in{ padding: var(--ds-colpad) }

/* Room outside the tile. Written on the tile, not on the well inside it, so the
   gap falls between two tiles rather than inside both of them. */
.ds-col-mar{ margin: var(--ds-colmar) }

/* The layers behind the content. Absolute so they fill the tile whatever is in
   it, and under the content rather than over it. */
.ds-col-pic,
.ds-col-vid,
.ds-col-model{
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  background-repeat: no-repeat;
  pointer-events: none;
}

.ds-col-vid video{
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* The model is turnable, so it is the one layer that takes the pointer. */
.ds-col-model{ pointer-events: auto; cursor: grab }
.ds-col-model:active{ cursor: grabbing }
.ds-col-model canvas{ width: 100%; height: 100%; display: block }

/* Anything over the background sits above it. */
.ds-col-in{ position: relative; z-index: 1; height: 100% }

/* Letting the drag reach the model underneath.
 *
 * The content wrapper is the full height of the tile and sits over every layer,
 * so it caught the pointer across the whole frame and the model could never be
 * taken hold of - the cursor said grab and nothing turned. Only the wrapper
 * steps aside: everything actually in it takes the pointer back, so the words
 * are still selectable and a button in a tile still a button. The empty space
 * around them is what the drag goes through.
 *
 * Only on a tile that has a model. Anywhere else there is nothing underneath
 * worth reaching and this would be machinery for no reason. */
.ds-col:has(> .ds-col-model) > .ds-col-in{ pointer-events: none }
.ds-col:has(> .ds-col-model) > .ds-col-in > *{ pointer-events: auto }

/* What is behind, held back so what is over it can be read. A layer of the page
   color rather than black, so it works whichever way round the scheme is. */
.ds-col-dimmed > .ds-col-pic::after,
.ds-col-dimmed > .ds-col-vid::after,
.ds-col-dimmed::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--ds-bg, #000);
  opacity: var(--ds-coldim, 0);
  pointer-events: none;
}

/* Where the content sits, said by the tile rather than by the row.
 *
 * The row's Column height still decides for any column that has not been asked
 * - this only speaks for the ones that have, which is what lets one tile in a
 * bento center its contents while its neighbor keeps them at the top. */
/* The gap below an element is the gap to the next one, so the last one has none.
 *
 * Every other container here already says this - a box, a nested row's column,
 * a text element, a quote - and a page column was the one that did not. So the
 * last element in every column carried fifty pixels of margin under it against
 * the bottom padding, which read as a column padded far more at the bottom than
 * the top.
 *
 * It is also what stopped Vertical alignment working. Bottom aligning packs the
 * contents to the end of the box, and that phantom margin counted as part of
 * them: on a tile with only a little room to spare, contents plus fifty pixels
 * came to more than the box could hold, so there was no slack left to move and
 * everything stayed exactly where it was. */
/* Any last child, not only a .ds-el one. wpautop leaves loose paragraphs in a
   column that never went through the element renderer and so carry no class of
   ours at all - and those have a margin under them like any other paragraph. */
.ds-col-in > :last-child{ margin-bottom:0 }

.ds-col-vtop    > .ds-col-in{ display:flex; flex-direction:column; justify-content:flex-start }
.ds-col-vmiddle > .ds-col-in{ display:flex; flex-direction:column; justify-content:center }
.ds-col-vbottom > .ds-col-in{ display:flex; flex-direction:column; justify-content:flex-end }

/* One element at the foot of the column, everything else where it was.
 *
 * A top margin of auto in a flex column takes every pixel the column has spare,
 * so whatever carries it is pushed to the bottom and nothing above it moves. The
 * alternative was a margin big enough to reach - a figure only right at one
 * column height, wrong at every other, and wrong again the moment anything above
 * it changes length.
 *
 * The column is made a flex column only where something asks to sit at its foot.
 * A column laid out as a block behaves differently enough - margins collapse,
 * children size themselves differently - that switching every one of them would
 * be a change to every page on the site.
 *
 * The column also has to have a height for anything to be spare: on a bento that
 * is the tile, and on an ordinary row it is Columns sit left at Equal height.
 * Where a column is only as tall as its contents there is no room to push into,
 * and this correctly does nothing rather than doing something odd. */
.ds-col-hasbottom > .ds-col-in{ display:flex; flex-direction:column; min-height:100% }
.ds-col-in > .ds-el-bottom{ margin-top:auto }

.ds-col-aleft   > .ds-col-in{ text-align:left }
.ds-col-acenter > .ds-col-in{ text-align:center }
.ds-col-aright  > .ds-col-in{ text-align:right }

/* ==============================================================
 * A notch cut out of the tile
 *
 * An element that sits against one corner of a bento tile, keeps the tile's own
 * padding as the space around it on the other sides, takes the same corner
 * radius as everything else, and has the tile's background removed from behind
 * it - so the words read as cut into the shape rather than laid on top of it.
 *
 * Three separate things have to line up, and only the first two are here:
 * hugging the corner, and the padding. The hole is punched by dsb-cut.js, which
 * measures the notch and writes a mask onto the tile's background layers.
 * ============================================================== */
.ds-el-cut{
  /* As far as the padding line, and no further.
   *
   * This was fit-content, so the notch was the size of the words in it and the
   * band of tile left beside it was whatever happened to be left over - eighty
   * pixels next to a short caption, twenty next to a long one, and no relation
   * to anything that had been set. A notch is defined by the tile's padding:
   * flush against the two edges it is butted to, and exactly one padding away
   * from the other two. Filling the well and then pulling two sides out by the
   * padding gives precisely that, at whatever the padding is later changed to.
   *
   * See .ds-col-cutting for the same thing vertically - a block only fills its
   * width on its own. */
  width: auto;
  /* No max-width. 100% is the width of the well, which is the tile less its
     padding on both sides - so it capped the notch there and cancelled out the
     negative margin that is supposed to carry it out to the butted edge. The
     result was a gap of two paddings on the free side instead of one. Nothing
     needs capping now that the width is not content driven: the notch stretches
     to its container and the negative margin extends it by exactly the padding
     it is undoing. */

  /* No background of its own. What shows through is whatever the tile is
     standing on, once the mask has taken the tile's own background away.
   *
   * The color only, never the shorthand. A heading set in a gradient paints
   * the words with a background image and sets the text itself transparent so
   * the image shows through the glyphs - and `background: transparent` throws
   * that image away, leaving transparent text with nothing behind it. The
   * notch came out as an empty hole with its words gone. */
  background-color: transparent;
  border-radius: var(--ds-card-radius, 12px);

  /* The tile's padding, kept as the room inside the notch. Read from the tile
     rather than typed again, so the two cannot drift apart. */
  padding:
    var(--ds-colpad-top, var(--ds-pad-base, 0px))
    var(--ds-colpad-right, var(--ds-pad-base, 0px))
    var(--ds-colpad-bottom, var(--ds-pad-base, 0px))
    var(--ds-colpad-left, var(--ds-pad-base, 0px));

  position: relative;
  z-index: 2;
}

/* The notch reaches the padding line downwards as well as across.
 *
 * A block fills the width it is given and takes only the height of its contents,
 * so a notch that was correct across the tile still left a band above or below it
 * as tall as whatever the words happened to be short by. The well becomes a
 * column and the notch is told to grow, which gives the same rule on both axes:
 * flush on the two butted edges, one padding on the other two.
 *
 * Only where there is a notch. Every other column is left exactly as it was. */
.ds-col-hascut > .ds-col-in{
  display: flex;
  flex-direction: column;
  min-height: 100%;
}
.ds-col-hascut > .ds-col-in > .ds-el-cut{
  flex: 1 1 auto;

  /* The spare height collects at one end, not between the lines.
   *
   * The notch is stretched to reach the padding line, and where its contents are
   * a wrapping flex box - a counter is exactly that, a figure and a caption on
   * two lines - the default is to share the extra height out between those
   * lines. So growing the notch pushed the figure down the box and left a gap
   * above it far larger than the padding, which is not a padding at all but
   * distributed slack. Packed to one end, the slack falls where nobody reads it
   * as a gap. */
  align-content: flex-start;
}

/* And which end is the column's own Vertical alignment.
 *
 * Stretching the notch is what stops the tile having a band of leftover color
 * round it, but it also means the column's alignment has nothing left to move -
 * the notch already fills the tile, so setting a tile to Bottom left its words
 * exactly where they were. The alignment is handed down to the contents of the
 * notch instead, which is what somebody setting it is actually asking for. */
.ds-col-hascut.ds-col-vtop > .ds-col-in > .ds-el-cut{ align-content: flex-start }
.ds-col-hascut.ds-col-vmiddle > .ds-col-in > .ds-el-cut{ align-content: center }
.ds-col-hascut.ds-col-vbottom > .ds-col-in > .ds-el-cut{ align-content: flex-end }
/* Anything else in the tile keeps its own height, so a heading above a notch is
   not stretched to share the space with it. */
.ds-col-hascut > .ds-col-in > :not(.ds-el-cut){ flex: 0 0 auto }

/* Hugging a corner is undoing the tile's padding on exactly two sides.
 *
 * A negative margin of the same figure the tile is padded by, so the notch
 * reaches the edge however that padding is later changed. The other two sides
 * keep the gap, which is what makes it read as cut in rather than as a block
 * sitting in the corner. */
.ds-cut-tl{ margin-top: calc(var(--ds-colpad-top, var(--ds-pad-base, 0px)) * -1);    margin-left:  calc(var(--ds-colpad-left, var(--ds-pad-base, 0px)) * -1);  margin-right: auto }
.ds-cut-bl{ margin-bottom: calc(var(--ds-colpad-bottom, var(--ds-pad-base, 0px)) * -1); margin-left:  calc(var(--ds-colpad-left, var(--ds-pad-base, 0px)) * -1);  margin-right: auto }

/* Pushed across, not merely widened.
 *
 * The notch is only as wide as its words, so it sits where the flow puts it -
 * the left. A negative right margin extends its box rightward and moves the
 * element not at all, which is why a notch marked top right appeared in the
 * top left. The auto margin on the other side is what actually carries it
 * over to the edge it is meant to hug. */
.ds-cut-tr{ margin-top: calc(var(--ds-colpad-top, var(--ds-pad-base, 0px)) * -1);    margin-right: calc(var(--ds-colpad-right, var(--ds-pad-base, 0px)) * -1); margin-left: auto }
.ds-cut-br{ margin-bottom: calc(var(--ds-colpad-bottom, var(--ds-pad-base, 0px)) * -1); margin-right: calc(var(--ds-colpad-right, var(--ds-pad-base, 0px)) * -1); margin-left: auto }

/* The mask, written by the script as an SVG of the tile with the notch taken
   out of it. Applied to every background layer and to the tile's own color,
   which is why the color is drawn as a layer rather than on the column.
 *
 * Nothing at all until the script has measured, so a tile whose script has not
 * run yet is a whole tile rather than a half-masked one. */
.ds-col-cutting > .ds-col-pic,
.ds-col-cutting > .ds-col-vid,
.ds-col-cutting > .ds-col-fill{
  -webkit-mask-image: var(--ds-cut-mask);
  mask-image: var(--ds-cut-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* The tile's flat color and gradient, as a layer of their own.
 *
 * They used to be painted on the column itself, which cannot be masked without
 * masking everything in it as well. Drawn here instead, behind the content and
 * alongside the picture, so one mask takes the notch out of all of them. */
.ds-col-fill{
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
}

/* The surface color as a maskable layer, the same color .ds-col-surface uses. */
.ds-col-fill-surface{ background-color: var(--ds-card-bg, rgba(127,127,127,.08)) }

/* ==============================================================
 * Words turned on their side
 *
 * The angle is an inline variable rather than an inline transform, so a media
 * query can set it back to nought - a stylesheet cannot unpick a transform that
 * was written straight onto the tag.
 *
 * Standing back up matters more than it sounds. A line turned ninety degrees is
 * as tall as the words are long, and on a narrow screen that is taller than the
 * screen; the same line laid flat is wider than the column. Either way it
 * breaks the layout, so the author says where it gives up.
 * ============================================================== */
/* !important, and it has to be.
 *
 * The angle is written onto the tag, because it belongs to one element. An
 * inline declaration outranks any stylesheet rule whatever its media query
 * says, so the plain version of this did nothing at all - the class was on
 * the element, the query matched, and the words stayed on their side.
 *
 * An author stylesheet marked important does beat a normal inline
 * declaration, which is the one case where this is the tool rather than a
 * shortcut. */
@media (max-width: 1024px){
  .ds-rot-off-tablet{
    --ds-rot: 0deg !important;
    /* And the room it was taking. An element left as inline-block after the
       turn is gone sits in a box the size of its words, which is right - but it
       keeps whatever width the turn had reserved unless this is said. */
    display: inline;
  }
}

@media (max-width: 640px){
  .ds-rot-off-mobile{
    --ds-rot: 0deg !important;
    display: inline;
  }
}

/* Elements taken off one size of screen.

   Written here beside the rotation's own breakpoints on purpose: these are the
   same two widths, and two places deciding what "tablet" means is how a builder
   ends up hiding something at 1024 and straightening it at 900.

   display:none rather than visibility:hidden. The point is that the element is
   not there at this width - hidden but occupying its space leaves a gap nobody
   can explain, and a reader who finds one has no way of knowing it is deliberate.

   !important because the element may carry its own display from its own styles -
   a flex row, an inline-block heading - and a rule about whether it exists at
   this width has to beat a rule about how it lays out when it does. */
@media (max-width: 1024px){
  .ds-hide-tablet{ display: none !important }
}

@media (max-width: 640px){
  .ds-hide-mobile{ display: none !important }
}

/* And the other way round: on the screens the first two leave it on.

   Above the tablet breakpoint rather than below it, so an element hidden on
   desktop is back the moment the tablet rule would have applied - the two
   between them cover every width once, with no gap and no overlap. */
@media (min-width: 1025px){
  .ds-hide-desktop{ display: none !important }
}

/* ==============================================================
 * Reaching across, once there is nothing to reach across into
 *
 * A heading laid over the picture in the next column does it with a negative
 * side margin. That is exactly right while the columns sit side by side, and
 * meaningless the moment they stack - there is no next column, so the margin
 * stops being an overlap and becomes 118px of page nobody can see.
 *
 * So the reach is released where the stacking happens, and only sideways: a
 * negative top margin is how a picture is pulled up out of the top of a tile,
 * which still means exactly what it meant.
 *
 * Direct children of a column only. An element that has gone looking for a
 * negative margin somewhere deeper has its own reasons.
 * ============================================================== */
@media (max-width: 1024px){
  /* Clipped at the row rather than by unpicking the margin.
   *
   * The margin is written onto the element, so a stylesheet cannot lower it
   * without !important - and doing that would flatten deliberate positive
   * margins along with the negative ones. Clipping leaves every margin
   * exactly as the author set it and simply stops the reach painting where
   * there is no longer a column to reach into.
   *
   * clip rather than hidden: hidden makes a scroll container, which breaks
   * position:sticky in anything above it and gives the row a scrollbar of
   * its own. clip does neither. */
  .ds-row > .ds-row-in{ overflow-x: clip }
}

/* ==============================================================
 * A mark in the corner, a tile that answers the pointer, a tile that links
 * ============================================================== */

/* The mark. Sits in the padding of its corner, so it lines up with whatever
   else is inset by the tile's own padding rather than floating at a distance
   nobody chose. */
.ds-col-icon{
  position: absolute;
  z-index: 1;
  width: var(--ds-colicon, 28px);
  height: var(--ds-colicon, 28px);
  display: grid;
  place-items: center;
  pointer-events: none;
  color: currentColor;
  transition: transform .35s cubic-bezier(.22,.61,.36,1), opacity .35s ease;
}
.ds-col-icon svg{ width: 100%; height: 100%; display: block }

.ds-colicon-tl{ top: var(--ds-colpad-top, 16px);    left:  var(--ds-colpad-left, 16px) }
.ds-colicon-tr{ top: var(--ds-colpad-top, 16px);    right: var(--ds-colpad-right, 16px) }
.ds-colicon-bl{ bottom: var(--ds-colpad-bottom, 16px); left:  var(--ds-colpad-left, 16px) }
.ds-colicon-br{ bottom: var(--ds-colpad-bottom, 16px); right: var(--ds-colpad-right, 16px) }

/* Cut through the tile rather than drawn on it. The mark becomes a hole in the
   background exactly as the notch does, so what shows through it is whatever
   the tile is standing on. */
/* Cut through the tile rather than drawn on it.
 *
 * destination-out was the wrong tool: it needs the mark and the background
 * to be in one stacking context with nothing else in it, which a tile full of
 * words never is - so it composited against the page and did nothing at all.
 *
 * The mark is used as a mask on the layers instead, the same way the notch
 * is. What shows through is whatever the tile is standing on. */
.ds-colicon-cut{ visibility: hidden }

.ds-col-iconcut > .ds-col-pic,
.ds-col-iconcut > .ds-col-vid,
.ds-col-iconcut > .ds-col-fill{
  -webkit-mask-image: var(--ds-iconcut-mask);
  mask-image: var(--ds-iconcut-mask);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  /* Read by brightness, not by opacity.
   *
   * The mask is a white sheet with the mark drawn on it in black, and both are
   * fully opaque - so read the usual way, as alpha, every part of it said
   * "show this" and the mark cut nothing out. By luminance, white shows and
   * black hides, which is what the drawing has always meant. */
  -webkit-mask-source-type: luminance;
  mask-mode: luminance;
}

/* The whole tile as a link. Over the background, under the content - so a
   button inside the tile is still its own button. */
/* A div around the anchor, because an anchor on its own is an inline element
   and wpautop reads a loose inline run as a paragraph somebody forgot to
   open - which left a stray empty paragraph in every linked tile. The div
   holds the place and the anchor fills it. */
.ds-col-link{
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}
.ds-col-link a{
  position: absolute;
  inset: 0;
  display: block;
  border-radius: inherit;
}
.ds-col-linked{ cursor: pointer }
.ds-col-linked > .ds-col-in{ position: relative; z-index: 2 }

/* Only what can be clicked in its own right takes the pointer back. Plain words
   let the click through to the tile's link, which is what makes the whole tile
   feel like one thing. */
.ds-col-linked > .ds-col-in a,
.ds-col-linked > .ds-col-in button,
.ds-col-linked > .ds-col-in input,
.ds-col-linked > .ds-col-in select,
.ds-col-linked > .ds-col-in textarea{ position: relative; z-index: 3 }

.ds-col-link a:focus-visible{
  outline: 2px solid var(--ds-accent, currentColor);
  outline-offset: 3px;
}

/* ---- What it does when pointed at ----
 *
 * The background moves, never the tile. Moving the tile itself shifts the words
 * with it and pushes against its neighbors in the grid; moving what is behind
 * them leaves the layout exactly where it was.
 *
 * Slightly larger than the frame to begin with, so there is something to slide
 * into view rather than an edge arriving. */
/* The mark moves. The background does not.
 *
 * Every one of these used to slide or scale the picture behind the tile, on the
 * reasoning that moving the background moves nothing in the layout. That was the
 * wrong thing to move: the setting is about the corner mark, a photograph
 * lurching four percent sideways under the words is not what anybody asked a
 * mark to do, and it fought the mask on a tile with a cut. So the picture is
 * left alone and the mark is what answers the pointer.
 *
 * The transition belongs on the mark for the same reason. Without one, the
 * transform below applied on the frame the pointer arrived and the mark simply
 * appeared four pixels along - which read as the mark not moving at all. */
.ds-col-icon{
  transition: transform .4s cubic-bezier(.22,.61,.36,1);
  will-change: transform;
}

/* Rotate turns the mark rather than the picture. A photograph on its side is a
   mistake; an arrow turning to point somewhere is the thing people mean. */
.ds-colhov-rotate:hover > .ds-col-icon{ transform: rotate(45deg) }

/* And the mark leans whichever way the setting is named after. */
.ds-colhov-slide-left:hover  > .ds-col-icon{ transform: translateX(-25%) }
.ds-colhov-slide-right:hover > .ds-col-icon{ transform: translateX(25%) }
.ds-colhov-slide-up:hover    > .ds-col-icon{ transform: translateY(-25%) }
.ds-colhov-slide-down:hover  > .ds-col-icon{ transform: translateY(25%) }
.ds-colhov-scale:hover       > .ds-col-icon{ transform: scale(1.25) }

/* Asked for less movement means none of it. */
@media (prefers-reduced-motion: reduce){
  .ds-col-hover > .ds-col-pic,
  .ds-col-hover > .ds-col-vid,
  .ds-col-hover > .ds-col-fill,
  .ds-col-icon{ transition: none }
  .ds-colhov-slide-left:hover > .ds-col-pic,
  .ds-colhov-slide-right:hover > .ds-col-pic,
  .ds-colhov-slide-up:hover > .ds-col-pic,
  .ds-colhov-slide-down:hover > .ds-col-pic,
  .ds-colhov-scale:hover > .ds-col-pic,
  .ds-colhov-rotate:hover > .ds-col-icon{ transform: none }
}

/* Drift: the picture leans a few pixels toward the pointer.
 *
 * Applied as a translate the script only feeds numbers into, so it composes
 * with the settling scale above rather than the two overwriting each other. */
/* A picture leans by moving inside its layer, so the layer - and the mask on it -
   never moves. See render_column: the position is written as two percentages with
   the drift added, which is why nothing here touches the pic. */
.ds-col-drift > .ds-col-pic{
  transition: background-position .35s cubic-bezier(.22,.61,.36,1);
}

/* A video has no background to move, so it is the element that leans, and it is
   drawn a little larger so leaning never shows an edge. A flat color has
   nothing to lean at all. */
.ds-col-drift > .ds-col-vid{
  translate: var(--ds-driftx, 0px) var(--ds-drifty, 0px);
  transition: translate .35s cubic-bezier(.22,.61,.36,1);
  scale: 1.06;
}

/* While the pointer is actually on it, the lean should keep up rather than
   trail by a third of a second. */
.ds-col-drift:hover > .ds-col-pic{ transition: background-position .12s linear }
.ds-col-drift:hover > .ds-col-vid{ transition: translate .12s linear }

/* A video still leans as an element, so a mask on it is put back by the same
   distance to hold it against the tile. A picture needs none of this - it is the
   background inside a layer that never moved. */
.ds-col-drift > .ds-col-vid{
  -webkit-mask-position: calc(-1 * var(--ds-driftx, 0px)) calc(-1 * var(--ds-drifty, 0px));
  mask-position: calc(-1 * var(--ds-driftx, 0px)) calc(-1 * var(--ds-drifty, 0px));
}

@media (prefers-reduced-motion: reduce){
  .ds-col-drift > .ds-col-vid{ translate: none }
  /* And the picture sits at the position it was given, with no drift added. */
  .ds-col-drift > .ds-col-pic{ --ds-driftx: 0px; --ds-drifty: 0px }
}

/* ==============================================================
 * Badge
 *
 * A word in a pill, drawn from the tokens Theme Settings already sets under
 * Elements, Badge - the same ones the store uses for its product badges. Nothing
 * about the look is decided here, so a site that restyles its badges restyles
 * these with them.
 * ============================================================== */
.ds-badge{
  display: inline-flex;
  align-items: center;
  /* No element gap while it is flowing with the words.
   *
   * It carries ds-el, which is what gives an element its margin below and lets
   * an author's own margin apply - but that gap is fifty pixels by default, and
   * fifty pixels under a pill sitting in the middle of a sentence is not a gap,
   * it is a hole. A badge on a line of its own keeps it; see .ds-badge-block. */
  margin-bottom: 0;
  /* Inline, so several sit on one line and flow with the words around them.
     That is the whole reason this exists rather than a Button. */
  vertical-align: middle;
  font-family: var(--ds-badge-font, inherit);
  font-size: var(--ds-badge-size, 12px);
  line-height: 1.3;
  letter-spacing: .04em;
  white-space: nowrap;
  padding: var(--ds-badge-pad-y, 4px) var(--ds-badge-pad-x, 10px);
  border-radius: var(--ds-badge-radius, 99px);
  border: 1px solid transparent;
  text-decoration: none;
}

.ds-badge-solid{
  background: var(--ds-badge-bg, var(--ds-accent, #bdfe02));
  color: var(--ds-badge-text, #000);
  border-color: var(--ds-badge-border, transparent);
}

/* The outline variant the theme already has tokens for. */
.ds-badge-outline{
  background: var(--ds-badge-out-bg, transparent);
  color: var(--ds-badge-out-text, currentColor);
  border-color: var(--ds-badge-out-border, currentColor);
}

/* A badge given a line of its own.
 *
 * Still only as wide as its word. width:100% with justify-content would have
 * stretched the pill across the column, which is a bar and not a badge. It takes
 * a line by being a block that fits its contents, and it is placed on that line
 * with automatic margins. */
.ds-badge-block{ display: flex; width: fit-content; margin-bottom: var(--ds-el-gap, 50px) }
.ds-badge-block.ds-align-left  { margin-inline: 0 auto }
.ds-badge-block.ds-align-center{ margin-inline: auto }
.ds-badge-block.ds-align-right { margin-inline: auto 0 }

/* A badge that goes somewhere still says so under the pointer. */
a.ds-badge:hover, a.ds-badge:focus-visible{ filter: brightness(1.08) }

/* ==============================================================
 * Before / After
 *
 * Two pictures of the same thing with a handle saying how much of the top one
 * to show. The one underneath is the whole picture at its natural size, so the
 * element is as tall as the photograph is; the one on top is laid over it at
 * exactly the same size and clipped rather than resized, because two pictures
 * scaled differently put the same feature in two places and compare nothing.
 *
 * One figure drives it. --ds-ba-at is the split, and the clip and the handle
 * both read it, so they cannot come apart. Set on the element by PHP and moved
 * by the script; with no script at all it still draws at the author's figure.
 * ============================================================== */
.ds-ba{
  position:relative;
  overflow:hidden;
  touch-action:none;
  --ds-ba-at:50%;
}

.ds-ba-hasratio{ aspect-ratio:var(--ds-ba-ratio) }

.ds-ba img{ display:block; width:100%; height:auto }
.ds-ba-hasratio img{ height:100%; object-fit:cover }

/* The one underneath sets the size. The one on top is taken out of the flow and
   pinned to it, so both are the same box whatever the picture's shape. */
.ds-ba-before{ position:absolute; inset:0 }
.ds-ba-before img{ width:100%; height:100%; object-fit:cover }

/* Clipped, not scaled. inset() keeps the picture where it is and hides the part
   past the split, which is what makes the two line up. */
.ds-ba-x .ds-ba-before{ clip-path:inset(0 calc(100% - var(--ds-ba-at)) 0 0) }
.ds-ba-y .ds-ba-before{ clip-path:inset(0 0 calc(100% - var(--ds-ba-at)) 0) }

.ds-ba-r-soft, .ds-ba-r-soft img{ border-radius:var(--ds-radius-soft,10px) }
.ds-ba-r-round, .ds-ba-r-round img{ border-radius:var(--ds-radius-round,24px) }

/* The handle. A line across the picture with a grip on it, drawn rather than
   given a background image so it takes the site's accent and needs no asset. */
.ds-ba-handle{
  position:absolute;
  background:var(--ds-accent,#fff);
  cursor:grab;
}
.ds-ba-handle:active{ cursor:grabbing }

.ds-ba-x .ds-ba-handle{ top:0; bottom:0; left:var(--ds-ba-at); width:2px; transform:translateX(-1px) }
.ds-ba-y .ds-ba-handle{ left:0; right:0; top:var(--ds-ba-at); height:2px; transform:translateY(-1px) }

/* The grip, centered on the line. Its own box so the line stays thin. */
.ds-ba-handle::after{
  content:'';
  position:absolute;
  top:50%; left:50%;
  width:38px; height:38px;
  margin:-19px 0 0 -19px;
  border-radius:50%;
  background:var(--ds-accent,#fff);
  box-shadow:0 2px 12px rgba(0,0,0,.35);
}

.ds-ba-handle:focus-visible{ outline:2px solid var(--ds-accent,#fff); outline-offset:3px }

/* Words over each half, if there are any. Held to the corners furthest from the
   handle so neither sits under it at the middle. */
.ds-ba-tag{
  position:absolute;
  padding:.35em .7em;
  font-size:var(--ds-p-size,1rem);
  line-height:1.2;
  background:rgba(0,0,0,.55);
  color:#fff;
  border-radius:var(--ds-radius-soft,10px);
  pointer-events:none;
}
.ds-ba-x .ds-ba-tag-before{ top:12px; left:12px }
.ds-ba-x .ds-ba-tag-after { top:12px; right:12px }
.ds-ba-y .ds-ba-tag-before{ top:12px; left:12px }
.ds-ba-y .ds-ba-tag-after { bottom:12px; left:12px }

@media (prefers-reduced-motion: reduce){
  .ds-ba-handle{ transition:none }
}

/* ---------------------------------------------------------------------------
 * Popups
 *
 * A real <dialog>, so the browser handles Escape, focus and the backdrop. What
 * is left here is the shape of the box.
 *
 * A popup wears the theme's modal styling - every color below is the one
 * Theme Settings writes under Lightbox, the same set .ds-modal-panel reads.
 * A site that has dressed its modals has already answered what a box over the
 * page looks like there, and asking again under a second set of names would
 * let one site have two different modals.
 *
 * This block used to say background:#fff with color:inherit - a surface pinned
 * to white against whatever color the page happened to be using. On a dark
 * theme the inherited ink is near white, so the body copy and the close button
 * came out white on white: not faint, gone. A surface and its text are one
 * decision and have to come from one place.
 * ------------------------------------------------------------------------ */
.ds-popup{
  width: min(92vw, var(--ds-popup-w, 640px));
  max-width: 92vw;
  max-height: 88vh;
  padding: 0;
  border: var(--ds-modal-bw, 1px) solid
    var(--ds-modal-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius: var(--ds-field-radius,4px);
  background: var(--ds-modal-bg, var(--ds-bg,#0b0b0c));
  color: var(--ds-modal-ink, var(--ds-text,#f5f5f5));
  overflow: auto;
}

/* The named sizes, which are the modal panel's own figures under the modal
   panel's own names - so a popup and a lightbox modal both set to Large come
   out the same width on the same site.

   92vw stays in front of every one of them: these are desktop measurements and
   a 960px box on a phone is a box wider than the screen. */
.ds-popup-small { width: min(420px, 92vw) }
.ds-popup-medium{ width: min(680px, 92vw) }
.ds-popup-large { width: min(960px, 92vw) }
.ds-popup-full  {
  width: 100%; max-width: 100%;
  max-height: 100svh;
  border: 0; border-radius: 0;
}

/* The scrim is the modal's too, so a popup darkens the page by as much as a
   lightbox does rather than by its own figure. */
.ds-popup::backdrop{ background: var(--ds-modal-scrim, rgba(0,0,0,.6)) }

/* The modal panel's own inset, so the two are spaced alike. */
.ds-popup-body{ padding: var(--ds-modal-pad, clamp(24px,4vw,48px)) }

/* Headings answer to the modal's heading color, exactly as they do inside a
   modal panel - one control for a pale heading over quieter body copy. */
.ds-popup :is(h1,h2,h3,h4,h5,h6){ color: var(--ds-modal-head, inherit) }

/* Over the corner of the content rather than above it, so it costs no height
   and is in the same place whatever the layout inside is.

   Dressed from the modal's close button, not the popup's own idea of one. */
.ds-popup-x{
  position: absolute; top: 8px; right: 10px; z-index: 2;
  width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--ds-modalx-edge, var(--ds-line, rgba(255,255,255,.16)));
  border-radius: 50%;
  background: var(--ds-modalx-bg, rgba(255,255,255,.04));
  color: var(--ds-modalx-ink, var(--ds-text,#fff));
  font-size: 24px; line-height: 1; cursor: pointer;
}
.ds-popup-x:hover,
.ds-popup-x:focus-visible{
  color: var(--ds-modalx-ink-hover, #fff);
  background: var(--ds-modalx-bg, rgba(255,255,255,.12));
  border-color: var(--ds-modalx-edge, rgba(255,255,255,.4));
  filter: brightness(1.25);
}

/* A row inside a popup has no page gutter to fill - it is already inside a box
   with its own padding, and a full width row would sit against the edges. */
.ds-popup .ds-row{ width: 100%; margin-left: 0; margin-right: 0 }

/* And neither has the grid inside it.
 *
 * .ds-row-in carries the page's content gutter, which is the right figure on a
 * page and a second inset inside a box that already has one. It measured 50px a
 * side on top of the body's own padding, so a 640px popup was laying its fields
 * out in 485px and the content sat well short of the panel it is in.
 *
 * The max-width goes with it for the same reason: that is the page's content
 * measure, and a popup is already as wide as it was told to be. */
.ds-popup .ds-row-in{
  padding-left: 0;
  padding-right: 0;
  max-width: none;
}

@media (prefers-reduced-motion: no-preference){
  .ds-popup[open]{ animation: ds-popup-in .18s ease both }
  @keyframes ds-popup-in{ from{ opacity:0; transform:translateY(6px) } to{ opacity:1; transform:none } }
}
