/**
 * The motion elements.
 *
 * Every effect has to look finished with the script blocked, so nothing here
 * hides anything that JavaScript is expected to bring back. The one exception
 * is the split reveal, which needs a mask - and that one un-hides itself if
 * the script never runs.
 */

.ds-fx{ --fx-line:color-mix(in srgb, var(--ds-text,#f5f5f5) 16%, transparent);
  --fx-fill:color-mix(in srgb, var(--ds-text,#f5f5f5) 6%, transparent) }

/* ---------------------------------------------------------- magnetic */
.ds-mag{ display:inline-grid; place-items:center; padding:18px 40px; border-radius:999px;
  border:1px solid currentColor; text-decoration:none; color:inherit;
  font-size:max(.95rem, min(1.2vw, 1.1rem), 0.9167vw); letter-spacing:.02em; will-change:transform }
.ds-mag:hover{ background:var(--fx-fill) }
.ds-mag span{ display:block; will-change:transform }

/* --------------------------------------------------------- proximity */
.ds-fx-proximity{ display:grid; grid-template-columns:repeat(var(--ds-cols,8),1fr);
  gap:max(8px, min(1.4vw, 18px), 0.9375vw); padding:max(16px, min(3vw, 40px), 2.0833vw) 0 }
.ds-fx-proximity i{ aspect-ratio:1; display:grid; place-items:center; opacity:.35;
  border-radius:6px; background:var(--fx-fill); font-style:normal;
  font-size:max(.6rem, min(1vw, .85rem), 0.7083vw); text-align:center; will-change:transform }

/* ------------------------------------------------------ cursor trail */
/* The trail itself is fixed to the window, so the element has nothing in it.
   It still needs to be somewhere on the page, or a page whose only content is
   a cursor trail is a blank page with no hint that anything is there. */
.ds-fx-cursortrail{ position:relative; min-height:max(220px, min(32vw, 380px), 19.7917vw);
  border:1px dashed var(--fx-line); border-radius:10px; display:grid;
  place-items:center }
.ds-fx-cursortrail::before{ content:'Move the pointer'; font-size:.9rem;
  letter-spacing:.14em; text-transform:uppercase; opacity:.4 }
.ds-trail{ position:fixed; top:0; left:0; z-index:9998; pointer-events:none;
  width:14px; height:14px; margin:-7px 0 0 -7px;
  background:var(--ds-trail-shadow, currentColor);
  border-radius:50%; will-change:transform }
.ds-trail.ring{ background:none; border:1px solid var(--ds-trail-shadow, currentColor) }
.ds-trail.square{ border-radius:2px }

/* ------------------------------------------------------- image trail */
/* overflow:hidden so a picture thrown off the edge is clipped by the element
   rather than widening the page. The pictures are absolute inside it. */
/* No border, no background, no cursor of its own - an element brings only what
   it was asked to bring. Height is the one thing it must have: a box with no
   height has nothing to move a pointer across. */
/* The element does not clip - its clip layer does. Words set to the viewport
   are wider than the column this sits in, and an element clipping everything it
   holds would cut them off at its own edges. */
.ds-fx-imagetrail{ position:relative; isolation:isolate;
  display:grid; place-items:center }
.ds-it-clip{ position:absolute; inset:0; display:block; overflow:hidden;
  z-index:0; pointer-events:none }
.ds-fx-imagetrail[data-tall="short"] { min-height:max(200px, min(26vw, 320px), 16.6667vw) }
.ds-fx-imagetrail[data-tall="medium"]{ min-height:max(320px, min(44vw, 560px), 29.1667vw) }
.ds-fx-imagetrail[data-tall="tall"]  { min-height:max(460px, min(64vw, 760px), 39.5833vw) }
/* svh, not vh: on a phone vh is the tallest the window ever gets, so a full
   screen element is taller than the screen until the address bar has gone. */
.ds-fx-imagetrail[data-tall="full"]  { min-height:100vh; min-height:100svh }
.ds-fx-imagetrail:not([data-tall]){ min-height:max(320px, min(44vw, 560px), 29.1667vw) }

/* Only written when there are words. Inherits size and color unless the
   element sets them. */
.ds-it-say{ pointer-events:none; text-align:center; max-width:24ch }

/* The page's own sizes, not numbers of its own. Line height comes with each
   one: a heading size on body leading is a heading with gaps in it. */
.ds-it-say[data-says="h1"]{ font-size:var(--ds-h1-size); line-height:var(--ds-h1-lh,1em) }
.ds-it-say[data-says="h2"]{ font-size:var(--ds-h2-size); line-height:var(--ds-h2-lh,1em) }
.ds-it-say[data-says="h3"]{ font-size:var(--ds-h3-size); line-height:var(--ds-h3-lh,1em) }
.ds-it-say[data-says="h4"]{ font-size:var(--ds-h4-size); line-height:var(--ds-h4-lh,1em) }
.ds-it-say[data-says="h5"]{ font-size:var(--ds-h5-size); line-height:var(--ds-h5-lh,1em) }
.ds-it-say[data-says="h6"]{ font-size:var(--ds-h6-size); line-height:var(--ds-h6-lh,1em) }
.ds-it-say[data-says="p"] { font-size:var(--ds-p-size);  line-height:var(--ds-p-lh,1.4em) }

/* Full width is a fit rather than a size: effects.js measures the words and
   sets the size that puts them edge to edge. One line, no wrapping cap, and
   nothing here that would fight the number it writes.
 *
 * Everything but the size comes from the Full width text row in Typography -
 * the same row the element and the footer block read, because all three are the
 * same thing: one line of type across the width it is given. */
/* On its own layer. Type this size is expensive to paint, and inside a smooth
   scroller the whole page is a moving surface - so every frame of a scroll was
   repainting a viewport of 200px letterforms. Promoted, it is rasterized once
   and moved about, which is what a compositor is for. */
.ds-it-say[data-says="full"]{ display:inline-block; max-width:none;
  white-space:nowrap;
  /* The Typography row's own face first. Two settings name a full width font -
     Fonts > Full width, and the family on the Typography > Full width row - and
     only the first was ever read here, so the row that also carries the weight,
     the case and the tracking for this element could not carry its face. */
  font-family:var(--ds-fullwidth-family, var(--ds-font-fullwidth, var(--ds-font-heading)));
  font-weight:var(--ds-fullwidth-weight,400);
  text-transform:var(--ds-fullwidth-case,none);
  letter-spacing:var(--ds-fullwidth-track,-0.02em);
  line-height:var(--ds-fullwidth-lh,1);
  transform:translateZ(0); will-change:transform }

/* Out of the column and across the window. The offset and the width are
   measured by effects.js - left:0 here is only a starting point, so a browser
   with the script blocked still gets the words in the right place rather than
   somewhere arbitrary. */
.ds-it-say[data-says="full"][data-sayw="viewport"]{
  position:absolute; left:0; top:50%; transform:translate3d(0,-50%,0);
  text-align:center }

/* No shadow of its own.
 *
 * A shadow is drawn from the element's box, so on a cut-out picture it
 * fills the see-through part with a rounded rectangle - the transparency
 * reads as a pale block sitting behind the subject. Every product shot
 * dropped by this element is a cut-out, so the shadow was wrong for the
 * one thing it is mostly used for.
 *
 * It is not simply deleted: .ds-it-shot is named in the Images target in
 * DS_Theme::target_map(), so ticking Images under Box shadows puts the
 * theme's own shadow on these and unticking it takes the shadow off.
 * Written here it could not be reached from the screen at all. */
.ds-it-shot{ position:absolute; top:0; left:0; display:block;
  pointer-events:none; will-change:transform; border-radius:8px }

/* On where the element's own Shadow field says On.
 *
 * ds-el-shadow is written by the builder for every element carrying the
 * standard Shadow field - Theme default / Off / On - so this needs no
 * field of its own, and the control sits where it does on every other
 * element rather than somewhere particular to this one.
 *
 * Off is already handled: .ds-el-noshadow img is in the builder's own
 * stylesheet and these are img elements. */
.ds-el-shadow .ds-it-shot{
  box-shadow:var(--ds-shadow, 0 20px 50px -20px rgba(0,0,0,.7)) }
/* Matched on the attribute the renderer actually writes. These were class
   selectors, and the box carries data-size - so every trail drew its pictures
   at whatever size they happened to be. */
.ds-fx-imagetrail[data-size="small"]  .ds-it-shot{ width:max(70px, min(8vw, 110px), 5.7292vw) }
.ds-fx-imagetrail[data-size="medium"] .ds-it-shot{ width:max(110px, min(12vw, 170px), 8.8542vw) }
.ds-fx-imagetrail[data-size="large"]  .ds-it-shot{ width:max(150px, min(17vw, 240px), 12.5vw) }
/* A trail saved before the size setting existed still needs a sane width. */
.ds-it-shot{ width:max(110px, min(12vw, 170px), 8.8542vw) }

/* -------------------------------------------------------------- dock */
.ds-fx-dock{ display:flex; gap:14px; align-items:flex-end; padding:24px 0 34px }
.ds-align-center.ds-fx-dock{ justify-content:center }
.ds-align-right.ds-fx-dock{ justify-content:flex-end }
.ds-dock-item{ display:block; width:max(44px, min(6vw, 74px), 3.8542vw); flex:0 0 auto;
  transform-origin:50% 100%; will-change:transform }
.ds-dock-item img{ width:100%; height:auto; display:block; border-radius:12px }

/* ----------------------------------------------------------- counter */
/* Two gaps, because there are two kinds of gap here.
 *
 * One figure is separated from the next by a generous amount - they are separate
 * statements and want air between them. But this was written as a single value,
 * and a single value is a row gap as well as a column gap: the moment a counter
 * wrapped onto a second line, the space meant to sit between two figures landed
 * between a figure and its own caption. At a 1098px window that was 55px of
 * nothing above the words, inside a tile that then had to be tall enough to hold
 * it - which reads as the element having been given a large padding it does not
 * have. The row gap is small, because a caption belongs to the figure above it. */
.ds-fx-counter{ display:flex; flex-wrap:wrap; gap:10px max(24px, min(5vw, 70px), 3.6458vw) }
.ds-align-center.ds-fx-counter{ justify-content:center }
.ds-align-right.ds-fx-counter{ justify-content:flex-end }
.ds-count > b{ display:block; line-height:1;
  font-variant-numeric:tabular-nums; font-weight:500;
  color:var(--ds-counter-num, inherit) }
/* The clamp only where no size has been chosen.
 *
 * It was on .ds-count b, an element selector, which beats the .ds-as-* class the
 * counter's size selector puts on that same element - so picking H1 landed the
 * right class against the right token and the figure went on drawing at whatever
 * the clamp said. Every size in the dropdown did nothing, and the dropdown was
 * not the thing that was wrong. */
.ds-count > b:not([class*="ds-as-"]){ font-size:max(2.6rem, min(7vw, 5.5rem), 4.5833vw) }
.ds-count > span{ display:block; margin-top:8px; letter-spacing:.08em;
  text-transform:uppercase;
  /* Its own color where one is set, and the old faded ink where it is not -
     an opacity and a color together would tint whatever was chosen. */
  color:var(--ds-counter-label, inherit);
  opacity:var(--ds-counter-label-fade, .6) }
/* And the label's size, for the same reason as the figure's above. */
/* The label, which is a direct child. Unscoped, this reached every span
   inside the counter - including the ticker's own digit columns, which are
   spans nested in the figure. They were being drawn at .9rem inside an 80px
   number, so each masked window was 14px tall and the digits it was meant to
   show were six times its height. */
.ds-count > span:not([class*="ds-as-"]){ font-size:.9rem }

/* ------------------------------------------------------ text effects */
/* The display size belongs to the headings, not to the class.
   This set clamp(1.8rem,5vw,4.4rem) on .ds-fx-text whatever tag it sat on, so
   choosing p for a split reveal still rendered at four and a half rem - the
   Level control swapped the element and changed nothing anybody could see. A
   paragraph now reads at the size the page sets, which is why you would pick
   it. */
.ds-fx-text{ margin:0 }
/* The line height belongs to the heading too, for the same reason the size
   above it does. Hardcoded at 1.06 this was the one heading on the site
   that ignored Theme Settings: every other h1 took its line height from
   the type screen and the hero - the largest type on the page and the
   first thing anybody sees - sat at 1.06 whatever was set, with its
   descenders running into the paragraph beneath. */
h1.ds-fx-text,h2.ds-fx-text,h3.ds-fx-text{
  font-size:max(1.8rem, min(5vw, 4.4rem), 3.6667vw) }
h1.ds-fx-text{ line-height:var(--ds-h1-lh,1.2em) }
h2.ds-fx-text{ line-height:var(--ds-h2-lh,1.2em) }
h3.ds-fx-text{ line-height:var(--ds-h3-lh,1.25em) }
p.ds-fx-text{ font-size:inherit; line-height:inherit }
.ds-fx-splitreveal .ds-splitline{ overflow:hidden }

/* A split line is one line, and has to stay one.
 *
 * SplitText decides which words belong to a line by measuring them, and that
 * measurement is fractional while the box they have to fit in is not. A line
 * that measures 465.4 in a 465 box is written as one line and then wraps when
 * it is drawn: the last word drops onto a second line inside a box built for
 * one, and the paragraph reads as broken in half - "building a life and
 * raising" ending a line, "her family" beginning the next.
 *
 * Measured on the home page: nine such boxes, each 38px against a 19px line
 * height, and the worst needed 466 pixels of text in 465 pixels of box. One
 * pixel.
 *
 * So the promise is made explicit. These are lines by construction, so they are
 * told not to wrap, and a rounding error overhangs by a pixel nobody can see
 * rather than folding the sentence. */
.ds-splitline{ white-space:nowrap }

.ds-caret{ display:inline-block; width:2px; height:.9em; margin-left:3px;
  background:currentColor; vertical-align:-.08em; animation:ds-blink 1s steps(1) infinite }
@keyframes ds-blink{ 50%{ opacity:0 } }

/* ---------------------------------------------------------- card stack */
.ds-fx-cardstack{ display:grid; gap:var(--fx-card-gap, max(30px, min(6vw, 90px), 4.6875vw)) }
.ds-card-item{ margin:0; border-radius:14px; overflow:hidden; background:var(--fx-fill);
  will-change:transform }
.ds-card-item img{ width:100%; height:auto; display:block }

/* One shape for every card.

   Unset the cards are whatever their pictures are, which on a stack of mixed
   uploads meant each one a different height as it went past. Given a ratio the
   picture fills it and is cropped from the middle - the alternative is letter
   boxing, which is a gray bar somebody has to explain. */
/* Both kinds of card: a gallery picture in a figure, and a blog post whose
   image sits in its own wrapper. Same stack, same crop. */
.ds-fx-cardstack[data-crop="16:9"] :is(.ds-card-item, .ds-post-card-img) img{ aspect-ratio:16/9; object-fit:cover; height:100%; width:100% }
.ds-fx-cardstack[data-crop="3:2"]  :is(.ds-card-item, .ds-post-card-img) img{ aspect-ratio:3/2;  object-fit:cover; height:100%; width:100% }
.ds-fx-cardstack[data-crop="4:3"]  :is(.ds-card-item, .ds-post-card-img) img{ aspect-ratio:4/3;  object-fit:cover; height:100%; width:100% }
.ds-fx-cardstack[data-crop="1:1"]  :is(.ds-card-item, .ds-post-card-img) img{ aspect-ratio:1/1;  object-fit:cover; height:100%; width:100% }
.ds-card-item figcaption{ padding:16px 20px; font-size:.9rem; opacity:.65 }

/* A card has to fit the window it is pinned into.
 *
 * The stack pins each card from 12% down to 12% up, which is about three
 * quarters of the screen - but a card is its picture plus its title and
 * standfirst underneath, and that came to 739px against a 391px window on a
 * short viewport. The title sat 675px down, so it was below the fold for the
 * whole time the card was held, and the only sight of it was as the card shrank
 * and faded to 45% on its way out. Hence a title that is never readable.
 *
 * The card is capped at the pinned window and laid out as a column: the words
 * take the room they need and the picture takes what is left. object-fit means
 * the picture crops rather than squashes, which it was already doing for the
 * fixed ratios above.
 *
 * svh rather than vh, so a phone's shrinking address bar does not change the
 * cap mid-scroll and re-run the whole layout. */
.ds-fx-cardstack [data-card]{
  display:flex;
  flex-direction:column;
  max-height:76svh;
  overflow:hidden;
}

.ds-fx-cardstack [data-card] :is(.ds-post-card-img, .ds-card-item-pic, figure){
  flex:1 1 auto;
  min-height:0;
  overflow:hidden;
}

/* The card's own picture as well as one inside a wrapper.
 *
 * This named only a picture wrapped in something - .ds-post-card-img for a
 * post, a figure for anything else - and the plain card is a figure itself with
 * the image straight inside it. So on the one shape the element draws most, the
 * selector matched nothing: no object-fit, and the browser's own `fill`, which
 * stretches rather than crops.
 *
 * The card is a flex column with a height cap, so the image is squeezed to
 * whatever is left after the caption - and a tall picture stretched into that
 * strip is flattened out of shape. A box render 1524 wide and 2023 tall was
 * being drawn 1372 by 302.
 *
 * The direct child needs the flex terms too. Its wrapped cousin gets them from
 * the rule above; on its own it is the flex item, and without min-height:0 a
 * flex item will not shrink below its content. */
.ds-fx-cardstack [data-card] > img{
  flex:1 1 auto;
  min-height:0;
}

.ds-fx-cardstack [data-card] > img,
.ds-fx-cardstack [data-card] :is(.ds-post-card-img, .ds-card-item-pic, figure) img{
  width:100%;
  height:100%;
  object-fit:cover;
}

/* The words never shrink - they are the point of the card. */
.ds-fx-cardstack [data-card] :is(.ds-post-card-body, figcaption){
  flex:0 0 auto;
}

/* -------------------------------------------------- horizontal gallery */
.ds-fx-hgallery{ overflow:hidden }
.ds-hg-track{ display:flex; gap:max(14px, min(2vw, 30px), 1.5625vw); width:max-content; will-change:transform }
.ds-hg-item{ flex:0 0 auto; width:max(240px, min(38vw, 560px), 29.1667vw) }
.ds-fx-hgallery[data-size="small"] .ds-hg-item{ width:max(180px, min(24vw, 340px), 17.7083vw) }
.ds-fx-hgallery[data-size="large"] .ds-hg-item{ width:max(300px, min(52vw, 760px), 39.5833vw) }
.ds-hg-item img{ width:100%; height:auto; display:block; border-radius:10px }

/* A caption traveling with its own slide. flex+column so "over" is a reorder
   rather than absolute positioning, which would sit on top of the picture and
   need a scrim to stay readable. */
.ds-hg-item{ display:flex; flex-direction:column; gap:12px }
.ds-fx-hgallery[data-capat="over"] .ds-hg-item{ flex-direction:column-reverse }
.ds-hg-say{ font-size:.9rem; line-height:1.5; opacity:.7 }

/* ----------------------------------------------------- horizontal text */
.ds-fx-htext{ overflow:hidden }
.ds-htext-line{ display:inline-block; white-space:nowrap; will-change:transform;
  font-size:max(3rem, min(13vw, 12rem), 10vw); line-height:.95; letter-spacing:-.02em }

/* ------------------------------------------------------ pinned panels */
.ds-panel-slide{ min-height:100svh; display:grid; place-items:center;
  background:var(--ds-bg,#0b0b0c) center/cover no-repeat; position:relative }
.ds-panel-slide h3{ margin:0; position:relative; z-index:2;
  font-size:max(2rem, min(7vw, 6rem), 5vw); line-height:1; text-align:center; padding:0 6vw }
.ds-panel-slide[style]::after{ content:''; position:absolute; inset:0;
  background:rgba(0,0,0,.45) }

/* A panel built from elements rather than from a typed line.
 *
 * It carries none of the chrome above. That chrome exists because a panel of
 * typed words has to paint itself something to sit on and size its own
 * heading; a panel holding elements has a row behind it and elements that
 * bring their own type. Left in place it painted a gray sheet over the row and
 * fought the heading for its size - which is exactly what went wrong.
 *
 * min-height goes too. The stage below is what has the height; a panel keeping
 * its own screen inside a stage that is already a screen tall is a second
 * screen nobody asked for. */
.ds-panel-holds{ background:none; min-height:0; padding:0 }
.ds-panel-holds::after{ content:none }

/* Stretched, not centered to its content.
 *
 * place-items:center on the section makes every child as wide as its own text
 * wants to be, and Fit text then measures that box and grows the type to fill
 * it - each feeding the other until the words run off the side of the screen.
 * Stretching gives the child the panel's width to measure against, which is a
 * number that does not move. */
.ds-panel-holds{ justify-items:stretch; align-content:center }
.ds-panel-holds > *{ position:relative; z-index:2; width:100%; max-width:100% }

/* The messages, stacked in one place.
 *
 * Every message sits in the same box so one replaces another rather than
 * following it down the page. The element is the stage and is what gets
 * pinned; a screen tall, because that is the space a message is read in. */
.ds-panels-stacked{ position:relative; height:100svh }
.ds-panels-stacked .ds-panel-slide{ position:absolute; inset:0 }

/* Each word, while it arrives. Inline-block so it can be moved; the space
   between words is left as real text so the line still wraps and still reads
   as a sentence. */
.ds-panel-word{ display:inline-block }

/* ------------------------------------------------------------ slider */
.ds-fx-slider{ overflow:hidden }
/* The gap is a property so the element can set it, and the clamp is only what
   it falls back to when nobody has. The width sum below reads the same
   property, so a wider gap narrows the cards instead of pushing them out of
   the count somebody asked for. */
/* The gap between slides, growing with the page like everything else.
 *
 * It was a clamp with a 40px ceiling, so on a 4K screen the slides doubled in
 * size and the space between them stopped at forty - which reads as a row of
 * cards jammed together. Multiplied instead, so the rhythm of the row is the
 * rhythm it was designed with. */
.ds-slider-track{ display:flex; gap:var(--ds-slide-gap, max(8.125px, 2.0833vw)); width:max-content;
  will-change:transform }
/* Scoped to the track these belong to.
   Bare, .ds-slide claimed every slide on the page, and the row slideshow uses
   the same class for a slide that is meant to fill the screen. effects.css
   loads after builder.css, so a full width scroll driven slideshow drew each
   of its slides 380px wide in the corner of the window with the photograph
   cropped to match - on every page, whether a slider element was on it or
   not. The img and span rules went the same way: they restyled the picture
   and the caption of a slideshow that has neither. */
/* And the slides themselves, or the gap would grow while they did not. */
.ds-slider-track > .ds-slide{ flex:0 0 auto; width:max(77.1875px, 19.7917vw) }
/* The empty item that closes the loop. No width, so all it contributes is
   one more gap - which is the gap the doubled strip was missing. */
.ds-slider-track > .ds-slide-tail{ flex:0 0 auto; width:0; align-self:stretch }
/* Square, unless somebody says otherwise.
 *
 * This carried a written-in border-radius of 10px. Nothing set it and nothing
 * could turn it off: there is no image radius token in the theme, so a slider
 * of product boxes had its corners clipped whatever the site was set to. On a
 * phone, where the cards go to one per view and 240px wide, a 10px round is the
 * first thing you see.
 *
 * A product slide follows the shop's own product card radius, because that is a
 * figure somebody owns and set. Everything else is square, which is what a
 * picture is until it is asked to be otherwise. */
.ds-slider-track > .ds-slide img{ width:100%; height:auto; display:block; border-radius:0 }
.ds-slider-track > .ds-slide-product img{ border-radius:var(--ds-pcard-radius, 0) }
/* The caption, and only the caption.
 *
 * Written as every span inside a slide, which was already one class too wide -
 * a product slide wraps its picture in one, and the rule below had to undo the
 * damage. Now that a picture slide wraps its picture the same way it would have
 * had to undo it twice, and a spinner is a span as well. A direct child that is
 * not the picture's wrapper is what this always meant. */
/* A product slide's two lines are named and styled below, and this leaves them
   alone. It was written as one span with :not(.ds-slide-face), which carries a
   type selector the rules below do not - so it outranked both of them and the
   name and the price were drawn as captions: the same size, both dimmed, and
   both pushed down ten pixels. The gap under a product title was set to two and
   never moved, because this is what was being measured. */
.ds-slider-track > .ds-slide > span:not(.ds-slide-face, .dst-card-name, .dst-card-sum, .dst-card-price){
  display:block; margin-top:10px; font-size:.85rem; opacity:.6;
}

/* A product slide is the same card at the same width, and a link.
 *
 * It inherits the width and the picture rules above, so a slider of products
 * and a slider of pictures are the same object with different contents. Only
 * the two lines under the picture are its own. */
.ds-slider-track > .ds-slide-product{
  color:inherit; text-decoration:none;
  /* Dragged rather than followed: a slider is taken hold of, and a link that
     starts a navigation on every press cannot be. The click still works. */
  -webkit-user-drag:none; user-select:none;
}
/* The picture at full strength, on either kind of slide.
 *
 * The caption rule above used to be .ds-slide span, which is every span inside
 * a slide - and a product slide wraps its picture in one. So the artwork was
 * dimmed to six tenths by a rule written for the gray line of text under an
 * image, and every product came out washed out. That rule is narrowed now and
 * this is the belt: a picture is a picture whatever dims it.
 *
 * Positioned, because it is what the spinner behind the picture is placed
 * against. */
.ds-slider-track > .ds-slide .ds-slide-face{
  display:block; position:relative; opacity:1;
}
/* A product slide wears the shop's own card classes now, so the shop's
   stylesheet sets the name, the summary and the price - and a product looks the
   same in a slider as it does in the grid. What used to be here said a
   different size, a different weight and a different opacity on the price, and
   that was the whole of the difference between the two. */
/* Ten more than the card gives it. A card in the grid sits in its own cell with
   room around it; a slide is one of a moving row, and at the card's own figure
   the title reads as crowding the picture above it. */
.ds-slider-track > .ds-slide-product .dst-card-name{ margin-top:22px }

/* The name dressed as the heading it is set to, without being one - see the
   note in slider_products(). Each level reads the same variables the theme
   sets for that heading, so the two cannot drift apart.
 *
 * Tracking and case were missing from every one of these. A heading set with
 * letter spacing on Typography - h6 carries 0.06em by default - drew with none
 * of it here, so a product name matched its heading in face, size, weight and
 * leading and still did not look like one. */
.ds-slide-product .dst-card-name[data-ds-level="h1"]{ font-family:var(--ds-h1-family); font-size:var(--ds-h1-size); font-weight:var(--ds-h1-weight); line-height:var(--ds-h1-lh); letter-spacing:var(--ds-h1-track); text-transform:var(--ds-h1-case) }
.ds-slide-product .dst-card-name[data-ds-level="h2"]{ font-family:var(--ds-h2-family); font-size:var(--ds-h2-size); font-weight:var(--ds-h2-weight); line-height:var(--ds-h2-lh); letter-spacing:var(--ds-h2-track); text-transform:var(--ds-h2-case) }
.ds-slide-product .dst-card-name[data-ds-level="h3"]{ font-family:var(--ds-h3-family); font-size:var(--ds-h3-size); font-weight:var(--ds-h3-weight); line-height:var(--ds-h3-lh); letter-spacing:var(--ds-h3-track); text-transform:var(--ds-h3-case) }
.ds-slide-product .dst-card-name[data-ds-level="h4"]{ font-family:var(--ds-h4-family); font-size:var(--ds-h4-size); font-weight:var(--ds-h4-weight); line-height:var(--ds-h4-lh); letter-spacing:var(--ds-h4-track); text-transform:var(--ds-h4-case) }
.ds-slide-product .dst-card-name[data-ds-level="h5"]{ font-family:var(--ds-h5-family); font-size:var(--ds-h5-size); font-weight:var(--ds-h5-weight); line-height:var(--ds-h5-lh); letter-spacing:var(--ds-h5-track); text-transform:var(--ds-h5-case) }
.ds-slide-product .dst-card-name[data-ds-level="h6"]{ font-family:var(--ds-h6-family); font-size:var(--ds-h6-size); font-weight:var(--ds-h6-weight); line-height:var(--ds-h6-lh); letter-spacing:var(--ds-h6-track); text-transform:var(--ds-h6-case) }
/* The summary and the price, at whichever of the theme's sizes was chosen.
 *
 * Same idea as the name above: the element says which size and the stylesheet
 * reads the variables the theme already sets for it, so nothing here holds a
 * figure of its own. No attribute means the size each has always drawn at -
 * Text small for the summary, and whatever the slide inherits for the price. */
.ds-slide-product .dst-card-sum[data-ds-level="p"],
.ds-slide-product .dst-card-price[data-ds-level="p"]{ font-size:var(--ds-p-size); line-height:var(--ds-p-lh) }
.ds-slide-product .dst-card-sum[data-ds-level="small"],
.ds-slide-product .dst-card-price[data-ds-level="small"]{ font-size:var(--ds-text-small-size); line-height:var(--ds-text-small-lh) }
.ds-slide-product .dst-card-sum[data-ds-level="medium"],
.ds-slide-product .dst-card-price[data-ds-level="medium"]{ font-size:var(--ds-text-medium-size); line-height:var(--ds-text-medium-lh) }
.ds-slide-product .dst-card-sum[data-ds-level="large"],
.ds-slide-product .dst-card-price[data-ds-level="large"]{ font-size:var(--ds-text-large-size); line-height:var(--ds-text-large-lh) }

.ds-slider-track > .ds-slide-product img{ pointer-events:none }

/* A spinner, standing in the space the picture is about to fill.
 *
 * The cards past the edge of the view are lazy and are not fetched until the
 * loop carries them in, so a slider spends its whole life with pictures on the
 * way. Until one lands its card is an empty box, and an empty box the shape of
 * a photograph reads as something broken rather than as something coming.
 *
 * Behind rather than over it. The width and height on the tag give the box its
 * proportions before a byte arrives, so this sits in the middle of that box and
 * the picture covers it. Nothing moves when it lands, and there is no fade to
 * wait through.
 *
 * The script takes it out of the page the moment its picture is there, so the
 * animation stops with it rather than turning forever under an opaque
 * photograph. A card whose picture fails to arrive loses its spinner too: a
 * shape that never stops is a promise the page cannot keep. */
.ds-slide-wait{
  position:absolute; inset:0;
  display:grid; place-items:center;
  pointer-events:none;
  /* Two thirds, so it reads as a placeholder rather than as the content. */
  opacity:.66;
}
/* Everything else in the wrapper stands in front of it. Stated on the children
   rather than as a negative z-index on the spinner: -1 would put it behind the
   card's own background as well, and a slider inside a colored row would hide
   it completely. */
.ds-slider-track > .ds-slide .ds-slide-face > *:not(.ds-slide-wait){
  position:relative; z-index:1;
}

/* The size and the color go on the shape itself, never on the wrapper.
   DS_Preloader declares --ds-spin on .ds-spin - the overlay's own size, which
   is 96px on a site that has not changed it - so a variable inherited from
   here would be overruled the moment it reached the shape and every card would
   carry a 96 pixel ring. This is the more specific rule, so it wins. The
   button spinner in the theme is written the same way for the same reason. */
.ds-slide-wait > .ds-spin{
  /* Not a percentage: a percentage width answers to the card and a percentage
     height answers to the picture, so the two sides of a circle would be
     measured against different boxes and the ring would come out an ellipse. */
  --ds-spin:max(24px, min(2.4vw, 40px), 2.0833vw); --c:currentColor;
  width:var(--ds-spin); height:var(--ds-spin); color:inherit;
}
/* Two of the shapes lay their pieces out in a row and are sized by the pieces,
   so they take their measurements from the figure above rather than from the
   fixed ones the overlay draws them at. */
.ds-slide-wait > .ds-spin-dots{ display:flex; gap:calc(var(--ds-spin) * .18) }
.ds-slide-wait > .ds-spin-bars{ display:flex; gap:calc(var(--ds-spin) * .12);
  height:var(--ds-spin) }
.ds-slide-wait > .ds-spin-bars i{ width:calc(var(--ds-spin) * .16) }
/* The ring's border is a flat 3px, which at this size is nearly a solid disc.
   Proportional, with a floor so it never thins away. */
.ds-slide-wait > .ds-spin-ring{ border-width:max(1.5px, calc(var(--ds-spin) * .1)) }

@media (prefers-reduced-motion: reduce){
  .ds-slide-wait > .ds-spin, .ds-slide-wait > .ds-spin *{ animation:none !important }
}

/* ---------------------------------------------------------- sequence */
.ds-fx-sequence{ width:100%; background:var(--fx-fill); border-radius:10px;
  overflow:hidden }
.ds-fx-sequence canvas{ width:100%; height:100%; display:block; object-fit:cover }
.ds-fx-sequence.ds-ratio-16-9{ aspect-ratio:16/9 }
.ds-fx-sequence.ds-ratio-4-3{ aspect-ratio:4/3 }
.ds-fx-sequence.ds-ratio-1-1{ aspect-ratio:1/1 }
.ds-fx-sequence.ds-ratio-9-16{ aspect-ratio:9/16 }

/* -------------------------------------------------------------- skew */
.ds-fx-skew{ display:grid; gap:max(20px, min(4vw, 60px), 3.125vw) }
.ds-skew-item{ will-change:transform }
.ds-skew-item img{ width:100%; height:auto; display:block; border-radius:10px }

/* An effect that threw shows its content plainly rather than half-moved. */
.ds-fx.is-plain *{ opacity:1 !important; transform:none !important; clip-path:none !important }

@media (prefers-reduced-motion: reduce){
  .ds-fx *{ transition:none !important }
  .ds-trail{ display:none }
}

/* =====================================================================
 * The second set
 * ================================================================== */

/* ------------------------------------------------ cursor image preview */
/* No space under it by default.
 *
 * Every element carries --ds-el-gap below it, which is right for something that
 * ends where its content ends. This one ends on a rule: the last row draws its
 * bottom border, so the gap sits under a line and reads as the element having
 * been left unfinished rather than as spacing.
 *
 * A default, not a rule - Space on the element still sets a margin where one is
 * wanted, because that is written on the element itself and outranks this. */
/* The rules are this element's edges.
 *
 * Space on the element is handed to the rows instead of padding the box - see
 * the renderer - so the padding it writes onto the element itself is taken
 * back off here. Without this the figure would apply twice: once above the
 * first line and once inside every row.
 *
 * Written as its own declaration rather than a shorthand, so the left and
 * right of a Space setting still inset the element the way they do anywhere
 * else. It is only the vertical pair that belongs to the rows. */
.ds-fx-imagepreview{ position:relative; margin-bottom:0;
  padding-top:0 !important; padding-bottom:0 !important }
/* No padding out of the box.
 *
 * It was a clamp written here, which is a decision about somebody else's page
 * taken in the stylesheet: a row of one heading wants less room than a row of
 * three elements, and neither figure was reachable from the editor. A row is
 * now exactly as tall as what is in it, and the space between the words and the
 * rule is Row padding on the element.
 *
 * The typed-list rows keep the old figure - they hold a single line of words
 * with nothing to space them, so nought there is a list with no gaps at all. */
.ds-ip-row{ display:block; padding:var(--ds-ip-pad, 0); text-decoration:none; color:inherit;
  border-top:var(--ds-ip-bw, 1px) solid var(--ds-ip-bc, var(--fx-line));
  font-size:max(1.4rem, min(3.4vw, 2.8rem), 2.3333vw) }
/* The typed-list rows keep the old figure - they hold a single line of words
   with no element inside to space them, so nought there is a list with no gaps
   at all. A placed row is a Box and uses the Box's own Space. */
a.ds-ip-row{ padding:max(14px, min(2vw, 26px), 1.3542vw) 0 }
.ds-ip-row:last-of-type{ border-bottom:var(--ds-ip-bw, 1px) solid var(--ds-ip-bc, var(--fx-line)) }
.ds-ip-row:hover span{ opacity:.55 }
.ds-ip-row span{ transition:opacity .3s ease }

/* A row built out of elements rather than typed as a line.
 *
 * The rules above dress a link holding one line of words: a display size, and a
 * fade on the span inside it. Neither is right for a row holding a kicker, a
 * heading and a sentence - the size would set the type for all three at once,
 * and the fade would catch every span in them, including the one wrapping a
 * colored word inside a heading.
 *
 * So a placed row keeps the rule between rows and the air around them, and
 * everything else is whatever the elements inside were set to. The row fades as
 * a whole rather than the words in it: one transition instead of one per span,
 * and it reads as the row answering rather than the text flickering. */
.ds-ip-row.ds-ip-holds{ font-size:inherit }
.ds-ip-row.ds-ip-holds:hover span{ opacity:1 }

/* ---- what a row does under the cursor ----
 *
 * Dim was written into the element with no way to turn it off. It is a taste
 * rather than a behavior, and on pale words against black it reads as the row
 * switching off rather than answering, so it is one of six now and still the
 * default - every page already using this looks exactly as it did.
 *
 * The transition is on the row whatever is chosen, so nothing snaps, and it
 * names the properties rather than using `all` - a row holding a picture would
 * otherwise transition that picture's every computed change as well. */
.ds-ip-row,
.ds-ip-row > *{ transition:opacity .3s ease, transform .3s cubic-bezier(.22,1,.36,1) }

/* The row hovered goes quieter. */
/* The contents dim, not the row.
 *
 * Opacity on the row fades its own border with it, so the rule between rows
 * faded in and out as the cursor passed - the one part of the element that
 * should stay put. The children carry it instead; they do not overlap, so
 * fading each is the same picture as fading the group. */
.ds-ip-hv-dim .ds-ip-row:hover > *{ opacity:var(--ds-ip-dim, .55) }

/* The others go quieter instead, so the eye is led by what stayed bright.
   :has on the element, so it only applies while a row is actually hovered -
   without it every row would sit dimmed until the cursor arrived. */
.ds-ip-hv-quiet:has(.ds-ip-row:hover) .ds-ip-row > *{ opacity:var(--ds-ip-dim, .4) }
.ds-ip-hv-quiet:has(.ds-ip-row:hover) .ds-ip-row:hover > *{ opacity:1 }

.ds-ip-hv-lift .ds-ip-row:hover{ transform:translateY(-6px) }
.ds-ip-hv-slide .ds-ip-row:hover{ transform:translateX(20px) }
.ds-ip-hv-grow .ds-ip-row:hover{ transform:scale(1.03) }

/* Grow and slide move the row over the rule above or below it, so the one
   being pointed at is drawn on top rather than under its neighbor. */
.ds-ip-hv-lift .ds-ip-row:hover,
.ds-ip-hv-slide .ds-ip-row:hover,
.ds-ip-hv-grow .ds-ip-row:hover{ position:relative; z-index:2 }

/* The row fills black from its bottom edge and its words turn white.
 *
 * Drawn by a pseudo-element scaled up from the bottom, not by a background
 * transition - a background cannot slide, it can only cross-fade. It sits
 * behind the row's own contents inside the row's own stacking context, so
 * nothing on the page above or below is covered.
 *
 * A table row cannot carry a positioned pseudo-element of its own in any
 * browser worth naming, so for a row that is a <tr> each cell draws its
 * share; they abut, so the eye sees one bar rising under the whole row. */
.ds-ip-hv-fill .ds-ip-row,
.ds-ip-hv-fill tr[data-row] > *{ position:relative; z-index:0;
  transition:color .35s ease, opacity .3s ease, transform .3s cubic-bezier(.22,1,.36,1) }
.ds-ip-hv-fill .ds-ip-row::before,
.ds-ip-hv-fill tr[data-row] > *::before{ content:""; position:absolute; inset:0; z-index:-1;
  background:#000; transform:scaleY(0); transform-origin:bottom;
  transition:transform .35s cubic-bezier(.22,1,.36,1) }
/* Each cell's share overlaps its neighbors by a pixel. Fixed-layout columns
   land on fractional widths, and two boxes meeting on a fraction leave a
   hairline of the page showing between them. The first and last keep the
   table's own edge. */
.ds-ip-hv-fill tr[data-row] > *::before{ left:-1px; right:-1px }
.ds-ip-hv-fill tr[data-row] > :first-child::before{ left:0 }
.ds-ip-hv-fill tr[data-row] > :last-child::before{ right:0 }
.ds-ip-hv-fill .ds-ip-row:hover,
.ds-ip-hv-fill tr[data-row]:hover > *{ color:#fff }
.ds-ip-hv-fill .ds-ip-row:hover::before,
.ds-ip-hv-fill tr[data-row]:hover > *::before{ transform:scaleY(1) }

/* Nothing is a real answer, and reduced motion turns the moving ones into
   nothing rather than into a different movement. */
@media (prefers-reduced-motion: reduce){
  .ds-ip-row{ transition:none }
  .ds-ip-hv-lift .ds-ip-row:hover,
  .ds-ip-hv-slide .ds-ip-row:hover,
  .ds-ip-hv-grow .ds-ip-row:hover{ transform:none }
  .ds-ip-hv-fill .ds-ip-row::before,
  .ds-ip-hv-fill tr[data-row] > *::before{ transition:none }
}

/* The row already carries its own air, so the first and last thing in it do
   not add more against the rule. */
.ds-ip-row.ds-ip-holds > .ds-el:first-child{ margin-top:0 }
.ds-ip-row.ds-ip-holds > .ds-el:last-child{ margin-bottom:0 }
.ds-ip-peek{ position:absolute; top:0; left:0; width:var(--ds-ip-w, max(180px, min(22vw, 320px), 16.6667vw));
  pointer-events:none; opacity:0; visibility:hidden; z-index:3; will-change:transform }
/* Two pictures in one panel, stacked.
 *
 * The first is in the flow and gives the panel its height; the second is laid
 * over it. Moving from row to row shows the new one while the old is still
 * leaving, which a single image cannot do - swapping its source is instant
 * however long the fade around it lasts. */
.ds-ip-peek img{ width:100%; height:auto; display:block; border-radius:8px }
.ds-ip-peek img + img{ position:absolute; inset:0; height:100% }

/* A row that follows its own link says so under the cursor.
 *
 * Nothing is drawn over the row and nothing is wrapped around it - the script
 * takes the click and follows the anchor the row already holds. An anchor laid
 * on top would sit between the cursor and anything in the row meant to be
 * clicked, and an anchor wrapped around it is not markup a browser keeps: it
 * closes at the first link inside and the rest of the row falls outside it. */
.ds-ip-row.ds-ip-linked{ cursor:pointer }
/* A table row that carries a link of its own. The whole row answers the
   pointer, and the link inside it is not underlined or recolored - the row is
   the thing being clicked, and the name is not a link sitting in a row of
   text that is not one. */
tr.ds-ip-linked{ cursor:pointer }
tr.ds-ip-linked a{ color:inherit; text-decoration:none }
/* Nothing is chosen until a row is hovered, and an img with no src draws a
   broken-image icon in some browsers. */
.ds-ip-peek img:not([src]){ display:none }

/* --------------------------------------------------- full screen sections */

/* The element is one screen, and every section is stacked inside it.
 *
 * Its own stacking context and its own clipping, so the sections cannot be
 * seen where they are parked and cannot escape over the rest of the page. */
.ds-fx-sections{
  position:relative;
  height:100svh;
  overflow:hidden;
  isolation:isolate;
}

/* Each section fills it and they sit on top of one another. Hidden until it is
   this one's turn - visibility rather than display, so a hidden section keeps
   its size and its pictures stay decoded. */
.ds-sec{
  position:absolute;
  inset:0;
  visibility:hidden;
}

/* The two wrappers that make the lag.
 *
 * They travel opposite ways by the same amount, so the contents of the inner
 * one stand still while the pair of them move - and then the picture inside is
 * given its own smaller travel on top. That is the parallax: three boxes, two
 * of which cancel out. Both clip, or the panel being uncovered shows the one
 * behind it down the edge. */
.ds-sec-outer,
.ds-sec-inner{
  width:100%;
  height:100%;
  overflow:hidden;
}

.ds-sec-bg{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
}

.ds-sec-bg img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

/* The words sit over the picture, and have to stay legible on any of them. */
.ds-sec-say{
  position:relative;
  z-index:2;
  margin:0;
  display:grid;
  place-items:center;
  height:100%;
  padding:0 6vw;
  text-align:center;
  color:#fff;
  text-shadow:0 2px 30px rgba(0,0,0,.45);
  font-size:max(2rem, min(7vw, 6rem), 5vw);
  line-height:1.05;
}

/* Which section is showing, and a way to jump to one. */
.ds-sec-dots{
  position:absolute;
  right:max(14px, min(2vw, 28px), 1.4583vw);
  top:50%;
  transform:translateY(-50%);
  z-index:5;
  display:grid;
  gap:10px;
}

.ds-sec-dot{
  width:10px;
  height:10px;
  padding:0;
  border:1px solid rgba(255,255,255,.7);
  border-radius:50%;
  background:none;
  cursor:pointer;
}

.ds-sec-dot.is-here{ background:#fff }

/* ----------------------------------------------------- before and after */

/* The two pictures stack, and the element is the size of them.
 *
 * Height from the aspect ratio rather than a fixed figure, so the two photos
 * are the same shape as each other whatever they are - two pictures of the same
 * scene compared at different proportions is not a comparison. */
.ds-fx-compare{
  position:relative;
  overflow:hidden;
  aspect-ratio:16/9;
  border-radius:10px;
}

.ds-cmp-pic{
  position:absolute;
  inset:0;
  overflow:hidden;
}

/* The picture is the full element, not the full wrapper.
 *
 * The after wrapper is narrowed and slid by the script, and an image sized to
 * its wrapper would squeeze with it. Sized to the element it stays the same
 * picture while the wrapper travels over it. */
.ds-cmp-pic img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

/* Sits above the before picture, and is the one that moves. */
.ds-cmp-after{ z-index:2 }

/* ------------------------------------------------------- bento gallery */
/* The whole arrangement on one screen.
 *
 * Rows were a minimum height each, so eight cells came to about twice the
 * height of the window and most of the gallery sat below the fold. That is
 * wrong twice over: the arrangement is the thing being shown and half of it
 * could not be seen, and the cell in the middle of the grid was nowhere near
 * the middle of the screen - so the zoom picked a picture the reader was not
 * looking at.
 *
 * 1fr rows share whatever height there is, so the grid is the window however
 * many pictures are in it. svh rather than vh: a phone's address bar sliding
 * away must not resize a grid that is pinned and being measured. */
.ds-fx-bento{ display:grid; grid-template-columns:repeat(6,1fr);
  grid-auto-rows:1fr; height:100svh; gap:max(8px, min(1.2vw, 16px), 0.8333vw) }
.ds-bento-cell{ margin:0; overflow:hidden; border-radius:10px; background:var(--fx-fill) }
.ds-bento-cell img{ width:100%; height:100%; object-fit:cover; display:block }

/* A cell that opens holds a button, and the picture is inside that.
 *
 * The cell sizes its image to fill it, so with a button in between the image
 * fills the button instead - and a button is only as tall as its contents,
 * which collapses the cell to nothing. The button has to be the full cell for
 * the image to have something to fill.
 *
 * zoom-in rather than the plain pointer the shared rule sets: this one opens a
 * bigger copy, and the cursor is the only thing that says so before the click. */
.ds-bento-cell .ds-lb-open{
  width:100%;
  height:100%;
  cursor:zoom-in;
}

/* No hover zoom on a bento.
 *
 * The clickable picture brings one with it - every other element that opens a
 * lightbox nudges the image up a couple of per cent under the pointer. Here the
 * scroll is already scaling the whole grid, and a second scale on one cell
 * fights it: the picture creeps as the pointer crosses it, in the middle of a
 * movement the reader is driving with the wheel.
 *
 * The transition goes too, or the image eases back down from the hover after
 * the pointer leaves and that eases against the scroll as well. */
.ds-bento-cell .ds-lb-open img,
.ds-bento-cell .ds-lb-open:hover img{
  transform:none;
  filter:none;
  transition:none;
}
/* The one arrangement. The zoom is a transform over the top of it, so there is
   no second layout to switch to and the spans are simply what the grid is. */
.ds-bento-cell.c1{ grid-column:span 4; grid-row:span 2 }
.ds-bento-cell.c2{ grid-column:span 2 }
.ds-bento-cell.c3{ grid-column:span 2 }
.ds-bento-cell.c4{ grid-column:span 2; grid-row:span 2 }
.ds-bento-cell.c5{ grid-column:span 2 }
.ds-bento-cell.c6{ grid-column:span 2 }

/* The grid grows far past its own box on the way in, and everything outside
   that box has to be cut off - otherwise the cells being carried outwards lie
   over whatever is above and below, and the ones going sideways give the page a
   horizontal scrollbar.
 *
 * Cut on the pinned wrapper rather than on the grid itself: the grid is the
 * thing being transformed, and an element cannot clip its own transform. The
 * wrapper is the pin-spacer, which is GSAP's and only exists while the section
 * is pinned - which is exactly when there is anything to clip. */
.pin-spacer:has(> .ds-fx-bento){ overflow:clip }



/* --------------------------------------------------------- radial menu */
.ds-fx-radial{ position:relative; min-height:320px; display:grid; place-items:center }
.ds-radial-btn{ position:relative; z-index:2; width:110px; height:110px; border-radius:50%;
  border:1px solid currentColor;
  background:var(--ds-radial-btn, var(--ds-bg,#0b0b0c)); color:inherit;
  cursor:pointer; font:inherit; font-size:.95rem }
.ds-radial-ring{ position:absolute; inset:0; display:grid; place-items:center;
  pointer-events:none }
.ds-fx-radial.is-open .ds-radial-ring{ pointer-events:auto }
.ds-radial-item{ position:absolute; padding:11px 20px; border-radius:999px;
  border:1px solid var(--fx-line);
  background:var(--ds-radial-arc, var(--ds-bg,#0b0b0c)); color:inherit;
  text-decoration:none; font-size:.9rem; white-space:nowrap }
.ds-radial-item:hover{ border-color:currentColor }

/* --------------------------------------------------------------- the pit */

/* A pit in a full screen row is the height of that row.
 *
 * The same figure the row itself is given, written as a definite length rather
 * than a percentage of its parents. That distinction is the whole point: the
 * engine reads clientHeight once, when it builds the floor, and a percentage
 * height has to wait for a flex chain to resolve before it means anything. It
 * had not resolved by then, so the box ended up one height and its floor
 * another, and the pictures dropped straight through the bottom of the row.
 * 100svh needs nothing resolved and is right on the first read.
 *
 * Left at its own height on the first row of a page, where the row is 100svh
 * less whatever stands above it and the two figures would disagree again. */
.ds-row-screen .ds-fx-pit{ height:100svh }

.ds-main > .ds-row:first-child.ds-row-screen .ds-fx-pit,
.ds-main > :first-child > .ds-row:first-child.ds-row-screen .ds-fx-pit{
  height:var(--ds-pit-h, 460px);
}

.ds-fx-pit{
  position:relative; overflow:hidden;
  height:var(--ds-pit-h, 460px);
  /* Its own width only where one was given; otherwise the column it stands in.
     Capped at the column either way, so a figure wider than the page cannot
     push the row sideways. */
  width:var(--ds-pit-w, 100%);
  max-width:100%;
  /* The pointer drives the pit, so a drag must not also be the browser
     selecting the pictures or panning the page sideways. Vertical panning is
     left alone - the page still scrolls through a pit under a finger. */
  touch-action:pan-y;
  -webkit-user-select:none; user-select:none;
}

/* Laid out only once the engine has them.
 *
 * Before that they are still in the flow, so a pit whose script never arrives
 * is a row of pictures rather than a heap in one corner - every one of them
 * would otherwise sit at 0,0 on top of the last. */
.ds-fx-pit[data-pit-ready] .ds-pit-item{ position:absolute; top:0; left:0 }

/* The words behind the pit.
 *
 * Centered on its own layer under the pictures, and not laid out absolutely by
 * the engine the way the pictures are - it is scenery, not a body. Nothing here
 * catches the pointer, so a drag that starts over a letter still picks up the
 * picture on top of it. */
.ds-pit-say{
  position:absolute; inset:0; z-index:0;
  display:grid; place-items:center;
  pointer-events:none;
  padding:0 4%;
}

.ds-pit-item{
  display:inline-block; vertical-align:top;
  will-change:transform;
  cursor:grab;
  position:relative; z-index:1;
}
.ds-fx-pit.is-held .ds-pit-item{ cursor:grabbing }

.ds-pit-item img{
  display:block; width:100%; height:100%;
  object-fit:cover; pointer-events:none; -webkit-user-drag:none;
}

.ds-fx-pit[data-pit-still] .ds-pit-item{ cursor:auto }

/* ----------------------------------------------- scroll progress links */
/* The rail stays put while the sections it tracks go past.
 *
 * It was static, so it scrolled out of the top of the window as soon as the
 * first section started - every link was tracking its target correctly and
 * filling its own rule, above the fold, where none of it could be seen. A
 * progress rail that leaves before the progress happens has nothing to show.
 *
 * align-self keeps it the height of its own links: in a column it would
 * otherwise stretch the full height of the row, and a sticky box as tall as
 * its container has nowhere to travel and never sticks. */
.ds-fx-progresslinks{
  display:grid; gap:4px;
  position:sticky; top:var(--ds-pl-top, 96px);
  align-self:start;
}
.ds-fx-progresslinks[data-side="right"]{ justify-items:end }
.ds-pl-link{ display:flex; align-items:center; gap:12px; padding:7px 0;
  text-decoration:none; color:inherit; opacity:.5; transition:opacity .3s ease }
.ds-pl-link.is-here{ opacity:1 }

/* The name first, and the bar running out to meet the content.
 *
 * The bar used to sit in front of the name at a fixed 52px, which left every
 * label starting part way across its own column and a gap of nothing between
 * the list and the content beside it. The name now starts at the left edge
 * like everything else on the page, and the bar takes whatever room is left -
 * so it reaches the end of the column, which is the edge of the content.
 *
 * Only where the list sits on the left. On the right the whole list is set
 * against that edge and the bar leading into the name is the correct way
 * round. */
.ds-fx-progresslinks:not([data-side="right"]) .ds-pl-link{ width:100% }
.ds-fx-progresslinks:not([data-side="right"]) .ds-pl-link span{ order:0; flex:0 0 auto }
.ds-fx-progresslinks:not([data-side="right"]) .ds-pl-link i{ order:1; width:auto; flex:1 1 auto; min-width:24px }

/* A Progress section holds its own contents and adds nothing of its own to how
   they look - it is a name, an address and a length for the rail to measure.
   The scroll margin keeps its top clear of a sticky header when a link travels
   to it, so the first line is not left sitting underneath the bar. */
.ds-pl-section{ display:block; scroll-margin-top:var(--ds-pl-top, 96px) }

/* Sections held in one place, taking turns.
 *
 * The column keeps a real height - the tallest section, measured and written
 * on it - so the row it is in is as tall as its content and the pin has
 * something to hold. The sections themselves are stacked over that space, so
 * one replaces another instead of following it down the page. */
.ds-pl-stage{ position:relative }
.ds-pl-stage > .ds-pl-held{ position:absolute; inset:0 }
.ds-pl-stage > .ds-pl-held:first-of-type{ position:relative }

/* One section showing at a time, faded by the stylesheet.
 *
 * The fade is a transition rather than a tween on purpose: gsap's ticker stops
 * when the browser decides the tab is idle, and a tween that never gets a
 * frame leaves whichever section was showing first on screen for ever. A class
 * and a transition are drawn by the browser and do not need the loop. */
.ds-pl-stage > .ds-pl-held{
  opacity:0; visibility:hidden;
  transition:opacity .35s ease, visibility .35s ease;
}
.ds-pl-stage > .ds-pl-held.is-on{ opacity:1; visibility:visible }

@media (prefers-reduced-motion: reduce){
  .ds-pl-stage > .ds-pl-held{ transition:none }
}
.ds-pl-link i{ display:block; width:52px; height:2px;
  background:var(--ds-prog-track, var(--fx-line));
  position:relative; overflow:hidden }
.ds-pl-link i::after{ content:''; position:absolute; inset:0;
  background:var(--ds-prog-fill, currentColor);
  transform:scaleX(var(--ds-progress,0)); transform-origin:0 50% }

/* ----------------------------------------------------------- waypoints */
.ds-fx-waypoints{ display:grid; grid-template-columns:auto 1fr; gap:max(18px, min(3vw, 40px), 2.0833vw) }
.ds-wp-rail{ position:relative; width:34px }
.ds-wp-line{ position:absolute; left:16px; top:0; bottom:0; width:1px;
  background:var(--ds-way-rail, var(--fx-line)) }
.ds-wp-rider{ position:absolute; left:0; top:0; width:34px; height:34px; z-index:2;
  display:grid; place-items:center }
.ds-wp-rider img{ width:100%; height:100%; object-fit:cover; border-radius:50%; display:block }
.ds-wp-dot{ width:12px; height:12px; border-radius:50%; display:block;
  background:var(--ds-way-mark, currentColor) }
.ds-wp-list{ list-style:none; margin:0; padding:0; display:grid; gap:max(24px, min(5vw, 70px), 3.6458vw) }
.ds-wp-stop{ opacity:.4; transition:opacity .35s ease }
.ds-wp-stop.is-here{ opacity:1 }
.ds-wp-stop b{ display:block; font-size:.8rem; letter-spacing:.14em; opacity:.6 }
.ds-wp-stop span{ display:block; margin-top:6px; font-size:max(1.2rem, min(2.6vw, 2rem), 1.6667vw) }

/* ------------------------------------------------------- looped panels */
.ds-fx-loopedpanels{ position:relative; height:74svh; min-height:400px; overflow:hidden;
  border-radius:12px; touch-action:none }
.ds-lp-slide{ position:absolute; inset:0; display:grid; place-items:center;
  background:var(--ds-bg,#0b0b0c) center/cover no-repeat }
.ds-lp-slide h3{ margin:0; position:relative; z-index:2; text-align:center; padding:0 6vw;
  font-size:max(1.8rem, min(6vw, 4.6rem), 3.8333vw) }
.ds-lp-slide[style]::after{ content:''; position:absolute; inset:0; background:rgba(0,0,0,.45) }

/* --------------------------------------------------------- curved edge */
.ds-fx-curveedge{ line-height:0; color:var(--ds-bg,#0b0b0c) }
.ds-fx-curveedge svg{ width:100%; height:max(60px, min(9vw, 140px), 7.2917vw); display:block }
.ds-fx-curveedge[data-edge="top"]{ transform:rotate(180deg) }

/* -------------------------------------------------------- rolling text */
.ds-fx-rollingtext{ display:grid; gap:2px }
.ds-align-center.ds-fx-rollingtext{ justify-items:center }
.ds-align-right.ds-fx-rollingtext{ justify-items:end }
.ds-roll{ display:block; text-decoration:none; color:inherit; overflow:hidden;
  height:1.1em; line-height:1.1; font-size:max(1.6rem, min(4.4vw, 3.4rem), 2.8333vw) }
.ds-roll-in{ display:block; transition:transform .45s cubic-bezier(.22,1,.36,1) }
.ds-roll-in i{ display:block; font-style:normal; height:1.1em }
.ds-roll:hover .ds-roll-in,.ds-roll.is-on .ds-roll-in{ transform:translateY(-1.1em) }

/* -------------------------------------------------------- flip gallery */
.ds-fx-flipgallery{ position:relative; display:grid;
  grid-template-columns:repeat(var(--ds-cols,3),1fr); gap:max(8px, min(1.4vw, 18px), 0.9375vw) }
.ds-fg-cell{ padding:0; border:0; background:none; cursor:zoom-in; line-height:0;
  border-radius:10px; overflow:hidden }
.ds-fg-cell img{ width:100%; height:100%; object-fit:cover; display:block }
.ds-fg-cell.is-open{ position:fixed; inset:6vh 8vw; z-index:9999; cursor:zoom-out;
  border-radius:12px }
.ds-fg-cell.is-open img{ object-fit:contain; background:#000 }
.ds-fg-veil{ position:fixed; inset:0; z-index:9998; background:rgba(6,6,7,.9);
  backdrop-filter:blur(10px) }
.ds-fg-veil[hidden]{ display:none }


/* ------------------------------------------------------- flip carousel */
.ds-fx-flipcarousel{ display:grid; gap:18px }
.ds-fc-stage{ position:relative; height:max(280px, min(42vw, 520px), 27.0833vw) }
.ds-fc-item{ position:absolute; top:0; left:0; margin:0; width:62%; height:100%;
  border-radius:12px; overflow:hidden; background:var(--fx-fill) }
.ds-fc-item img{ width:100%; height:100%; object-fit:cover; display:block }
.ds-fc-item figcaption{ position:absolute; left:0; right:0; bottom:0; padding:14px 18px;
  font-size:.9rem; background:linear-gradient(transparent,rgba(0,0,0,.7)) }
.ds-fc-item.pos-0{ left:0; z-index:4 }
.ds-fc-item.pos-1{ left:26%; z-index:3; transform:scale(.93); opacity:.85 }
.ds-fc-item.pos-2{ left:44%; z-index:2; transform:scale(.86); opacity:.6 }
.ds-fc-item.pos-3{ left:56%; z-index:1; transform:scale(.8); opacity:.35 }
.ds-fc-btns{ display:flex; gap:10px }
.ds-fc-btns button{ width:46px; height:46px; border-radius:50%; cursor:pointer;
  border:1px solid var(--fx-line); background:none; color:inherit; font-size:1rem }
.ds-fc-btns button:hover{ border-color:currentColor }

/* --------------------------------------------------------- filter grid */
.ds-fgd-bar{ display:flex; flex-wrap:wrap; gap:8px; margin:0 0 max(16px, min(2.4vw, 28px), 1.4583vw) }
.ds-fgd-bar button{ padding:9px 18px; border-radius:999px; cursor:pointer; font:inherit;
  font-size:.85rem; border:1px solid var(--fx-line); background:none; color:inherit;
  opacity:.6; transition:opacity .25s ease, border-color .25s ease }
.ds-fgd-bar button.on{ opacity:1; border-color:currentColor }
.ds-fgd-grid{ display:grid; grid-template-columns:repeat(var(--ds-cols,3),1fr);
  gap:max(10px, min(1.6vw, 20px), 1.0417vw) }
.ds-fgd-cell{ margin:0; border-radius:10px; overflow:hidden; background:var(--fx-fill) }
.ds-fgd-cell[hidden]{ display:none }
.ds-fgd-cell img{ width:100%; height:auto; display:block; aspect-ratio:4/3; object-fit:cover }
.ds-fgd-cell figcaption{ padding:12px 14px; font-size:.85rem; opacity:.7 }


/* ------------------------------------------------- directional marquee */
.ds-fx-dirmarquee{ overflow:hidden; padding:max(10px, min(1.6vw, 20px), 1.0417vw) 0;
  border-block:1px solid var(--fx-line) }
.ds-dm-track{ display:flex; width:max-content; will-change:transform }
.ds-dm-item{ padding:0 max(18px, min(2.6vw, 40px), 2.0833vw); white-space:nowrap;
  font-size:max(1.4rem, min(4vw, 3.4rem), 2.8333vw); line-height:1.1 }

/* --------------------------------------------------------- curve swipe */
.ds-fx-curveswipe{ position:relative; overflow:hidden; border-radius:12px;
  min-height:max(320px, min(46vw, 600px), 31.25vw); display:grid }
.ds-cs-stage{ position:relative; grid-area:1/1 }
.ds-cs-panel{ position:absolute; inset:0; display:grid; place-items:center;
  background:var(--fx-fill) }
.ds-cs-panel h3{ margin:0; font-size:max(1.8rem, min(6vw, 4.4rem), 3.6667vw); text-align:center; padding:0 6vw }
.ds-cs-veil{ grid-area:1/1; width:100%; height:100%; position:relative; z-index:3;
  color:var(--ds-bg,#0b0b0c); pointer-events:none }
/* Top, not bottom. The stage is up to 600px tall, so a control pinned to the
   bottom sits a long way from the text that explains what it does. */
.ds-cs-btn{ position:absolute; right:20px; top:20px; z-index:4; padding:12px 26px;
  border-radius:999px; border:1px solid currentColor; background:var(--ds-bg,#0b0b0c);
  color:inherit; cursor:pointer; font:inherit; font-size:.9rem }

/* ----------------------------------------------------- scroll waypoints */
.ds-fx-waypointpath{ position:relative }
.ds-wpp-svg{ width:100%; height:auto; display:block; color:currentColor }
.ds-wpp-rider{ position:absolute; top:0; left:0; width:44px; height:44px;
  display:grid; place-items:center }
.ds-wpp-rider img{ width:100%; height:100%; object-fit:cover; border-radius:50%; display:block }
.ds-wpp-stops{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:18px; margin-top:max(16px, min(3vw, 34px), 1.7708vw) }
.ds-wpp-stop{ opacity:.4; transition:opacity .35s ease }
.ds-wpp-stop.is-here{ opacity:1 }
.ds-wpp-stop b{ display:block; font-size:.78rem; letter-spacing:.14em; opacity:.6 }
.ds-wpp-stop span{ display:block; margin-top:5px; font-size:1.05rem }


/* --------------------------------------------------------- full width text */
/* The Full width text row in Typography, same as the footer block reads.
 *
 * The row was written for one of the three things on the site that draw a line
 * of type across a whole column, and only that one was reading it - so setting
 * the full width font changed the footer and left the element and the words
 * behind an image trail on whatever they had. One row, everything it names.
 *
 * The element's own Weight and Tracking are more specific than this, so a page
 * that sets them still wins - this is what it falls back to. */
.ds-fx-fittext{ width:100% }
/* Clipped sideways only.
 *
 * overflow:hidden clips both ways, and a line box set under 1em is shorter
 * than the letters in it - so the tops of a display line were sliced flat. The
 * clipping is here to stop a line overflowing its column before the size has
 * been worked out, which is a horizontal problem; clip on one axis leaves the
 * other visible, where hidden would force it to scroll. */
/* On its own layer.
 *
 * A line this size sits inside a smooth scroller, which moves the whole page by
 * fractions of a pixel - so the glyphs were re-rasterized at a slightly
 * different sub-pixel offset on every frame, and 265px letterforms redrawn
 * sixty times a second shimmer. Promoted, it is drawn once and moved, which is
 * what a compositor is for. */
.ds-fit{ margin:0; width:100%; white-space:nowrap;
  overflow-x:clip; overflow-y:visible;
  transform:translateZ(0); will-change:transform; backface-visibility:hidden;
  font-family:var(--ds-fullwidth-family, var(--ds-font-fullwidth, var(--ds-font-heading)));
  font-weight:var(--fit-weight, var(--ds-fullwidth-weight,500));
  text-transform:var(--ds-fullwidth-case,none);
  letter-spacing:var(--ds-fullwidth-track,-0.02em);
  line-height:var(--ds-fullwidth-lh,.9) }
.ds-fit [data-fittext]{ display:inline-block; white-space:nowrap }
.ds-fx-fittext[data-tight="tight"] .ds-fit{ letter-spacing:-.03em }
.ds-fx-fittext[data-tight="wide"] .ds-fit{ letter-spacing:.06em }
.ds-fx-fittext[data-weight="300"] .ds-fit{ font-weight:300 }
.ds-fx-fittext[data-weight="400"] .ds-fit{ font-weight:400 }
.ds-fx-fittext[data-weight="600"] .ds-fit{ font-weight:600 }
.ds-fx-fittext[data-weight="700"] .ds-fit{ font-weight:700 }
.ds-fx-fittext[data-weight="800"] .ds-fit{ font-weight:800 }

/* ------------------------------------------------------ shaded pictures */
.ds-shaded{ position:relative; display:block; line-height:0 }
.ds-shaded img{ width:100%; height:auto; display:block }
.ds-shaded canvas{ position:absolute; inset:0; width:100%; height:100%;
  display:block; opacity:0; transition:opacity .3s ease }

/* The canvas takes over only once it has the picture on it. Until then, and
   for ever on a browser without WebGL, the img is what is seen. */
.ds-shaded.is-shaded canvas{ opacity:1 }
.ds-shaded.is-shaded img{ visibility:hidden }

/* ---------------------------------------------------------------------
 * A marquee driven by the scroll
 *
 * Four copies on one line, moved a quarter of their width by the page. The
 * frame clips, so what leaves one side is already arriving at the other and
 * the line never shows an end.
 * ------------------------------------------------------------------ */
.ds-fx-marqueescroll{ overflow:hidden; width:100% }

.ds-fx-marqueescroll .ds-marquee-track{
  display:flex;
  align-items:center;
  width:max-content;
  will-change:transform;
}

.ds-fx-marqueescroll .ds-marquee-word{
  white-space:nowrap;
  font-size:var(--ds-marquee-size, max(2.5rem, min(9vw, 8rem), 6.6667vw));
  line-height:1;
}

/* Between one copy and the next. Blank leaves a plain gap, which is what a
   marquee with nothing set looks like. */
.ds-fx-marqueescroll .ds-marquee-sep{
  padding:0 var(--ds-marquee-gap, .35em);
  opacity:.55;
}

.ds-fx-marqueescroll .ds-marquee-word + .ds-marquee-word{
  padding-left:var(--ds-marquee-gap, .35em);
}

/* ---- the tickerboard ----
 *
 * One column per place, each exactly one digit tall with the digits stacked
 * inside it. The column is the mask; the strip is what moves. Sliding a strip
 * behind a window is what makes this a board turning rather than text being
 * swapped out, which is all it was before.
 *
 * The offset is carried in em, so a figure drawn at H1 on a desktop and H3 on a
 * phone keeps its digits registered without anything measuring pixels. */
.ds-tick{ display:inline-flex; align-items:baseline }
.ds-tick-col{
  display:inline-block;
  height:1em;
  overflow:hidden;
  /* The digits are the width of a figure, and tabular figures keep every place
     the same width so the number does not breathe as it turns. */
  font-variant-numeric:tabular-nums;
  vertical-align:baseline;
}
.ds-tick-strip{
  display:block;
  will-change:transform;
  transform:translateY(calc(var(--ds-tick-at, 0) * -1em));
}
.ds-tick-strip > span{ display:block; height:1em; line-height:1 }
/* A point or a minus sits on the line with them rather than in a window. */
.ds-tick-fixed{ display:inline-block; font-variant-numeric:tabular-nums }

/* Asked for less movement means the figure, not the journey. */
@media (prefers-reduced-motion: reduce){
  .ds-tick-strip{ transition:none }
}

/* ----------------------------------------------------- fill from left
 *
 * The words are painted with a gradient clipped to the glyphs - the same
 * technique the heading gradient uses - and what the scroll moves is where that
 * gradient sits, not the words themselves.
 *
 * Two stops at the same position, so the join is an edge sweeping across the
 * line rather than a soft smear. The sheet is twice the width of the line, so
 * sliding it from one end to the other carries that edge the whole way.
 *
 * The color is read off the element and written in as --ds-tfx-ink, so the
 * effect still never picks one: it works on any heading in any palette and
 * follows a color set anywhere above it.
 *
 * It cannot be currentColor, and that is why this never worked. The gradient was
 * built from currentColor and this rule sets color:transparent three lines
 * below - in the same rule. currentColor resolves against the element's own
 * computed color, so it resolved against the transparency just declared here
 * and every stop came out rgba(0,0,0,0): a transparent gradient, clipped to
 * text, with the text made transparent to show it through. Nothing on screen,
 * on any element, in any state.
 *
 * A color cannot be captured before it is overwritten from inside the rule that
 * overwrites it, so text-fx.js reads the computed color and writes it here as a
 * literal before adding the class. currentColor stays as the fallback so a run
 * that somehow arrives without it degrades to the old behavior rather than
 * disappearing. */
.ds-tfx-fill{
  --ds-tfx-dim: .33;
  background-image: linear-gradient(
    90deg,
    var(--ds-tfx-ink, currentColor) 0 50%,
    color-mix(in srgb, var(--ds-tfx-ink, currentColor) calc(var(--ds-tfx-dim, .33) * 100%), transparent) 50% 100%
  );
  background-size: 200% 100%;
  background-position-x: 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* Nothing to clip to on a browser that cannot, so the words keep their color
   and simply do not fill. Better a plain heading than an invisible one. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)){
  .ds-tfx-fill{ color: currentColor; -webkit-text-fill-color: currentColor; background-image: none }
}

/* Somebody who asked for less movement gets the finished line, not a third of
   one sitting there waiting for a scroll that will never animate it. */
@media (prefers-reduced-motion: reduce){
  .ds-tfx-fill{ background-position-x: 0% }
}

/* ---------------------------------------------------------------------------
 * Chart
 *
 * The shape and the key, side by side or stacked. Everything the chart needs
 * to be read is in the markup; this only places it and draws it in.
 * ------------------------------------------------------------------------ */
.ds-fx-chart{ display:flex; align-items:center; gap:max(16px, min(3vw, 40px), 2.0833vw); flex-wrap:wrap }
.ds-align-center.ds-fx-chart{ justify-content:center }
.ds-align-right.ds-fx-chart{ justify-content:flex-end }

/* Underneath rather than beside: the plot takes the full width it is given and
   the key runs under it, which is the only arrangement that survives a phone. */
.ds-chart-key-below.ds-fx-chart{ flex-direction:column; align-items:flex-start }
.ds-align-center.ds-chart-key-below.ds-fx-chart{ align-items:center }

/* Under the chart, the key belongs to it and sits close.
 *
 * The 40px that separates a chart from a key beside it is a column gutter; the
 * same figure underneath reads as a gap between two unrelated things, and the
 * key stopped looking like the chart's own labels. */
.ds-chart-key-below.ds-fx-chart{ gap:14px }

.ds-chart-plot{ flex:0 0 auto; width:var(--ds-chart-size,260px); max-width:100% }
.ds-chart-svg{ display:block; width:100%; height:auto; overflow:visible }


.ds-chart-web{ fill:none; stroke:currentColor; stroke-width:.4; opacity:.18 }

.ds-chart-key{ display:flex; flex-direction:column; gap:8px; min-width:0 }
.ds-chart-key-below .ds-chart-key{ flex-direction:row; flex-wrap:wrap; gap:8px 20px }

.ds-chart-item{ display:flex; align-items:baseline; gap:6px; min-width:0 }
.ds-chart-dab{ flex:0 0 auto; width:10px; height:10px; border-radius:2px; transform:translateY(1px) }
.ds-chart-label{ min-width:0; overflow-wrap:anywhere }
/* The figure sits with the words it belongs to.
 *
 * margin-left:auto pushed it to the far edge of the key, so a short label left
 * a hand's width of nothing between the name and its number and the two read as
 * separate columns rather than as one fact. */
.ds-chart-num{ font-variant-numeric:tabular-nums; opacity:.7 }

/* Drawn in.
 *
 * The shape is already correct in the markup; these only hide it so the script
 * can reveal it. Without script, or with motion turned down, everything below
 * is skipped and the chart is simply there - which is the state it has to be
 * readable in anyway. */
.ds-fx-chart[data-anim="yes"] .ds-chart-arc{ stroke-dasharray:0 9999 }
.ds-fx-chart[data-anim="yes"] .ds-chart-cols .ds-chart-bar{ transform:scaleY(0) }
/* The line is wiped in from its left rather than drawn by a dash offset.
   A dash length is measured in the drawing's own units, and this drawing is
   stretched to the column while its stroke is held off that stretch - so the
   two disagreed and the line came in as a row of gaps. A wipe is the same
   movement and cannot disagree with anything. */
.ds-fx-chart.ds-chart-line[data-anim="yes"] .ds-chart-svg{ clip-path:inset(0 100% 0 0) }
.ds-fx-chart[data-anim="yes"] .ds-chart-dot,
.ds-fx-chart[data-anim="yes"] .ds-chart-shape{ opacity:0 }

@media (prefers-reduced-motion: reduce){
  .ds-fx-chart[data-anim="yes"] .ds-chart-arc{ stroke-dasharray:inherit }
  .ds-fx-chart[data-anim="yes"] .ds-chart-cols .ds-chart-bar{ transform:none }
  .ds-fx-chart.ds-chart-line[data-anim="yes"] .ds-chart-svg{ clip-path:none }
  .ds-fx-chart[data-anim="yes"] .ds-chart-dot,
  .ds-fx-chart[data-anim="yes"] .ds-chart-shape{ opacity:1 }
}

/* Bars as columns.
 *
 * One column per figure: the bar in a well that gives every column the same
 * height to grow in, and the words underneath. Equal fractions, so a bar is
 * exactly as wide as the text it describes and sits directly over it. */
.ds-chart-bar.ds-fx-chart .ds-chart-plot{ width:min(100%, calc(var(--ds-chart-size,260px) * 2)) }

.ds-chart-cols{
  display:grid;
  grid-auto-flow:column;
  grid-auto-columns:1fr;
  gap:max(8px, min(2vw, 20px), 1.0417vw);
  align-items:end;
}

/* The well is the full height every bar is measured against, so two charts
   with different figures still stand on the same baseline. */
.ds-chart-well{ display:flex; align-items:flex-end; height:var(--ds-chart-size,260px) }
.ds-chart-cols .ds-chart-bar{ width:100%; transform-origin:50% 100% }

.ds-chart-tick{
  display:flex;
  align-items:baseline;
  gap:6px;
  flex-wrap:wrap;
  margin-top:10px;
}


/* The tooltip.
 *
 * One per chart, moved to whatever is being pointed at, rather than one node
 * per slice sitting in the markup waiting. It is positioned against the chart
 * itself so it cannot be clipped by a row's overflow. */
.ds-fx-chart{ position:relative }

.ds-chart-tip{
  position:absolute;
  z-index:6;
  pointer-events:none;
  opacity:0;
  transform:translate(-50%,-100%);
  margin-top:-10px;
  padding:6px 10px;
  border-radius:4px;
  background:var(--ds-heading,#111);
  color:var(--ds-bg,#fff);
  font-size:max(calc(13px * var(--ds-lo, .5333)), 0.6771vw);
  line-height:1.3;
  white-space:nowrap;
  transition:opacity .12s ease;
}

.ds-chart-tip.is-on{ opacity:1 }

/* The targets. Invisible, but they have to be reachable. */
.ds-chart-hit{ fill:transparent; cursor:pointer }
.ds-chart-arc,
.ds-chart-cols .ds-chart-bar{ cursor:pointer }

/* Pointed at, or reached by keyboard: the rest of the chart steps back so the
   one being read is obvious. */
.ds-fx-chart[data-tips="yes"] .ds-chart-arc:hover,
.ds-fx-chart[data-tips="yes"] .ds-chart-arc:focus-visible,
.ds-fx-chart[data-tips="yes"] .ds-chart-cols .ds-chart-bar:hover,
.ds-fx-chart[data-tips="yes"] .ds-chart-cols .ds-chart-bar:focus-visible{ filter:brightness(1.15) }

.ds-chart-hit:focus-visible{ outline:2px solid var(--ds-accent); outline-offset:2px }

/* ------------------------------------------------------------------
   A line, and a radar
   ------------------------------------------------------------------
   Both draw a shape in SVG and put their words over the top in HTML.
   Text inside the drawing is scaled by the viewBox, so it never matched
   the page it sat on; over the top it is the page's own type and can
   wrap rather than clip.
   ------------------------------------------------------------------ */
.ds-chart-graph{ position:relative }

/* A line is a figure over time and wants the width it is given. Only its
   height comes from the size setting; the drawing stretches to the column
   and the stroke is kept off the stretch. */
.ds-chart-line.ds-fx-chart .ds-chart-plot{ width:100%; flex:1 1 100% }
.ds-chart-line .ds-chart-graph{ height:var(--ds-chart-size,260px) }
.ds-chart-line .ds-chart-svg{ height:100% }

/* The dot sits on its own vertex and stays round however wide the chart is. */
.ds-chart-line .ds-chart-dot{
  position:absolute;
  width:10px;
  height:10px;
  border-radius:50%;
  transform:translate(-50%,-50%);
  cursor:pointer;
}
.ds-chart-line .ds-chart-dot:focus-visible{ outline:2px solid var(--ds-accent); outline-offset:3px }

/* The names under the points they belong to, each pulled to its own point
   rather than spaced evenly on a row of their own. */
.ds-chart-axis{ position:relative; height:1.6em; margin-top:10px }
.ds-chart-axis .ds-chart-tick{
  position:absolute;
  top:0;
  display:block;
  margin-top:0;
  white-space:nowrap;
  opacity:.75;
}

/* A radar is square, and its names are allowed outside that square.
 *
 * The room for them is a share of the box rather than a fixed measure, so a
 * chart in a narrow column shrinks its drawing and keeps its names instead of
 * standing at full size and hanging out of the column. The square never grows
 * past the size that was asked for. */
.ds-chart-radar.ds-fx-chart .ds-chart-plot{
  width:100%;
  max-width:calc(var(--ds-chart-size,260px) * 2);
  padding:1.4em 0;
}
/* Half the box is the drawing and a quarter each side is for the names, at
   every width - so the room a name gets shrinks with the chart rather than
   staying put while the column narrows around it. */
.ds-chart-radar .ds-chart-graph{
  width:min(var(--ds-chart-size,260px), 50%);
  aspect-ratio:1;
  margin-inline:auto;
}

/* A name wraps rather than hangs off the chart. The room each side is half
   the square, so anything longer than that turns onto a second line instead
   of pushing the column open. */
.ds-chart-point{
  position:absolute;
  font-size:inherit;
  line-height:1.2;
  max-width:46%;
  text-align:center;
  opacity:.75;
}

/* ------------------------------------------------------------------
   Progress bars
   ------------------------------------------------------------------
   The label and the figure sit above the bar, not inside it. A figure
   printed on the bar is unreadable exactly when the bar is shortest,
   which is when a low number most needs reading.
   ------------------------------------------------------------------ */
.ds-fx-meter{ display:grid; gap:max(12px, min(2vw, 20px), 1.0417vw) }
.ds-meter-row{ display:grid; gap:6px }
.ds-meter-said{ display:flex; align-items:baseline; gap:10px }
.ds-meter-label{ font-weight:600 }
.ds-meter-num{ margin-left:auto; font-variant-numeric:tabular-nums; opacity:.7 }
.ds-meter-track{
  position:relative;
  overflow:hidden;
  background:color-mix(in srgb, currentColor 12%, transparent);
}
.ds-meter-fill{
  height:100%;
  border-radius:inherit;
  background:var(--ds-accent, currentColor);
  transform-origin:left center;
}

/* Nothing has run yet: the bar is already at its figure, so a failed or
   absent script leaves a readable chart rather than an empty track. */
.ds-fx-meter[data-anim="yes"].is-armed .ds-meter-fill{ transform:scaleX(0) }

@media (prefers-reduced-motion:reduce){
  .ds-fx-meter[data-anim="yes"].is-armed .ds-meter-fill{ transform:none }
}

/* ------------------------------------------------------------------
   Social links
   ------------------------------------------------------------------
   The list itself is drawn by DS_Theme and dressed by the theme sheet,
   which already reads --ds-social-ink, --ds-social-bg, --ds-social-hit,
   --ds-social-radius and --ds-social-size. Nothing here restyles a
   mark; the element only sets those variables and places the row.
   ------------------------------------------------------------------ */
.ds-fx-social{ display:flex }
.ds-align-center.ds-fx-social{ justify-content:center }
.ds-align-right.ds-fx-social{ justify-content:flex-end }

.ds-fx-social .ds-footer-social{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  list-style:none;
  margin:0;
  padding:0;
}

/* Icon only: no hit box behind the mark, so a row of marks sits at the
   size that was asked for rather than inside a 38px circle of nothing. */
.ds-social-plain{ --ds-social-hit:auto; --ds-social-radius:0 }
.ds-social-plain .ds-footer-social a{ width:auto; height:auto }

/* In a shape: the mark keeps its size and the shape grows around it. */
.ds-social-round,
.ds-social-square{
  --ds-social-hit:calc(var(--ds-social-size,24px) * 1.9);
  --ds-social-bg:color-mix(in srgb, currentColor 10%, transparent);
}
.ds-social-round{ --ds-social-radius:50% }
.ds-social-square{ --ds-social-radius:6px }

/* ------------------------------------------------------------------
   Pricing table
   ------------------------------------------------------------------
   One plan. Three of them are three columns, so the card fills the
   height it is given and the button is pushed to the bottom - a row of
   plans with different numbers of features still has its buttons on
   one line.
   ------------------------------------------------------------------ */
.ds-fx-pricing{
  display:flex;
  flex-direction:column;
  gap:14px;
  position:relative;
}

/* Padding belongs to the card, not to the plan. Given a background or an edge
   it becomes a card and takes the inset that goes with one; left plain it
   stands on the same left edge as every other element in the column. */
.ds-fx-pricing.is-card{ --ds-price-pad:max(20px, min(3vw, 34px), 1.7708vw); padding:var(--ds-price-pad) }
.ds-fx-pricing.is-card .ds-flag-right{ right:max(20px, min(3vw, 34px), 1.7708vw) }
.ds-fx-pricing.is-card .ds-flag-left{ left:max(20px, min(3vw, 34px), 1.7708vw) }

/* A plan fills its column only when it is what the column is for.
 *
 * Three plans in three columns are the same height and their buttons land on
 * one line, which is the whole point of a row of them. Stacked in with other
 * elements it takes the height it needs - filling there made one card as tall
 * as everything beside it put together. */
.ds-fx-pricing:only-child{ height:100% }
.ds-align-center.ds-fx-pricing{ text-align:center; align-items:center }
.ds-align-right.ds-fx-pricing{ text-align:right; align-items:flex-end }

/* The highlight label sits on the card's top edge, at whichever end it is
   asked to. Centered it needs the transform on both axes, so the horizontal
   offset is written per position rather than shared. */
.ds-price-flag{
  position:absolute;
  top:0;
  margin:0;
  padding:4px 12px;
  border-radius:999px;
  background:var(--ds-accent);
  color:var(--ds-bg,#fff);
  font-size:.8em;
  font-weight:600;
}
.ds-flag-right{ right:0; transform:translateY(-50%) }
.ds-flag-left{ left:0; transform:translateY(-50%) }
.ds-flag-center{ left:50%; transform:translate(-50%,-50%) }

.ds-price-name{ margin:0 }

/* The figure, with whatever stands in front of and behind it.
 *
 * A row of inline pieces, so a mark beside the price can line up against a
 * number several times its height. Where a mark is asked to sit above or
 * below instead, it is pushed onto its own line by taking the full width. */
.ds-price-fig{
  display:flex;
  align-items:baseline;
  flex-wrap:wrap;
  column-gap:8px;
  margin:0;
}
.ds-align-center.ds-fx-pricing .ds-price-fig{ justify-content:center }
.ds-align-right.ds-fx-pricing .ds-price-fig{ justify-content:flex-end }

/* The default is only the default. A price asked to be H2 takes the theme's
   H2, so the clamp below has to stand aside rather than out-weigh it. */
.ds-price-num{ line-height:1 }
.ds-price-num:not([class*="ds-as-"]){ font-size:max(2rem, min(5vw, 3.4rem), 2.8333vw); font-weight:700 }
.ds-price-prefix,
.ds-price-suffix{ opacity:.7; line-height:1 }

/* Where a mark sits against the figure. A flex item aligns itself, so each
   mark answers for its own position rather than the row answering for both. */
.ds-price-v-top{ align-self:flex-start }
.ds-price-v-middle{ align-self:center }
.ds-price-v-bottom{ align-self:flex-end }

/* On its own line above or below. Order puts it there whichever side of the
   figure it was written on, so a suffix asked to sit above does not have to
   be typed into the prefix field to get there. */
.ds-price-at-top,
.ds-price-at-bottom{ flex:0 0 100%; align-self:auto }
.ds-price-at-top{ order:-1 }
.ds-price-at-bottom{ order:1 }
.ds-align-center.ds-fx-pricing .ds-price-at-top,
.ds-align-center.ds-fx-pricing .ds-price-at-bottom{ text-align:center }
.ds-align-right.ds-fx-pricing .ds-price-at-top,
.ds-align-right.ds-fx-pricing .ds-price-at-bottom{ text-align:right }

.ds-price-note{ margin:0; opacity:.75 }

/* Full width of the card whatever the card's alignment, so banded rows are
   bands across it rather than a stripe the width of the longest feature. */
.ds-price-list{ list-style:none; margin:0; padding:0; display:grid; align-self:stretch; width:100% }
.ds-price-list li{ position:relative; padding-left:1.5em }
.ds-align-center.ds-fx-pricing .ds-price-list li{ padding-left:0 }
.ds-price-list li::before{
  content:"";
  position:absolute;
  left:.25em;
  top:.5em;
  width:.5em;
  height:.5em;
  border-radius:50%;
  background:var(--ds-price-mark, var(--ds-accent));
}
.ds-align-center.ds-fx-pricing .ds-price-list li::before{ display:none }

/* Plain rows are spaced apart; banded rows are not, because a gap between
   two bands is a stripe of the page showing through the middle of them. */
.ds-price-list:not(.is-striped){ gap:8px }
/* Bands run the full width of the card, through its padding and out to its
   edges - inside the padding they read as a stripe the width of the text
   rather than as a band across the plan. The text keeps the inset the rest
   of the card stands on, so only the color reaches the edge. */
.ds-fx-pricing.is-card .ds-price-list.is-striped{
  width:calc(100% + (var(--ds-price-pad, 0px) * 2));
  margin-inline:calc(var(--ds-price-pad, 0px) * -1);
}
.ds-price-list.is-striped li{
  padding:.5em .75em .5em 1.75em;
  background:var(--ds-price-row, transparent);
}
.ds-fx-pricing.is-card .ds-price-list.is-striped li{
  padding-left:calc(var(--ds-price-pad, 0px) + 1.25em);
  padding-right:var(--ds-price-pad, 0px);
}
.ds-fx-pricing.is-card .ds-price-list.is-striped li::before{
  left:calc(var(--ds-price-pad, 0px) + .25em);
}
.ds-price-list.is-striped li:nth-child(even){
  background:var(--ds-price-row2, color-mix(in srgb, currentColor 7%, transparent));
}
.ds-price-list.is-striped li::before{ left:.75em; top:1em }
.ds-align-center.ds-fx-pricing .ds-price-list.is-striped li{ padding-left:.75em }
.ds-align-center.ds-fx-pricing.is-card .ds-price-list.is-striped li{ padding-left:var(--ds-price-pad, 0px) }

/* Pushed to the bottom of whatever height the card is given, so a row of
   plans with different numbers of features has its buttons on one line. */
.ds-price-act{ margin-top:auto; display:flex }
.ds-align-center.ds-fx-pricing .ds-price-act{ justify-content:center }
.ds-align-right.ds-fx-pricing .ds-price-act{ justify-content:flex-end }

/* ------------------------------------------------------------------
   Team member
   ------------------------------------------------------------------ */
.ds-fx-person{ display:flex; flex-direction:column; gap:12px }
.ds-align-center.ds-fx-person{ text-align:center; align-items:center }
.ds-align-right.ds-fx-person{ text-align:right; align-items:flex-end }

.ds-person-shot{ width:100%; overflow:hidden; line-height:0; position:relative }
.ds-person-shot img{ width:100%; height:100%; object-fit:cover; display:block;
  transition:filter .45s ease }

/* The photo moves inside its own frame, which is what the frame's clipping is
   for - the picture grows and the space it occupies on the page does not. */
.ds-person-over,
.ds-person-under{ position:absolute; inset:0; line-height:0 }
.ds-person-over{ position:relative; z-index:1 }
.ds-person-over img,
.ds-person-under img{ width:100%; height:100%; object-fit:cover; display:block }

.ds-person-over{ transition:transform 1.6s cubic-bezier(.2,.7,.3,1), opacity .45s ease }
.ds-shot-fast .ds-person-over{ transition-duration:.45s, .45s }

.ds-shot-slow:hover .ds-person-over,
.ds-shot-fast:hover .ds-person-over{ transform:scale(1.08) }

/* Out is the other way round: it starts in and settles back, which suits a
   portrait that is already tight on the face. */
.ds-shot-out .ds-person-over{ transform:scale(1.08) }
.ds-shot-out:hover .ds-person-over{ transform:scale(1) }

/* Swap. The second photo is already on the page underneath, so the first
   simply stops covering it. */
.ds-shot-swap:hover .ds-person-over{ opacity:0 }

/* Grayscale, always or until pointed at. */
.ds-shot-gray-yes img{ filter:grayscale(1) }
.ds-shot-gray-hover img{ filter:grayscale(1) }
.ds-shot-gray-hover:hover img{ filter:grayscale(0) }
/* And the same while the pointer is on it, said as a class rather than as a
   state the browser is tracking.
 *
 * :hover is re-evaluated when the pointer moves over the page. This theme scrolls
 * with a transform, so the page moves and the pointer does not - the browser
 * never reconsiders what is underneath it, the picture slides out of its own
 * hover state, and the colour fades back to grey with the cursor still sitting
 * on it. The class is put on by measuring where the pointer actually is, so it
 * holds through a scroll. */
.ds-shot-gray-hover.is-pointed img{ filter:grayscale(0) }

/* No hover on a touch screen, so nothing that only happens on hover is the
   only way to see something. The swap's second photo is shown instead. */
@media (hover:none){
  .ds-shot-swap .ds-person-over{ opacity:1 }
  .ds-shot-gray-hover img{ filter:grayscale(0) }
}

@media (prefers-reduced-motion:reduce){
  .ds-person-over{ transition:opacity .45s ease }
  .ds-shot-slow:hover .ds-person-over,
  .ds-shot-fast:hover .ds-person-over,
  .ds-shot-out .ds-person-over{ transform:none }
}
.ds-person-square .ds-person-shot{ aspect-ratio:1 }
.ds-person-round .ds-person-shot{ aspect-ratio:1; border-radius:50%; max-width:280px }
.ds-person-tall .ds-person-shot{ aspect-ratio:3/4 }
.ds-person-wide .ds-person-shot{ aspect-ratio:4/3 }

.ds-person-name{ margin:0 }
.ds-person-role{ margin:0; color:var(--ds-accent) }
.ds-person-bio{ opacity:.85 }
.ds-person-bio > :last-child{ margin-bottom:0 }
.ds-person-links{ display:flex; gap:10px; list-style:none; margin:0; padding:0 }

/* ------------------------------------------------------------------
   Timeline
   ------------------------------------------------------------------
   A rule down the entries with a marker on each. The rule is drawn on
   the list rather than per row, so it runs unbroken between markers
   whatever height a row turns out to be.
   ------------------------------------------------------------------ */
.ds-time-list{ list-style:none; margin:0; padding:0; position:relative }
.ds-time-row{ position:relative; padding:0 0 max(20px, min(3vw, 34px), 1.7708vw) 34px }
.ds-time-row:last-child{ padding-bottom:0 }

/* The rule is each row's own left edge, so it stops with the last marker
   instead of running past it into empty space. */
/* Each segment runs from its own marker to the next one, overlapping the gap
   between rows so the line is unbroken - and the last row draws none, so the
   line stops at the last marker rather than running on into the space under
   it. */
.ds-time-row::before{
  content:"";
  position:absolute;
  left:5px;
  /* Half the date's line box - the point the marker is centered on. */
  top:.68em;
  bottom:calc(-.68em - 1px);
  width:2px;
  background:color-mix(in srgb, currentColor 18%, transparent);
}
.ds-time-row:last-child::before{ display:none }

/* Centered on the line it hangs off, and reaching back out to the rule.
   34px is the row's own indent, so the dot lands on the rule whatever the
   line above it is doing. */
.ds-time-when,
.ds-time-title{ position:relative }

.ds-time-dot{
  position:absolute;
  left:-34px;
  top:50%;
  transform:translateY(-50%);
  width:12px;
  height:12px;
  border-radius:50%;
  background:var(--ds-time-ink, var(--ds-accent));
}

/* An explicit line height so the rule below can start exactly where the
   marker sits. Inherited, the line box is whatever the theme's body copy
   uses and the two drift apart on any site that sets it differently. */
.ds-time-when{ margin:0; font-size:.85em; line-height:1.6; opacity:.7 }
.ds-time-title{ margin:.1em 0 .3em }
.ds-time-text{ margin:0; opacity:.85 }

/* Alternating: the rule runs up the middle and the entries take turns.
   One column on a narrow screen, because two columns of half a sentence
   each is not a timeline. */


/* Armed for the reveal. Readable with no script, as everything else is. */
.ds-fx-timeline[data-anim="yes"].is-armed .ds-time-row{ opacity:0 }

@media (prefers-reduced-motion:reduce){
  .ds-fx-timeline[data-anim="yes"].is-armed .ds-time-row{ opacity:1 }
}

/* A resting slider is taken hold of, so it says so. The drifting one is a band
   going past and is not asking to be touched. */
.ds-fx-slider.is-resting{ cursor:grab }
.ds-fx-slider.is-resting:active{ cursor:grabbing }

/* The two arrows.
 *
 * Inside the element and over the cards, because the slider is the thing they
 * belong to and a row of controls underneath would be a second thing to lay
 * out on every page that carries one. */
.ds-fx-slider{ position:relative }
.ds-slider-arrow{
  position:absolute; top:50%; transform:translateY(-50%); z-index:3;
  width:40px; height:40px; padding:0; border:0; border-radius:999px;
  background:var(--ds-bg,#fff); color:var(--ds-text,#111);
  box-shadow:0 1px 6px rgba(0,0,0,.18); cursor:pointer;
  display:grid; place-items:center;
}
.ds-slider-prev{ left:10px }
.ds-slider-next{ right:10px }

/* Both arrows under the cards instead of over them.
 *
 * Still absolute, so the pair is centered on the element rather than on
 * whatever the last card left behind, and the element is given the height to
 * put them in - it has none of its own, being as tall as the cards make it. */
.ds-fx-slider.arrows-bottom .ds-slider-arrow{ top:auto; bottom:9px; transform:none }
.ds-fx-slider.arrows-bottom .ds-slider-prev{ left:50%; margin-left:-46px }
.ds-fx-slider.arrows-bottom .ds-slider-next{ left:50%; right:auto; margin-left:6px }
.ds-slider-arrow::before{
  content:''; width:9px; height:9px;
  border-right:2px solid currentColor; border-bottom:2px solid currentColor;
}
.ds-slider-prev::before{ transform:rotate(135deg); margin-left:3px }
.ds-slider-next::before{ transform:rotate(-45deg); margin-right:3px }
.ds-slider-arrow:hover{ box-shadow:0 2px 10px rgba(0,0,0,.26) }
.ds-slider-arrow:focus-visible{ outline:2px solid currentColor; outline-offset:2px }

/* The growing card needs somewhere to grow into and something to grow from -
   the middle of its own edge, so it opens both ways rather than pushing right.
 *
 * The room above and below is padding, and the figure comes from the script,
 * which is the only thing that knows how tall the cards came out. Without it
 * the middle card was cut off along its top edge on every slider, whatever the
 * count was set to: the element hides its overflow because the track is twice
 * the width of the window, and hiding it sideways hides it upwards too. */
.ds-fx-slider.has-centre{ padding-block:var(--ds-centre-pad,0px) }
.ds-fx-slider.has-centre .ds-slider-track{ align-items:center }

/* The strip along the bottom the arrows stand in.
 *
 * After the growth padding above and not beside it: both rules name two
 * classes, so they carry the same weight and the last one written is the one
 * that applies. Declared first, the arrows were handed a bottom edge that a
 * centered slider then took back, and the pair sat in the growing card's room
 * rather than under it. The growth is added on rather than replaced, because a
 * slider can be doing both. */
.ds-fx-slider.arrows-bottom{ padding-bottom:calc(var(--ds-centre-pad,0px) + 58px) }
/* One card is larger and the rest are their own size, so the change is a swap
   from one card to the next rather than a size being recomputed every frame.
   It is eased because a swap with nothing between the two sizes is a jump. */
.ds-fx-slider.has-centre .ds-slider-track > *{
  transform-origin:center center;
  transition:transform .35s ease;
  will-change:transform;
}

@media (prefers-reduced-motion: reduce){
  .ds-fx-slider.has-centre .ds-slider-track > *{ transform:none !important }
}

/* How many cards are on screen at once.
 *
 * The count is the setting and the width follows from it, because how many fit
 * is the decision somebody is making - a width has to be recalculated by hand
 * every time the layout around the slider changes.
 *
 * The slider is the container the cards measure against. The track is
 * width:max-content, so a percentage on a card would be a share of the whole
 * strip rather than of what can be seen; cqw is a share of the slider, which is
 * the visible window and the thing the count is about.
 *
 * Only applied where a count was given. Without one the cards keep the clamp
 * they have always had, so nothing already built moves. */
.ds-fx-slider.ds-slider-fits{
  --ds-slide-gap: max(22px, min(2.4vw, 40px), 2.0833vw);
  --per: var(--ds-per-d, 4);
}

/* The window the cards are seen through.
 *
 * Held in by one gap at each end and clipping at that line, so the strip
 * appears with a gap before the first card and after the last rather than
 * running out to the edges with whichever cards land there cut in half.
 *
 * The clipping is here and not on the element, because the arrows are
 * positioned against the element and clipping that would cut them off too.
 *
 * It is also the container the cards measure themselves against - the count is
 * about how many fit in what can be seen, and what can be seen is this, not the
 * element it sits in. */
.ds-fx-slider.ds-slider-fits .ds-slider-view{
  container-type: inline-size;
  margin-inline: var(--ds-slide-gap);
}

/* Clipped sideways only.
 *
 * `overflow:hidden` hides both axes, and the middle card of a Large center
 * slider grows past the top and bottom of this box - the room the script
 * sets aside for that growth is on the element outside, so clipping here cut
 * the big card off at exactly the line the element had made space for.
 * `clip` is the one value that can be set on a single axis. */
.ds-fx-slider .ds-slider-view{ overflow-x: clip; overflow-y: visible }

/* On a row that runs the full width of the screen the cut is the edge of the
 * screen. The inset still holds the count and the gap - the cards measure
 * against the view - but a card passing out of it is seen all the way to the
 * edge rather than vanishing one gap short of it. The element clips instead,
 * and its edge is the screen's. */
.ds-row-bleed .ds-fx-slider .ds-slider-view{ overflow-x: visible }

/* Tablet and phone answers, on the viewport rather than the container: the
   element is its own container and cannot query itself. */

@media (max-width: 560px){
  .ds-fx-slider.ds-slider-fits{ --per: var(--ds-per-m, var(--ds-per-t, 1)) }
}

.ds-fx-slider.ds-slider-fits .ds-slider-track > .ds-slide{
  width: calc((100cqw - (var(--per) - 1) * var(--ds-slide-gap)) / var(--per));
}

/* Sound on a film that plays by itself.
 *
 * The whole picture is the switch, so it takes the pointer that says so. The
 * mark sits in the middle and comes up under the pointer: printed there
 * permanently it stops being a film and becomes a film with a badge on it. */
.ds-video-tap{ position:relative; cursor:pointer }

.ds-video-sound{
  /* The one control on a film, sized the way everything else is.
   *
   * It was 64px with a 26px mark in it and both stayed put on a wide screen,
   * so on 4K it was a small dark disc on a very large picture. The mark is a
   * share of the button rather than a second figure to keep in step. */
  --ds-vs: max(4rem, 3.3333vw);
  position:absolute; z-index:2; top:50%; left:50%;
  display:grid; place-items:center;
  width:var(--ds-vs); height:var(--ds-vs); padding:0;
  margin:calc(var(--ds-vs) / -2) 0 0 calc(var(--ds-vs) / -2);
  color:#fff; cursor:pointer;
  background:rgba(0,0,0,.45);
  border:1px solid rgba(255,255,255,.3); border-radius:50%;
  backdrop-filter:blur(8px);
  opacity:0; transform:scale(.9);
  transition:opacity .22s ease, transform .22s ease, background .22s ease;
}

.ds-video-sound svg{ width:40.625%; height:40.625% }

/* Under the pointer, or wherever the keyboard has got to. */
.ds-video-tap:hover .ds-video-sound,
.ds-video-tap:focus-within .ds-video-sound{ opacity:1; transform:scale(1) }

.ds-video-sound:hover{ background:rgba(0,0,0,.68) }
.ds-video-sound:focus-visible{ opacity:1; transform:scale(1); outline:2px solid #fff; outline-offset:3px }

/* A touch screen has no hover, so it is simply there until the sound is on. */
@media (hover: none){
  .ds-video-tap:not(.is-loud) .ds-video-sound{ opacity:1; transform:scale(1) }
}

@media (prefers-reduced-motion:reduce){
  .ds-video-sound{ transition:none }
}

/* ---- Google reviews ----
 *
 * A row of cards that becomes a column when there is no room for a row. Each
 * card is somebody's words, their name and their score, and the photo sits
 * wherever the element was told to put it - beside the words on either side,
 * above them, or nowhere.
 *
 * Sized by the row rather than by a count: the element does not know whether
 * five came back or two, and a grid told to make five columns draws three
 * empty ones when Google filters two of them out. */
.ds-fx-reviews{
  --ds-review-gap: 20px;
  --ds-review-radius: 10px;
  --ds-review-star: #f5a623;
}
.ds-fx-reviews .ds-review-row{
  display: flex; flex-wrap: wrap; gap: var(--ds-review-gap);
  align-items: stretch;
}
.ds-fx-reviews .ds-review{
  flex: 1 1 240px; min-width: 0; margin: 0;
  display: flex; gap: 14px;
  padding: 18px;
  border-radius: var(--ds-review-radius);
  background: var(--ds-review-bg, rgba(127,127,127,.08));
  color: var(--ds-review-ink, inherit);
}
.ds-fx-reviews .ds-review-body{ min-width: 0; flex: 1 1 auto }

/* Where the photo goes. Row either way round, or a column with it on top -
   and hidden is the card with nothing to lay out beside. */
.ds-review-pic-left  .ds-review{ flex-direction: row }
.ds-review-pic-right .ds-review{ flex-direction: row-reverse }
.ds-review-pic-top   .ds-review{ flex-direction: column }
.ds-review-pic-none  .ds-review{ flex-direction: column }

.ds-fx-reviews .ds-review-face{
  flex: 0 0 auto; width: 48px; height: 48px;
  border-radius: 50%; object-fit: cover; display: block;
  /* The theme puts a bottom margin under every picture in content, and this is
     a picture in content - fifty pixels of it, under a forty eight pixel
     avatar, on top of the row's own gap. The distance between the face and the
     words is this card's business and nothing else's. */
  margin-block: 0;
}
/* Closer on top than beside: a face above the words is a heading for them, and
   the same fourteen that reads as a column beside them reads as a gap here. */
.ds-review-pic-top .ds-review{ gap: 10px }

/* The paragraph wpautop leaves behind.
 *
 * A card is a picture followed by a block, and the filter that runs over post
 * content wraps the join between them in a paragraph of its own. It has no
 * height and no words, but it has eighteen pixels of margin top and bottom, and
 * with the row's own gap either side that is fifty six pixels of nothing under
 * every avatar. */
.ds-fx-reviews p:empty{ display: none }
/* Centered with the words when the words are centered, so a photo on top is
   over the middle of what it belongs to rather than off to one side. */
.ds-align-center .ds-review-face{ margin-inline: auto }
.ds-align-right .ds-review-pic-top .ds-review-face{ margin-inline-start: auto }

.ds-fx-reviews .ds-review-words{
  margin: 0 0 10px; padding: 0; border: 0;
  font-size: .95rem; line-height: 1.55;
}
.ds-fx-reviews .ds-review-who{
  font-size: .85rem; font-weight: 600;
  display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline;
}
.ds-fx-reviews .ds-review-when{ font-weight: 400; opacity: .6 }

/* The row of stars, and the one figure above the row. */
.ds-fx-reviews .ds-review-stars{
  display: inline-flex; gap: 2px; margin-bottom: 8px;
}
.ds-fx-reviews .ds-review-star{
  width: 1em; height: 1em; display: block;
  fill: currentColor; opacity: .25;
}
.ds-fx-reviews .ds-review-star.is-on{ fill: var(--ds-review-star); opacity: 1 }

.ds-fx-reviews .ds-review-score{
  display: flex; align-items: center; gap: 10px;
  margin-bottom: var(--ds-review-gap);
}
.ds-fx-reviews .ds-review-score .ds-review-stars{ margin-bottom: 0 }
.ds-fx-reviews .ds-review-figure{ font-weight: 700; font-size: 1.1rem }
.ds-fx-reviews .ds-review-total{ opacity: .6; font-size: .85rem }

/* Alignment is the element's, and it governs the whole card - the words, the
   name and the stars, which is what somebody means by asking for centered. */
.ds-fx-reviews.ds-align-center{ text-align: center }
.ds-fx-reviews.ds-align-right{ text-align: right }
.ds-align-center .ds-review-stars,
.ds-align-center .ds-review-who,
.ds-align-center .ds-review-score{ justify-content: center }
.ds-align-right .ds-review-stars,
.ds-align-right .ds-review-who,
.ds-align-right .ds-review-score{ justify-content: flex-end }
/* The score line's own side.
 *
 * Said on the element itself rather than inherited from the cards, because a
 * heading over a row is not always set the way the row is - and these win over
 * the alignment rules above, which are what it falls back to when nobody has
 * chosen. */
.ds-fx-reviews .ds-review-score-left{ justify-content: flex-start }
.ds-fx-reviews .ds-review-score-center{ justify-content: center }
.ds-fx-reviews .ds-review-score-right{ justify-content: flex-end }

/* One under the other on a phone. Two cards squeezed side by side on a narrow
   screen are two columns of single words. */
@media (max-width: 640px){
  .ds-fx-reviews .ds-review{ flex: 1 1 100% }
}

/* The reason, where the reviews would have been.
 *
 * Only ever drawn for somebody who can edit the page - see reviews_why() - so
 * this is a note to a builder rather than anything a reader sees. Dashed,
 * because a dashed box is how every editor on earth says "this is not the
 * finished thing". */
.ds-review-why{
  padding: 16px 18px;
  border: 1px dashed currentColor;
  border-radius: var(--ds-review-radius, 10px);
  opacity: .65;
  font-size: .9rem;
}
.ds-review-why p{ margin: 0 }

/* Where to go and get the missing thing, under the reason it is missing. */
.ds-review-why .ds-review-go{ margin: 8px 0 0; display: flex; flex-wrap: wrap; gap: 14px }
.ds-review-why .ds-review-go a{ color: inherit }

/* A picture while the page is leaning.
 *
 * Its transition is what the lean is fighting - see hold() in scroll-tilt.js.
 * Added for as long as the lean is moving and taken away the moment it
 * settles, so a hover zoom still eases and a scroll still leans. */
.ds-tilting{ transition: none !important }
