/* ============================================================================
   services.css - page styles for /services/ only.

   The shared layer carries everything both pages use: .container, .card,
   .icon-tile, .eyebrow, .badge, .btn/.btn-primary/.btn-ghost/.btn-xarrow,
   .section, .section-head, .reveal, the FAQ accordion, the .tbx carousel and
   the .u-hero shell with its bars. None of that is restated here.

   What lives here is only what this page invented: the pricing card, the
   session beam and the booking modal.
   ========================================================================== */

/* Carousel frame ratio. The shared .tbx-car-frame img is 1100/626 for the home
   page's Tweaking Utility shots, which really are that shape. This page's Toolbox
   shots are 1092/693 (taller), so under the shared ratio object-fit:cover sliced
   the bottom row - Website / Discord / TikTok - off every slide. Match the frame
   to the real files so the whole window shows. Scoped to this page so the home
   carousel keeps its own ratio. */
.page-services .tbx-car-frame img {
  aspect-ratio: 1092 / 693;
  object-fit: contain;
  object-position: center;
}

/* --- Card palette -------------------------------------------------------
   One card component, two palettes. The page sells two categories of work and
   the colour is what tells them apart, so the card reads its accent from a
   variable instead of existing twice.
   Defaults live on :root, not on .s-plan, because the same values are used by
   the checkout panel and the reassurance chips. Scoped to the card they would
   be undefined out there, and an rgba() with an undefined variable is an
   invalid declaration that the browser drops silently. */
:root {
  --card-accent: 42, 91, 255;      /* royal: the rebuild packages */
  --card-bright: 120, 160, 255;
  --card-deep: 30, 58, 174;
  --card-dots: 170, 200, 255;
  --card-tick: #9dbcff;
}
/* Violet: overclocking. Same saturation family as the royal, clearly a
   different category, so the two sections read as one system rather than two
   unrelated palettes. */
:is(.s-plan, .s-modal).is-oc {
  --card-accent: 147, 68, 255;
  --card-bright: 186, 138, 255;
  --card-deep: 88, 22, 196;
  --card-dots: 216, 188, 255;
  --card-tick: #c9a2ff;
}

/* --- Grid. The shared layer has no generic one. -------------------------- */
.s-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.s-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.s-grid-wipe { max-width: 1000px; margin-inline: auto; grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Stacked, but capped. A pricing card stretched to the full 940px of a tablet
   stops reading as a card and starts reading as a banner, and its 7 word feature
   rows end up on lines twice as long as they are anywhere else. Cap the column
   and centre it so the shape survives every width. */
@media (max-width: 980px) {
  .s-grid-3, .s-grid-wipe {
    grid-template-columns: minmax(0, 460px);
    justify-content: center;
  }
}
/* Three across, but only just: at 981px each card is about 290px and the 24px
   side padding is eating a quarter of the text width. Give it back until there
   is room to spare. */
@media (min-width: 981px) and (max-width: 1100px) {
  .s-plan-body, .s-plan-band { padding-left: 18px; padding-right: 18px; }
  .s-plan-band h3 { font-size: 1.35rem; }
}

/* --- Hero tail ---------------------------------------------------------- */
/* The hero shell itself is shared. Only the button row below it is local. */
.s-hero-cta { margin-top: 30px; display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; }

/* --- Hero badge: a session lamp, not a chip ----------------------------- */
/* The shared pill was built around an avatar cluster, and its 8px left padding
   is the socket that cluster sat in. This page has no faces to put there and
   will not invent any, so the socket takes the thing the service actually is:
   a live connection. Every remote desktop client shows session state as a small
   lamp, so the badge borrows the vernacular of its own subject and says "someone
   is on the other end" before the headline says anything.
   Deliberately still. The hero already animates its bars; a second pulsing
   element would compete with them and read as a notification. */
.page-services .u-hero-pill {
  gap: 11px;
  padding: 7px 17px 7px 13px;   /* symmetric now that the socket is filled */
}
.pill-lamp {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  /* nudged down a pixel: centred on the flex line the dot read a hair high
     against the x-height of the text beside it */
  position: relative;
  top: 1px;
  border-radius: 999px;
  background: linear-gradient(180deg, #a8c4ff, var(--accent));
  /* first shadow is the socket ring, second is the light it throws */
  box-shadow:
    0 0 0 3px rgba(42, 91, 255, 0.15),
    0 0 11px -1px rgba(42, 91, 255, 0.85);
  /* Leaves with the line it belongs to. The fade is deliberately shorter than
     the travel and shorter than the text's 0.42s: the text is clipped by the
     19px swap window, so it is out of sight about halfway through its own
     transition, while the dot has nothing to hide behind and would linger on
     screen after the words had gone. Matching the numbers made them look
     mismatched; matching the moment they vanish is what actually reads as one
     gesture. Only opacity and transform move, so the 8px box stays in flow and
     the pill cannot resize under the cursor. */
  transition:
    transform 0.42s var(--ease-out),
    opacity 0.2s var(--ease-out);
}
.u-hero-pill:hover .pill-lamp,
.u-hero-pill:focus-visible .pill-lamp {
  transform: translateY(-10px);
  opacity: 0;
}
.page-services .u-hero-pill { text-decoration: none; }
/* It is a link now, and 35px is under the height a finger reliably hits. Only
   on touch: on a mouse the smaller pill is the better proportion. */
@media (pointer: coarse) {
  .page-services .u-hero-pill { min-height: 42px; }
}
.page-services .u-hero-pill strong {
  color: var(--text-hi);
  font-weight: 600;
  letter-spacing: -0.005em;
}

/* --- the hover swap -----------------------------------------------------
   One line leaves upward as the other arrives from below, inside a window the
   height of a single line. The resting line stays in normal flow so IT sets the
   pill's width; the second is absolute, so a shorter or longer alternative can
   never make the badge resize under the cursor. */
.pill-swap {
  position: relative;
  display: inline-flex;
  align-items: center;
  overflow: hidden;
  height: 1.45em;
}
.pill-line {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  white-space: nowrap;
  color: var(--text-mid);
  transition:
    transform 0.42s var(--ease-out),
    opacity 0.42s var(--ease-out);
}
.pill-line-alt {
  position: absolute;
  inset: 0;
  justify-content: center;
  transform: translateY(100%);
  opacity: 0;
}
.u-hero-pill:hover .pill-line-rest,
.u-hero-pill:focus-visible .pill-line-rest { transform: translateY(-100%); opacity: 0; }
.u-hero-pill:hover .pill-line-alt,
.u-hero-pill:focus-visible .pill-line-alt { transform: translateY(0); opacity: 1; }

/* The claim and its qualifier are two different things, so a rule separates
   them instead of a second colour doing the work. */
.pill-sep {
  width: 1px;
  height: 13px;
  flex-shrink: 0;
  background: var(--border-strong);
  /* a pixel down, same as the lamp: centred on the flex line the rule sat a
     hair above the optical middle of the lowercase text either side of it */
  position: relative;
  top: 1px;
}

/* The reference's tilt, kept, but painted by a mask rather than an inline <svg>.
   The halo came from the SVG rasteriser: a stroke-width of 2 in a 24 viewBox
   shown at 13px is a 1.08px line, and rotating that puts it between pixels, so
   it comes back doubled and soft, which reads as an outline. A mask is a single
   alpha layer with no stroke to smear, and the wider stroke below lands the
   glyph on more than one pixel at every angle. */
.pill-arrow {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: 0.55;
  background-color: currentColor;
  color: var(--text-mid);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7'/%3E%3Cpath d='M8 7h9v9'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7'/%3E%3Cpath d='M8 7h9v9'/%3E%3C/svg%3E") center / contain no-repeat;
  transition:
    transform 0.3s var(--ease-out),
    opacity 0.3s var(--ease-out),
    color 0.3s var(--ease-out);
}
.u-hero-pill:hover .pill-arrow,
.u-hero-pill:focus-visible .pill-arrow {
  opacity: 1;
  color: #cfe0ff;
  transform: translateX(2px) rotate(-45deg);
}

.u-hero-pill:hover { border-color: var(--border-strong); }
.u-hero-pill:focus-visible { outline: 2px solid rgba(94, 140, 255, 0.85); outline-offset: 3px; }

/* Nothing slides for someone who asked for less motion: the two lines simply
   cross-fade in place. */
@media (prefers-reduced-motion: reduce) {
  .pill-line,
  .pill-arrow,
  .pill-lamp { transition: opacity 0.2s linear; transform: none !important; }
}

/* Measured, not guessed: with the rule the pill is 319px and needs a 355px
   window, so it survives a 375px phone and only gives up on the 320px ones,
   where the line would have to wrap. The swap needs one unbroken line, so on
   those the alternative is dropped rather than wrapped into the window. */
@media (max-width: 360px) {
  .pill-sep { display: none; }
  .pill-line-rest { white-space: normal; text-align: center; }
  .pill-swap { height: auto; overflow: visible; }
  .pill-line-alt, .pill-arrow { display: none; }
}

/* --- Lamp over the packages --------------------------------------------- */
/* Vanilla port of the Aceternity lamp (21st.dev), recoloured royal. A hot line
   with a cone opening downward, so the packages read as lit from above rather
   than decorated: the light lands exactly where the decision gets made.
   The original animated width with Framer Motion. Here the site's own .reveal
   observer flips .is-visible and CSS opens the fixture, so no second observer
   runs and the timing matches every other entrance on the page.
   Everything animates width only, never a filter or a blur radius, because the
   blurred layers would re-rasterise every frame if their radius moved. */
.lamp {
  /* Everything is placed against the filament, exactly as the reference does:
     the wings' conic origin, the two glows and the hard cut all measure from
     this one line. Move it and the whole fixture moves together. */
  --lamp-line: 58px;
  /* A share of the FIXTURE, not of the viewport. The wings are positioned from
     the centre line, so a width in vw could exceed half the container and get
     sliced by `overflow: hidden` - which is exactly what happened below ~1000px:
     at 393px each 306px wing overshot the 357px box by 128px, so the cone's
     outer falloff ended in a hard vertical edge on the page margin, and the
     filament stretched to 90% of the width and read as a rule across the page.
     At 44% the wing always stops short of the edge, so the cone fades out on its
     own terms at every screen size and no breakpoint has to chase it. */
  --lamp-open: 44%;                       /* wing + filament width, open */
  --lamp-shut: calc(var(--lamp-open) * 0.5);
  --lamp-light: 42, 91, 255;

  position: relative;
  height: 220px;
  /* the heading is pulled up into the pool of light rather than sitting under it */
  margin-bottom: clamp(-118px, -11vw, -76px);
  overflow: hidden;
  pointer-events: none;
  isolation: isolate;
  /* Two jobs in one mask. The hard step at the filament is what the reference
     achieves by painting an opaque block over everything above the line; doing
     it by mask instead means nothing on this page has to be painted over, which
     matters because the background here is not flat - it carries the ambient
     gradients and the starfield. The tail then dissolves the fixture into the
     section instead of ending it on an edge. */
  -webkit-mask-image: linear-gradient(to bottom,
    transparent calc(var(--lamp-line) - 1px), #000 var(--lamp-line), #000 58%, transparent 100%);
  mask-image: linear-gradient(to bottom,
    transparent calc(var(--lamp-line) - 1px), #000 var(--lamp-line), #000 58%, transparent 100%);
}
.lamp > * {
  position: absolute;
  transition: width 0.9s var(--ease-out);
}

/* --- the two conic wings ------------------------------------------------
   The shape is a conic gradient whose origin sits ON the filament, not a
   triangle: the falloff across the cone is what makes it read as light rather
   than as a filled shape, and a clip-path cannot do that. */
.lamp-wing {
  top: var(--lamp-line);
  height: 224px;
  width: var(--lamp-shut);
}
.lamp.is-visible .lamp-wing { width: var(--lamp-open); }

.lamp-wing-l {
  right: 50%;
  background-image: conic-gradient(from 70deg at center top,
    rgba(var(--lamp-light), 0.75), transparent 50%, transparent);
  /* The reference trims each wing with two background-coloured overlays, one
     fading up from the bottom and one fading in from the outer edge. Same two
     fades, composited as masks so nothing is painted over the page. */
  -webkit-mask-image:
    linear-gradient(to top, transparent, #000 62%),
    linear-gradient(to right, transparent, #000 48%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to top, transparent, #000 62%),
    linear-gradient(to right, transparent, #000 48%);
  mask-composite: intersect;
}
.lamp-wing-r {
  left: 50%;
  background-image: conic-gradient(from 290deg at center top,
    transparent, transparent 50%, rgba(var(--lamp-light), 0.75));
  -webkit-mask-image:
    linear-gradient(to top, transparent, #000 62%),
    linear-gradient(to left, transparent, #000 48%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to top, transparent, #000 62%),
    linear-gradient(to left, transparent, #000 48%);
  mask-composite: intersect;
}

/* --- the glows ---------------------------------------------------------
   Two of them at different offsets, as in the reference: a wide soft pool the
   heading sits in, and a tighter hotter one hugging the filament. Both reach
   above the line and are cut there by the container mask. */
.lamp-glow {
  top: calc(var(--lamp-line) - 26px);
  left: 50%;
  /* Same reason as the wings: a share of the fixture, so the blurred pool cannot
     grow wider than the box it has to fade out inside. */
  width: min(22rem, 66%);
  height: 118px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: rgba(var(--lamp-light), 0.44);
  filter: blur(58px);
}
.lamp-core {
  top: calc(var(--lamp-line) - 46px);
  left: 50%;
  width: calc(var(--lamp-shut) * 0.46);
  height: 118px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: rgba(110, 148, 255, 0.85);
  filter: blur(40px);
}
.lamp.is-visible .lamp-core { width: calc(var(--lamp-open) * 0.46); }

/* --- the filament: the only hard edge in the fixture -------------------- */
.lamp-bar {
  top: var(--lamp-line);
  left: 50%;
  width: var(--lamp-shut);
  height: 2px;
  transform: translateX(-50%);
  background: rgba(140, 175, 255, 0.95);
}
.lamp.is-visible .lamp-bar { width: var(--lamp-open); }

/* the heading has to sit above the light, not behind it */
.section-head.is-lit { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .lamp > * { transition: none; }
}

/* The fixture scales with its container now, but the blur radii and the height
   are absolute, so on a phone a 58px blur inside a 357px box spreads the pool
   over most of the width and the fixture stops reading as a lamp and starts
   reading as a wash. Shrink the fixture with the screen: shorter body, tighter
   blurs, filament nearer the top. */
@media (max-width: 560px) {
  .lamp {
    --lamp-line: 44px;
    height: 176px;
    margin-bottom: -80px;
  }
  .lamp-wing { height: 156px; }
  .lamp-glow { height: 88px; filter: blur(42px); }
  .lamp-core { height: 88px; filter: blur(28px); }
}

/* --- Pricing card ------------------------------------------------------- */
/* Order follows the reference exactly: name band, price paired with turnaround,
   one sentence, the booking button, then the feature list, then a quiet second
   button. The booking button sits ABOVE the list on purpose - someone who has
   already decided should not have to scroll a feature list to act. */
.s-plans { padding-top: 22px; }

.s-plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 0;                 /* the band has to reach the card edges */
  overflow: hidden;
  border-radius: var(--r-panel);
}
/* The lift is a margin, not a transform: .reveal animates transform, and a
   static transform on the same element is overwritten the moment it scrolls in. */
/* Lifted, and the same size as its neighbours. A stretched grid item resolves
   its height as the row minus its margins, so a lone negative margin-top makes
   the card TALLER rather than higher; the matching positive margin-bottom
   cancels that, leaving net margin zero, an identical rectangle, shifted up. */
.s-plan.is-featured {
  margin-top: -22px;
  margin-bottom: 22px;
  border-color: rgba(var(--card-bright), 0.34);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.15),
    0 28px 60px -30px rgba(0, 0, 0, 0.7),
    0 0 60px -34px rgba(var(--card-accent), 0.5);
}
/* Stacked there is no row to stand out from, and a lone offset card just looks
   misaligned. */
@media (max-width: 980px) {
  .s-plan.is-featured { margin-top: 0; margin-bottom: 0; }
}

/* The shared calibration straddles the border by pulling the beam 1.5px outward.
   This card clips its own overflow so the name band can reach the edges, so a
   beam pulled outward is simply cut off. It rides just inside the line instead,
   and the radius follows this card's panel radius rather than the plan cards'. */
.s-plan .border-beam {
  inset: 0;
  border-radius: var(--r-panel);
  padding: 2px;
}

/* Name band, after the reference. Not a wash across the top of the card but an
   inset panel floating inside it: rounded, hairline-bordered, lit from a single
   bloom at top-centre - directly under where the flag hangs - with a halftone
   dot grid that is densest at that bloom and thins outward. The dark strip of
   card around it is the point; the band reads as a lit plate set into the card,
   the way the reference does, rather than a coloured header stuck on the edge.
   The 12px inset lines its text up with the body's 24px (12 margin + 12 pad). */
.s-plan-band {
  position: relative;
  padding: 24px 24px 26px;
  overflow: hidden;
  /* Flush to the top and sides - the card clips its own rounded corners, so the
     band fills them. Only the bottom is drawn: a hairline and a soft round that
     seat the lit plate into the card above the price. */
  border-bottom: 1px solid rgba(var(--card-bright), 0.14);
  border-radius: 0 0 16px 16px;
  background:
    /* focused glow directly under the light line - tight, so it reads as the
       line's own bleed rather than a second wash */
    radial-gradient(46% 40px at 50% 0%, rgba(var(--card-accent), 0.5), transparent 72%),
    /* the broad bloom, dimmed right down: it was the brightest thing on the card
       and drowned the halftone. Now it only tints the plate. */
    radial-gradient(80% 132% at 50% -14%, rgba(var(--card-accent), 0.16), transparent 68%),
    linear-gradient(180deg, rgba(20, 30, 66, 0.46), rgba(10, 14, 30, 0.14));
  box-shadow:
    inset 0 1px 0 rgba(var(--card-bright), 0.14),   /* top rim light on the card edge */
    inset 0 -20px 30px -26px rgba(0, 0, 0, 0.5);    /* floor shadow, so it sits IN the card */
}
/* The light line along the top rim: a white-hot core fading to the accent at
   the ends, with a soft glow bleeding down. This is the bright element now, in
   place of the broad bloom. White centre so the one rule serves both palettes -
   the colour comes from the glow, the way a lit tube looks. */
.s-plan-band::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 56%;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    transparent,
    rgba(var(--card-bright), 0.85) 20%,
    rgba(255, 255, 255, 0.95) 50%,
    rgba(var(--card-bright), 0.85) 80%,
    transparent);
  box-shadow: 0 0 12px 1px rgba(var(--card-accent), 0.7);
  pointer-events: none;
}
/* The halftone. A dot grid, masked by a radial from the same top-centre point
   as the bloom, so the dots crowd the light and fade to nothing at the sides -
   the screen-print texture of the reference rather than an even field of dots. */
.s-plan-band::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(var(--card-dots), 0.85) 1px, transparent 1.4px);
  background-size: 11px 11px;
  opacity: 0.3;
  -webkit-mask-image: radial-gradient(70% 128% at 50% -10%, #000 8%, transparent 72%);
          mask-image: radial-gradient(70% 128% at 50% -10%, #000 8%, transparent 72%);
  pointer-events: none;
}
.s-plan.is-featured .s-plan-band {
  border-bottom-color: rgba(var(--card-bright), 0.24);
  background:
    radial-gradient(48% 46px at 50% 0%, rgba(var(--card-accent), 0.6), transparent 72%),
    radial-gradient(82% 135% at 50% -14%, rgba(var(--card-accent), 0.22), transparent 68%),
    linear-gradient(180deg, rgba(22, 32, 72, 0.54), rgba(10, 14, 30, 0.14));
}
/* Brighter line on the featured card - it is the one carrying the flag, so the
   tube it hangs from should read as the strongest. */
.s-plan.is-featured .s-plan-band::after {
  width: 62%;
  box-shadow: 0 0 14px 1px rgba(var(--card-accent), 0.85);
}
.s-plan.is-featured .s-plan-band::before { opacity: 0.42; }
.s-plan-band h3 {
  position: relative;
  margin: 0;
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--text-hi);
}

/* Hangs off the top edge, the way the reference does it. */
.s-plan-flag {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  padding: 5px 14px 6px;
  border: 1px solid rgba(var(--card-bright), 0.32);
  border-top: 0;
  border-radius: 0 0 11px 11px;
  background: linear-gradient(180deg, rgba(var(--card-accent), 0.34), rgba(var(--card-deep), 0.26));
  box-shadow: 0 6px 18px -12px rgba(var(--card-accent), 0.8);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #dbe6ff;
}

.s-plan-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  /* 22px top, not 6: the price is the biggest thing on the card and was landing
     against the band's bottom hairline. It needs its own air below the band. */
  padding: 22px 24px 24px;
}

/* Price and turnaround share one row. For a service the buyer weighs cost and
   time-to-delivery together, so they are never separated. */
.s-plan-price {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.s-plan-cost { display: flex; align-items: baseline; gap: 6px; }
.s-plan-cost b {
  font-size: clamp(2.1rem, 4.6vw, 2.7rem);
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1;
  color: var(--text-hi);
  font-variant-numeric: tabular-nums;
}
.s-plan-cost span {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  color: var(--text-low);
}
.s-plan-when {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.86rem;
  color: var(--text-mid);
}
.s-plan-when svg { color: rgba(var(--card-bright), 0.72); flex-shrink: 0; }

.s-plan-note {
  margin: 16px 0 0;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--text-mid);
  min-height: 4.8em;          /* keeps the three buy buttons on one line */
}
@media (max-width: 980px) { .s-plan-note { min-height: 0; } }

.s-plan-go { width: 100%; margin-top: 22px; }

.s-plan-label {
  margin: 24px 0 13px;
  padding-top: 18px;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-mono);
  font-size: 0.72rem;     /* matches .eyebrow; 0.7rem fell under 11.5px */
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-low);
}

.s-plan-feat { margin: 0; padding: 0; list-style: none; display: grid; gap: 11px; }
.s-plan-feat li {
  display: grid;
  grid-template-columns: 17px 1fr;
  gap: 11px;
  align-items: start;
  font-size: 0.91rem;
  line-height: 1.45;
  color: var(--text-mid);
}
/* The tick is drawn by a mask rather than an inline <svg> per row: seven rows
   per card across three cards is 21 copies of the same path in the markup. */
.s-plan-feat li i {
  width: 17px;
  height: 17px;
  margin-top: 2px;
  background: linear-gradient(180deg, var(--card-tick), rgb(var(--card-accent)));
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 15px 15px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 15px 15px no-repeat;
}

/* The gap above the closing button belongs to the list, not to the button.
   Setting padding-top on a .btn adds it INSIDE the pill, so the label stops
   sitting on the vertical centre line: 22px above the text against .btn's own
   11px below it. Spacing between two elements is a margin on one of them.

   Which means it only applies when there IS a second element. On a card whose
   list is the last thing in the body (Elite, and every overclocking card) the
   same 26px lands on top of the body's own 24px padding and reads as the card
   forgetting to end. :has() states the actual condition, so a card that grows a
   button later gets the gap without anyone remembering to come back here. */
.s-plan-feat:has(+ .s-plan-more) { margin-bottom: 26px; }
/* margin-top:auto pins it to the bottom so the button lines up across cards
   with different numbers of features. */
.s-plan-more { margin-top: auto; align-self: stretch; }

/* Coming-soon tier: present enough to anchor the price, plainly not buyable.
   The band keeps the same lit inset as the others - the whole card is dimmed to
   0.62, so a second, flatter treatment on top would only read as a mistake. */
/* The dim, and the reveal system, were fighting over `opacity`.

   `.reveal-group .reveal-item { opacity: 0 }` in prospira.css parks a card until
   its group scrolls into view. That selector and `.s-plan.is-soon` weigh exactly
   the same (0,2,0), and this sheet is linked second - so the dim won, and this one
   card sat visible while the two beside it were still parked. Reaching the group
   then started the entrance animation from opacity 0, so the card vanished and
   faded back in: a flash, on the only card that does it.

   The parked state is restored below at a weight the dim cannot beat, and the
   resting value is handed to the animation instead of declared here, because the
   animation's `both` fill would overwrite a declaration anyway. */
.s-plan.is-soon { opacity: 0.62; }
.reveal-group .s-plan.is-soon { opacity: 0; }
.reveal-group.is-visible .s-plan.is-soon { --reveal-end: 0.62; }
.s-plan.is-soon .s-plan-band::before { opacity: 0.14; }
.s-plan.is-soon .s-plan-feat li i { background: rgba(180, 200, 245, 0.5); }
.s-plan-soon {
  display: block;
  width: 100%;
  margin-top: 20px;
  padding: 13px 20px;
  border-radius: var(--r-pill);
  border: 1px dashed var(--border-mid);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-low);
}


/* --- Category rail ------------------------------------------------------
   The page has one heading and two kinds of work under it, so the rail is what
   names each kind. That makes it structural, not decoration, and it has to be
   present at every width: hiding it on a phone would leave the overclock cards
   with nothing at all saying what they are.

   Two forms of the same object. Narrow: a tag with a hairline running off to
   the right, sitting above its grid. Wide: the same tag rotated up into the
   margin with the line running down beside the cards, where the line's length
   also shows how far the category runs. */
.s-cat { --rail: 120, 160, 255; --rail-ink: 190, 212, 255; }
.s-cat-oc { --rail: 186, 138, 255; --rail-ink: 222, 202, 255; }

/* The rail is anchored to the CARDS, not to the section: measured from the
   section it would start level with the heading and point at empty space. */
/* The rail is anchored to the CARDS, not to the section: measured from the
   section it would start level with the heading and point at empty space. */
.s-band { position: relative; }

/* Two catalogues under one heading, so the gap between them has to be clearly
   larger than anything inside them and still smaller than the gap to the next
   section. Without a rail the badge is not there to clear, so the spacing
   drops to what the content alone needs. */
/* --- "optional" on a form label ----------------------------------------
   Only two fields in the booking form are required. Without this the form reads
   as eight obligations and people abandon it; the marker costs nothing and
   removes that impression. Deliberately the quietest text in the dialog. */
.s-opt {
  margin-left: 7px;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-low);
  vertical-align: 1px;
}

/* A category break has to sit clearly BELOW a section break in the hierarchy.
   A flat 76px was within 8px of the effective gap between whole sections at
   phone width (84px), so two categories inside one section read as far apart as
   two unrelated sections. Fluid, and always the smaller of the two. */
.s-band + .s-band { margin-top: clamp(52px, 7vw, 84px); }
@media (min-width: 981px) {
  /* The badge and the top arm stand 68px above the band's own box, so that
     much is spent before any gap is visible at all. 118 leaves 50px of clear
     air above the first badge, which is the same gap the second one gets
     below the arm above it. */
  .s-band { margin-top: 118px; }
  .s-band + .s-band { margin-top: 152px; }
}

/* A bracket around the whole category: the arm runs right along the top, over
   the cards, and turns down the left of them. That is the difference between a
   mark and a container. Overclocking is the one block on this page you cannot
   buy, and the bracket is what holds it apart from the packages above without
   dimming it or fencing it in a box.

   Both catalogues carry one. They hold the same kind of thing - a row of
   service cards - so giving one a bracket and the other a centred heading
   under a spotlight would say they were different kinds of content when the
   only real difference is that one of them is not on sale yet. */
.s-rail { display: none; }

@media (min-width: 981px) {
  .s-rail {
    display: block;
    position: absolute;
    left: -14px;
    /* The top arm sets off to the right and stops. Run the full width of the
       grid and it stops being a mark in the margin and becomes a box drawn
       round the section, which is heavier than this content deserves: it is a
       catalogue you cannot buy yet, not the main offer. */
    width: 150px;
    top: -30px;
    bottom: -34px;          /* the down arm runs on past the last row */
    pointer-events: none;
  }
  /* Two borders of one box, not two elements: at a 1px hairline a butt join
     between separate strokes shows as a notch exactly at the corner, which is
     the one place the eye is following. */
  .s-rail-line {
    position: absolute;
    inset: 0;
    border-left: 1px solid rgba(var(--rail), 0.4);
    border-top: 1px solid rgba(var(--rail), 0.4);
    border-top-left-radius: 14px;
    /* Both open ends fade out. One gradient per axis, intersected: the top arm
       is at y=0 where the vertical gradient is still solid, the down arm is at
       x=0 where the horizontal one is, so each fades only at the end that is
       actually open. */
    -webkit-mask:
      linear-gradient(90deg, #000 44%, transparent),
      linear-gradient(180deg, #000 68%, transparent);
    mask:
      linear-gradient(90deg, #000 44%, transparent),
      linear-gradient(180deg, #000 68%, transparent);
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
  }
  /* Sits on the corner, in the margin, above the arm. Nothing else is out
     there, so it reads as the label of the bracket rather than a second
     heading competing with the real one below it. */
  .s-rail-tag {
    position: absolute;
    left: 0;
    bottom: 100%;
    margin-bottom: 11px;
    padding: 4px 11px 5px;
    border-radius: 7px;
    border: 1px solid rgba(var(--rail), 0.45);
    background: rgba(var(--rail), 0.1);
    font-family: var(--font-mono);
    font-size: 0.64rem;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: rgba(var(--rail-ink), 0.92);
    white-space: nowrap;
  }
}
/* Once there is a page margin to stand in, the bracket steps out of the
   content. Only far enough to clear the cards: pushed further the heading ends
   up sitting a long way inside the corner and the mark stops feeling attached
   to what it labels. */
@media (min-width: 1260px) {
  .s-rail { left: -26px; }
}

/* A category heading: it names the block and gets out of the way. The weight
   on this page is spent on the cards and their prices, which is what the
   reader came to compare. */
.s-cat-title {
  /* Tighter than the section head's own gap at every width, so the h3 reads as
     belonging to the grid under it rather than as a second section heading. At
     phone width the flat 30px happened to equal the section head's 30px, which
     flattened exactly that distinction. */
  margin: 0 0 clamp(16px, 2vw, 20px);   /* caps at one grid gutter (20px) */
  font-size: clamp(1.45rem, 2.4vw, 1.85rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--text-hi);
}
/* Below this the grid collapses to one 460px column and centres it, so a
   heading left-aligned to the container starts 148px away from the cards it
   labels. It stays left-aligned type; only its box follows the column. */
@media (max-width: 980px) {
  .s-cat-title { max-width: 460px; margin-inline: auto; }
}

/* --- Overclocking grid --------------------------------------------------- */
/* Same three columns as the packages above, so a card is a card at one size
   across the whole page. Five of them fall as three and two. */
.s-grid-oc { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 980px) {
  .s-grid-oc { grid-template-columns: minmax(0, 460px); justify-content: center; }
}


/* The saving reuses .s-plan-flag. It is the same object doing the same job in
   the same place — a word hanging off the top edge that says why this card is
   the one to read — and the flag already draws itself from --card-*, so it
   turns violet here with no override. A second near-identical rule would only
   be the first one with different decimals in the alphas. */

/* Coming soon, stated in words rather than by dimming. Elite is dimmed because
   it stands beside two cards you CAN buy and the contrast is the message; a
   whole section at 62% has nothing to contrast with and just looks broken. */
.s-plan.is-oc .s-plan-soon { border-color: rgba(var(--card-bright), 0.3); }

/* --- Full breakdown ------------------------------------------------------
   The same rows for every package, so the three can be read against each
   other. Rows a package does not include stay on the list at a weight that
   says so: drop them and a short list looks like a complete one, which is the
   one comparison this dialog exists to make honest. */
.s-bd-group + .s-bd-group { margin-top: 26px; }
/* The group headings are the booking dialog's field labels - same component, so
   they share that rule below rather than restating its values here. */
.s-bd-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.s-bd-list li {
  display: grid;
  grid-template-columns: 17px 1fr;
  gap: 11px;
  align-items: start;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--text-mid);
}
.s-bd-list li i { width: 17px; height: 17px; margin-top: 2px; }
.s-bd-list li.is-in i {
  background: linear-gradient(180deg, var(--card-tick), rgb(var(--card-accent)));
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 15px 15px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 15px 15px no-repeat;
}
/* A dash, not a cross. The row is absent from this package, not forbidden, and
   a cross reads as a warning about something that went wrong. */
.s-bd-list li.is-out { color: rgba(154, 168, 204, 0.4); }
.s-bd-list li.is-out i { position: relative; }
.s-bd-list li.is-out i::before {
  content: "";
  position: absolute;
  left: 2px;
  right: 2px;
  top: 8px;
  height: 1px;
  border-radius: 1px;
  background: currentColor;
}
/* Dimming says "not included" to the eye and nothing at all to a screen
   reader, so the state is written out for it too. */
.s-bd-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Small cards -------------------------------------------------------- */
.s-mini { display: flex; flex-direction: column; gap: 12px; padding: 20px; }
.s-mini h3 { margin: 0; font-size: 1rem; font-weight: 500; letter-spacing: -0.01em; color: var(--text-hi); }
.s-mini p { margin: 0; font-size: 0.9rem; line-height: 1.6; color: var(--text-mid); }
.s-mini p b { color: var(--text-hi); font-weight: 500; }

/* --- the format warning, inside step 02 --------------------------------- */
/* It used to be a section of its own restating what step 02 already said. Here
   it reads as part of the runbook: this is the minute it happens, so the
   checklist is an instruction rather than smallprint. */
.s-wipe {
  margin-top: 14px;
  padding: 14px 16px;
  border-radius: 14px;
  border: 1px solid rgba(245, 180, 95, 0.22);
  background: rgba(245, 165, 65, 0.05);
}
.s-wipe-lead {
  margin: 0 0 12px;
  font-size: 0.88rem;
  line-height: 1.6;
  color: #f0dcc0;
}
.s-wipe-lead b { color: #ffeacd; font-weight: 600; }
.s-wipe .s-need li { font-size: 0.88rem; color: rgba(240, 220, 192, 0.82); }
.s-wipe .s-need li svg { color: rgba(245, 190, 120, 0.7); }
.s-wipe .s-need li b { color: #ffeacd; }

/* --- Return from checkout ----------------------------------------------- */
/* Paid reads as a result, canceled reads as neutral: one border colour apart,
   because a closed checkout is not an error. */
#booked[hidden] { display: none; }
.s-booked { gap: 14px; }
.s-booked h3 { font-size: 1.14rem; }
[data-state="paid"] .s-booked {
  border-color: rgba(var(--card-bright), 0.34);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 0 60px -34px rgba(var(--card-accent), 0.5);
}

/* --- Requirement rows --------------------------------------------------- */
.s-need { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.s-need li {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 11px;
  align-items: start;
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--text-mid);
}
.s-need li svg { margin-top: 3px; color: rgba(150, 180, 255, 0.66); }
.s-need li b { color: var(--text-hi); font-weight: 500; }

/* --- Closing CTA -------------------------------------------------------- */
.s-close { margin-top: 44px; display: flex; flex-direction: column; align-items: center; gap: 16px; }
.s-close-alt { margin: 0; font-size: 0.92rem; color: var(--text-mid); text-align: center; }
/* The one plain-text link on the page. Without an underline nothing tells you
   which words are clickable, and a colour shift alone is not enough on a page
   where half the copy is already tinted. */
.s-close-alt a {
  color: #cfe0ff;
  text-decoration: underline;
  text-decoration-color: rgba(150, 180, 255, 0.5);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.s-close-alt a:hover {
  color: var(--text-hi);
  text-decoration-color: rgba(150, 180, 255, 0.9);
}

/* --- Booking modal ------------------------------------------------------ */
.s-modal[hidden] { display: none; }
.s-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: grid;
  place-items: center;
  /* inset:0 and vh both resolve against the viewport a phone would have with the
     address bar HIDDEN, so on a phone that still shows it the overlay - and the
     panel sized against it - reach below the visible area and the submit button
     gets cut in half. dvh tracks what is actually on screen. vh first as the
     fallback for engines without dvh. */
  height: 100vh;
  height: 100dvh;
  /* Belt and braces: if anything ever exceeds the box, it scrolls instead of
     being clipped. */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 20px;
  background: rgba(2, 3, 10, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: s-modal-in 0.22s var(--ease-out) both;
}
@keyframes s-modal-in { from { opacity: 0; } to { opacity: 1; } }

.s-modal-panel {
  position: relative;
  width: min(640px, 100%);   /* wider on desktop */
  max-height: min(86vh, 820px);
  max-height: min(86dvh, 820px);
  display: flex;
  flex-direction: column;
  border-radius: var(--r-panel);
  border: 1px solid var(--border-strong);
  /* Slightly translucent: the darkened, blurred page shows faintly through, so
     the panel reads as frosted glass over the site rather than a solid sheet. */
  background:
    radial-gradient(120% 90% at 10% 0%, rgba(42, 91, 255, 0.16), transparent 58%),
    linear-gradient(180deg, rgba(13, 18, 38, 0.82), rgba(8, 11, 24, 0.86));
  backdrop-filter: blur(22px) saturate(1.3);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  box-shadow: 0 40px 90px -40px rgba(0, 0, 0, 0.9);
  animation: s-panel-in 0.3s var(--ease-out) both;
}
@keyframes s-panel-in {
  from { opacity: 0; transform: translateY(14px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .s-modal, .s-modal-panel { animation: none; }
}

.s-modal-head {
  position: relative;      /* the centred title measures against this */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 20px 15px;
  border-bottom: 1px solid var(--border-soft);
}
.s-modal-head .badge { margin: 0; }
/* Plan badge, after the reference: an outlined chip that glows in the package's
   own accent - royal here, violet on an overclock breakdown - with the name in
   plain case rather than the mono caps of the base badge. The glow the pickers
   used to have lives here now, on the one thing that names the package. */
.s-modal-head .s-plan-badge {
  padding: 5px 14px 6px;
  border-radius: 10px;
  border: 1px solid rgba(var(--card-bright), 0.55);
  background: rgba(var(--card-accent), 0.14);
  box-shadow:
    0 0 20px -6px rgba(var(--card-accent), 0.6),
    inset 0 0 14px -8px rgba(var(--card-bright), 0.7);
  font-family: inherit;
  font-size: 0.94rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: #dbe7ff;
}
/* Dead centre of the panel, not centre of the leftover space. Out of flow, so
   the badge and the close button keep their space-between placement and a longer
   package name can never shove the title off centre. The max-width keeps it
   clear of both: the badge is about 76px, the X is 32px. */
.s-modal-title {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-width: calc(100% - 230px);
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-hi);
}
/* Below this the badge and the X leave no room for a centred line between them,
   and a truncated heading is worse than none - the badge already names the
   package and the fields say what to do. */
@media (max-width: 430px) {
  .s-modal-title { display: none; }
}
.s-modal-x {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: 9px;
  border: 1px solid var(--border-mid);
  background: rgba(150, 180, 255, 0.05);
  color: var(--text-mid);
  cursor: pointer;
}
.s-modal-x:hover { color: var(--text-hi); border-color: var(--border-strong); }

.s-modal-body { padding: 20px; overflow-y: auto; display: grid; gap: 20px; }
.s-modal-foot {
  padding: 15px 20px 18px;
  border-top: 1px solid var(--border-soft);
  display: grid;
  gap: 10px;
}
.s-modal-foot .btn { width: 100%; }
/* An explicit display beats the browser's own [hidden] { display: none }, so the
   footer needs this or hiding it leaves a bordered 80px strip. The shared .btn
   sets display too, so `bookBtn.hidden = true` alone never hid the button - it
   only looks hidden today because the whole footer goes with it. Stating it here
   means the attribute works on its own merit. */
.s-modal-foot[hidden] { display: none; }
.s-modal .btn[hidden] { display: none; }



.s-field > label,
.s-field-label,
.s-bd-title {
  display: block;
  margin: 0 0 10px;
  /* Same face as the body copy, not the mono caption - a small white heading
     rather than an eyebrow. Weight carries the hierarchy instead of case. */
  font-family: inherit;
  font-size: 0.96rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--text-hi);
}

/* Built from radios so it is keyboard and screen-reader correct for free.
   Stays a row down to very narrow screens: three options across a 375px phone
   leave about 105px each, and every row that stacks pushes the submit button
   further off the screen. */
.s-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px; }
@media (max-width: 340px) { .s-seg { grid-auto-flow: row; grid-auto-columns: auto; } }
.s-seg input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.s-seg label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.5), rgba(10, 14, 30, 0.56));
  font-size: 0.9rem;
  color: var(--text-mid);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.s-seg label:hover { border-color: var(--border-strong); color: var(--text-hi); }

/* Brand logo, keyed transparent so it sits straight on the pill - no white
   chip. Each is colour-corrected for a dark ground: Intel keeps its blue, AMD
   is drawn white because black would vanish, NVIDIA keeps its green eye. Sized
   by optical weight, not by pixels: a wordmark carries further than an icon, so
   the eye is set taller than the two wordmarks to hold the same presence. */
.s-seg .hw-mark {
  flex-shrink: 0;
  width: auto;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45));
  transition: transform var(--dur-1) var(--ease-out), opacity var(--dur-1) var(--ease-out);
}
/* Sized by LETTER height, not by box height. One shared height renders four
   visibly different sizes, which is why these differ.
   Measured off the files: as a share of its own box, AMD's caps are 0.81 of it,
   NVIDIA's 0.92, "ARC"'s 0.66, and intel's x-height (the 'n', which is what the
   eye reads on a lowercase wordmark) only 0.69. Sizes below put every set of
   caps at 12px and both intel wordmarks at an 8.8px x-height - the ratio at
   which lowercase reads level with caps. */
.s-seg {
  --hw-intel: 13px;    /* x-height 8.9 */
  --hw-amd: 15px;      /* caps 12.2 */
  --hw-nv: 13px;       /* caps 12.0 */
  --hw-arc: 13.5px;    /* intel x-height 8.9, ARC caps 8.7 - matched by design */
}
.hw-mark-intel { height: var(--hw-intel); }
.hw-mark-amd   { height: var(--hw-amd); }
.hw-mark-nv    { height: var(--hw-nv); }
.hw-mark-arc   { height: var(--hw-arc); }
/* Idle logos sit back a touch; selecting brings them fully forward. */
.s-seg label:not(:hover) .hw-mark { opacity: 0.88; }
.s-seg input:checked + label .hw-mark { opacity: 1; }
/* The Intel logo alone does not say Arc, so the GPU option keeps a small suffix. */
.hw-sub {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.02em;
  color: var(--text-mid);
}
.s-seg input:checked + label .hw-sub { color: #eaf0ff; }
/* RAM has no vendor, so it uses a Lucide line icon like the rest of the site.
   currentColor means it tracks the pill's text - muted when idle, bright royal
   when the pill lights. */
.s-seg .hw-ico {
  flex-shrink: 0;
  color: rgba(150, 180, 255, 0.8);
  transition: transform var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}
.s-seg input:checked + label .hw-ico { color: #d6e2ff; }

.s-seg label {
  transition:
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out),
    background var(--dur-1) var(--ease-out);
}
/* Selected: a clean royal fill and a brighter rim, no outer glow. The choice
   reads as filled-in rather than lit-up. */
.s-seg input:checked + label {
  color: #eaf0ff;
  border-color: rgba(130, 165, 255, 0.55);
  background: linear-gradient(180deg, rgba(42, 91, 255, 0.28), rgba(30, 58, 174, 0.24));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
.s-seg input:focus-visible + label { outline: 2px solid rgba(94, 140, 255, 0.85); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .s-seg label, .s-seg .hw-mark { transition: none; }
}

.s-input {
  width: 100%;
  min-height: 44px;
  padding: 11px 13px;
  border-radius: 12px;
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(10, 14, 30, 0.6), rgba(7, 10, 22, 0.7));
  color: var(--text-hi);
  font-family: inherit;
  font-size: 0.92rem;
}
.s-input::placeholder { color: var(--text-low); }
.s-input:focus {
  outline: none;
  border-color: rgba(120, 155, 255, 0.5);
  box-shadow: 0 0 0 3px rgba(42, 91, 255, 0.16);
}

/* Rows are 8px apart, the same step used inside a row and inside a picker. */
.s-games { display: grid; gap: 8px; }
.s-row { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 8px; align-items: center; }
/* The remove control appears only once there is a second row to remove: the
   column is not even reserved for a lone row, so a single game sits full width. */
.s-row-del { display: none; }
.s-games.is-multi .s-row { grid-template-columns: 1.4fr 1fr 1fr 44px; }
.s-games.is-multi .s-row-del {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 12px;
  border: 1px solid var(--border-mid);
  background: rgba(150, 180, 255, 0.04);
  color: var(--text-low);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.s-games.is-multi .s-row-del:hover {
  color: #ffb4b4;
  border-color: rgba(255, 120, 120, 0.42);
  background: rgba(255, 80, 80, 0.08);
}
.s-games.is-multi .s-row-del:focus-visible { outline: 2px solid rgba(255, 140, 140, 0.85); outline-offset: 2px; }
@media (max-width: 460px) {
  .s-row { grid-template-columns: 1fr; }
  /* Stacked: the delete button becomes a full-width row of its own under its inputs. */
  .s-games.is-multi .s-row { grid-template-columns: 1fr; }
  .s-games.is-multi .s-row-del { width: 100%; height: 40px; }
}

.s-addrow {
  width: 100%;
  min-height: 44px;        /* matches the input rows above it */
  margin-top: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: 12px;
  border: 1px dashed var(--border-mid);
  background: none;
  color: var(--text-mid);
  font-family: inherit;
  font-size: 0.88rem;
  cursor: pointer;
}
.s-addrow svg { color: rgba(150, 180, 255, 0.8); }
.s-addrow:hover { color: var(--text-hi); border-color: var(--border-strong); }
.s-addrow:hover svg { color: var(--text-hi); }

/* The session begins by erasing a drive, so this is the one moment a person
   states in their own action that they know what that means. Styled to be read,
   not skimmed past. */
.s-ack {
  /* Anchors the hidden checkbox below. Absolutely positioned with no top/left it
     resolved against the PANEL at its static offset, which put a 1px box 75px
     below the dialog, stretched the overlay's scroll height, and grew a scrollbar
     that stole 10px of panel width on a phone. */
  position: relative;
  padding: 14px;
  border-radius: 14px;
  border: 1px solid rgba(245, 180, 95, 0.24);
  background: rgba(245, 165, 65, 0.06);
}
/* The native box is kept in the DOM - it carries the value and the keyboard and
   screen-reader behaviour - but hidden from view so the drawn tick can replace
   it. Not display:none, which would take it out of the tab order too. */
.s-ack input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}
.s-ack label {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 12px;
  align-items: start;
  font-size: 0.88rem;
  line-height: 1.55;
  color: #f0dcc0;
  cursor: pointer;
}
/* Amber, not royal. Every other picker in this dialog lights royal, but this one
   box is deliberately amber because it is the destructive-action warning; a lone
   royal tick inside an amber field would read as a mismatch. */
.s-tick {
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 6px;
  border: 1px solid rgba(245, 180, 95, 0.5);
  background: rgba(10, 8, 4, 0.4);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  transition:
    background var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out);
}
/* The check itself is a mask, so one element is both the box and the mark and
   there is no second node to keep aligned. */
.s-tick::after {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background: #1a1206;
  opacity: 0;
  transform: scale(0.6);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 14px 14px no-repeat;
  transition: opacity var(--dur-1) var(--ease-out), transform var(--dur-1) var(--ease-out);
}
.s-ack label:hover .s-tick { border-color: rgba(245, 180, 95, 0.75); }
.s-ack input:checked + label .s-tick {
  border-color: rgba(245, 185, 100, 0.9);
  background: linear-gradient(180deg, #f6c069, #e39b32);
}
.s-ack input:checked + label .s-tick::after { opacity: 1; transform: scale(1); }
/* :focus as well as :focus-visible on purpose. Failing to tick this box is the
   one blocked submit in the form, and the handler moves focus here to say so -
   programmatic focus does not reliably match :focus-visible, so without the
   plain :focus the ring would be missing at the exact moment it is needed. */
.s-ack input:focus + label .s-tick,
.s-ack input:focus-visible + label .s-tick {
  outline: 2px solid rgba(245, 190, 110, 0.9);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .s-tick, .s-tick::after { transition: none; }
}

/* Revealed when the country list cannot name where someone is. */
.s-other { margin-top: 10px; }
.s-other[hidden] { display: none; }

/* --- Custom country dropdown --------------------------------------------
   The trigger is deliberately identical to .s-input so the field
   still reads as one of the form's text inputs; only the popup is ours. */
.s-sel { position: relative; }
.s-sel-btn {
  width: 100%;
  min-height: 44px;
  padding: 11px 13px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-radius: 12px;
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(10, 14, 30, 0.6), rgba(7, 10, 22, 0.7));
  color: var(--text-hi);
  font-family: inherit;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
}
/* Placeholder weight until something is chosen, matching ::placeholder. */
.s-sel-value.is-empty { color: var(--text-low); }
.s-sel-chev {
  flex-shrink: 0;
  color: #8ea3cc;
  transition: transform var(--dur-1) var(--ease-out);
}
.s-sel-btn[aria-expanded="true"] .s-sel-chev { transform: rotate(180deg); }
.s-sel-btn:hover { border-color: var(--border-strong); }
.s-sel-btn:focus-visible,
.s-sel-btn[aria-expanded="true"] {
  outline: none;
  border-color: rgba(120, 155, 255, 0.5);
  box-shadow: 0 0 0 3px rgba(42, 91, 255, 0.16);
}

.s-sel-list {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  z-index: 5;
  margin: 0;
  padding: 6px;
  list-style: none;
  /* A grid with a gap, not a flush stack: each highlight needs unlit space
     around it or dragging the pointer down the list reads as one growing block
     instead of a row at a time. */
  display: grid;
  gap: 3px;
  max-height: 268px;
  overflow-y: auto;
  border-radius: 12px;
  border: 1px solid rgba(120, 160, 255, 0.28);
  /* Opaque enough to read against whatever it covers - a translucent popup over
     translucent inputs turns both to mush. */
  background: linear-gradient(180deg, rgba(16, 22, 46, 0.98), rgba(9, 13, 28, 0.99));
  box-shadow: 0 26px 50px -24px rgba(0, 0, 0, 0.9);
  animation: s-sel-in 0.14s var(--ease-out) both;
}
.s-sel-list[hidden] { display: none; }
@keyframes s-sel-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .s-sel-list { animation: none; }
  .s-sel-chev { transition: none; }
}

/* Shorter than an input on purpose. The trigger stays 44px to line up with the
   fields around it, but a row in an open list is a target you are already
   pointing at, not one you have to find, so it does not need the same height. */
.s-sel-opt {
  padding: 7px 11px;
  border-radius: 8px;
  font-size: 0.9rem;
  line-height: 1.35;
  color: var(--text-mid);
  cursor: pointer;
}
/* On touch there is no pointer to aim with, so the rows go back up to a
   comfortable tap target. */
@media (pointer: coarse) {
  .s-sel-opt { padding: 10px 11px; }
}
/* One highlight for both the pointer and the keyboard, so the two never
   disagree about which row is about to be chosen. */
.s-sel-opt:hover,
.s-sel-opt.is-active {
  background: rgba(120, 160, 255, 0.12);
  color: var(--text-hi);
}
.s-sel-opt[aria-selected="true"] {
  color: #eaf0ff;
  background: linear-gradient(180deg, rgba(42, 91, 255, 0.26), rgba(30, 58, 174, 0.22));
}

/* --- Booking dialog on a phone ------------------------------------------
   The dialog keeps all three GPU options on one row down to 340px, so every
   number here is set by the narrowest case: at 360px the three buttons are
   96px each, leaving 79px of inner width, and the widest logo (intel ARC at
   15px tall) measures 68px. It fits, with room to spare, at every size above
   the 340px breakpoint where the pickers stack instead. */
@media (max-width: 560px) {
  /* Give the panel back the page gutter - 16px of width per side is a whole
     logo's worth of room on a 375px screen. */
  .s-modal { padding: 12px; }
  /* Exactly the visible height minus this overlay's own padding, so the panel
     can never be taller than the screen and the footer button is always whole. */
  .s-modal-panel { max-height: calc(100vh - 24px); }
  .s-modal-panel { max-height: calc(100dvh - 24px); }
  .s-modal-head { padding: 14px 16px 12px; }
  .s-modal-body { padding: 16px; gap: 18px; }
  .s-modal-foot { padding: 12px 16px 14px; }
  .s-modal-head .s-plan-badge { font-size: 0.88rem; padding: 4px 12px 5px; }

  .s-seg { gap: 6px; }
  .s-seg label { min-height: 42px; padding: 10px 8px; }
  /* Same letter-height logic as the desktop set, one step down. */
  .s-seg {
    --hw-intel: 11.5px;
    --hw-amd: 13px;
    --hw-nv: 11.5px;
    --hw-arc: 12px;
  }
  .s-field-label, .s-field > label, .s-bd-title { font-size: 0.92rem; }
  .s-input, .s-select, .s-sel-btn { font-size: 0.9rem; }
  .s-ack { padding: 12px; }
  .s-ack label { font-size: 0.85rem; }

  /* The breakdown list, tuned the same way as the form above it - it had no
     phone treatment at all, so its rows sat a step larger than every other
     piece of text in the dialog. At this width rows wrap to two or three lines,
     so they also need more space between them than the 10px they get on a
     desktop, or a wrapped row runs into the next one. */
  .s-bd-group + .s-bd-group { margin-top: 20px; }
  .s-bd-list { gap: 12px; }
  .s-bd-list li { font-size: 0.9rem; }
}

/* Under here the pickers stack, so a stacked row gets its full width back and
   the logos can return to their comfortable size. */
@media (max-width: 340px) {
  .s-seg {
    --hw-intel: 13px;
    --hw-amd: 15px;
    --hw-nv: 13px;
    --hw-arc: 13.5px;
  }
}

.s-err { margin: 0; font-size: 0.86rem; line-height: 1.5; color: #ffb4b4; }
.s-err[hidden] { display: none; }


/* ============================================================================
   SESSION TIME PICKER (#pickdate)
   ============================================================================
   A dialog of its own, driven by js/datepicker.js. It borrows the shared .s-modal
   overlay, panel, head and close button, so the backdrop, blur and stacking match
   the two dialogs beside it; everything named dp- belongs only to this component.

   A month grid with the times listed beside it. The selected day and the selected
   hour are filled with the same royal recipe as .s-seg input:checked further up
   this file rather than a new one - choosing a date and choosing a graphics card
   are the same kind of act, and two dialects of "chosen" in one flow would read as
   two products. Every number is mono with tabular figures, which is how this site
   sets figures everywhere else. */
.dp-panel { width: min(560px, 100%); }

/* The head keeps the shared parts - chip left, heading between, X right, all on
   one centred row - but the heading is laid out rather than positioned.

   The shared rule centres it on the panel with position:absolute and
   translate(-50%,-50%). Half of an odd text width is a half pixel, and text
   painted on a fractional offset renders soft; this heading was visibly blurry
   next to the crisp chip beside it. As an ordinary flex child it lands on whole
   pixels and sharpens up.

   It also changes what "centre" means, which is the point: the box now spans the
   gap between the chip and the X, so the heading sits centred in the space it
   actually has instead of dead centre of the panel with a chip crowding one side.
   Scoped to this dialog - the other two set their heading dead centre on purpose. */
.dp-modal .s-modal-title {
  position: static;
  transform: none;
  flex: 1;
  min-width: 0;
  max-width: none;
  text-align: center;
  /* A long package name must never push the X off the row. */
  overflow: hidden;
  text-overflow: ellipsis;
}

.dp-msg {
  margin: 0;
  padding: 18px 20px 22px;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.01em;
  color: var(--text-low);
}
.dp-msg.is-loading::after { content: ''; animation: dp-dots 1.4s steps(4, end) infinite; }
@keyframes dp-dots {
  0%   { content: ''; }
  25%  { content: '.'; }
  50%  { content: '..'; }
  75%  { content: '...'; }
}
.dp-msg.is-bad { color: #ffb4b4; }

/* The scrolling region, so the head and the footer stay put.

   The panel is a flex column capped at 86dvh. A flex child will not shrink below
   its own content unless it is told to, so without min-height:0 this row kept its
   full height and pushed the footer - the Continue button, the only way forward -
   out of the panel. On a phone held sideways that button sat 130px below the
   bottom of the screen. */
.dp-body {
  display: grid;
  grid-template-columns: 1fr 158px;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* A classic scrollbar here costs 15px of a 280px calendar - a whole column of
     thumb room on the narrowest phones. Touch devices draw it as an overlay and
     pay nothing; this is for narrow desktop windows. */
  scrollbar-width: thin;
}
/* Both scroll regions in this dialog, one recipe. The default bar is a light slab
   against a dark panel; same width, own colours. */
.dp-body,
.dp-times { scrollbar-color: rgba(150, 180, 255, 0.18) transparent; }
.dp-body::-webkit-scrollbar,
.dp-times::-webkit-scrollbar { width: 6px; }
.dp-body::-webkit-scrollbar-track,
.dp-times::-webkit-scrollbar-track { background: transparent; }
.dp-body::-webkit-scrollbar-thumb,
.dp-times::-webkit-scrollbar-thumb { background: rgba(150, 180, 255, 0.18); border-radius: 999px; }
.dp-body::-webkit-scrollbar-thumb:hover,
.dp-times::-webkit-scrollbar-thumb:hover { background: rgba(150, 180, 255, 0.32); }
.dp-cal { padding: 14px 16px 18px; min-width: 0; }

.dp-cal-head {
  display: grid;
  grid-template-columns: 30px 1fr 30px;
  align-items: center;
  margin-bottom: 12px;
}
.dp-month {
  text-align: center;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text-hi);
}
.dp-nav {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 9px;
  border: 1px solid transparent;
  background: none;
  color: var(--text-low);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.dp-nav:hover:not(:disabled) { color: var(--text-hi); border-color: var(--border-mid); }
/* Disabled, not removed: the window is only 30 days, so one arrow is always dead,
   and a control that disappears at the edge reads as a rendering fault. */
.dp-nav:disabled { opacity: 0.28; cursor: not-allowed; }
.dp-nav:focus-visible { outline: 2px solid rgba(94, 140, 255, 0.85); outline-offset: 1px; }

.dp-dow,
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.dp-dow { margin-bottom: 5px; }
.dp-dow span {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-low);
}

.dp-cell {
  /* A fixed row height, not aspect-ratio: 1. Square cells take their height from
     the column width, so a 52px-wide column made a 52px-tall row and six of those
     stretched the calendar 60px past the times beside it. Calendars are wider than
     they are tall; 42px keeps the touch target and closes the gap. */
  height: 42px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 10px;
  background: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.86rem;
  color: var(--text-mid);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.dp-cell.is-blank { cursor: default; }
/* button, not .dp-cell: the leading alignment cells are spans, and :not(:disabled)
   is true for a span - so hovering the empty squares before the 1st of the month
   lit a border on nothing. */
button.dp-cell:hover:not(:disabled) { color: var(--text-hi); border-color: var(--border-strong); }
/* The chosen day and the chosen hour are the same decision, so they are one rule.
   It is deliberately identical to .s-seg input:checked + label further up: picking
   a date and picking a graphics card should not read as two products. */
.dp-cell.is-on,
.dp-time.is-on {
  color: #eaf0ff;
  border-color: rgba(130, 165, 255, 0.55);
  background: linear-gradient(180deg, rgba(42, 91, 255, 0.28), rgba(30, 58, 174, 0.24));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
/* Two different facts, drawn differently. Struck through: this day was bookable
   and is now taken - the one state that argues for booking early. Merely dim: this
   day is outside the 24-hour to 30-day window, so it was never on offer, and
   striking it would claim something about availability that was never true. */
.dp-cell.is-gone { color: var(--text-low); text-decoration: line-through; cursor: not-allowed; }
.dp-cell.is-out { color: rgba(214, 225, 245, 0.2); cursor: not-allowed; }
.dp-cell:focus-visible { outline: 2px solid rgba(94, 140, 255, 0.85); outline-offset: 1px; }

.dp-times {
  border-left: 1px solid var(--border-soft);
  padding: 14px 14px 18px;
  display: grid;
  gap: 8px;
  align-content: start;
  /* Titan's six starts measure 336px at this rhythm, so the cap clears them
     outright - a bright bar beside a column that is already showing everything is
     worse than no bar. Boost's eight scroll, which is what the column is for. */
  max-height: 344px;
  overflow-y: auto;
  scrollbar-width: thin;
}
.dp-time {
  min-height: 44px;
  padding: 0 10px;
  border-radius: 10px;
  border: 1px solid var(--border-mid);
  background: linear-gradient(180deg, rgba(16, 22, 44, 0.5), rgba(10, 14, 30, 0.56));
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--text-mid);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}
.dp-time:hover { color: var(--text-hi); border-color: var(--border-strong); }
.dp-time:focus-visible { outline: 2px solid rgba(94, 140, 255, 0.85); outline-offset: 2px; }

.dp-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px 15px;
  border-top: 1px solid var(--border-soft);
}
/* States the span before Continue will go, so the commitment is read once in full
   rather than assembled from a highlighted square and a highlighted pill. */
.dp-out { margin: 0; flex: 1; min-width: 0; font-size: 0.82rem; line-height: 1.4; color: var(--text-mid); }
.dp-out b {
  display: block;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: 0.86rem;
  letter-spacing: -0.01em;
  color: var(--text-hi);
}
.dp-out span { font-size: 0.78rem; color: var(--text-low); }
.dp-go { flex: 0 0 auto; width: auto; }
.dp-go:disabled { opacity: 0.45; cursor: not-allowed; }

/* Stacked below the split: 158px of times beside a seven-column month leaves the
   calendar about 180px, and a 25px date cell is not a touch target. */
@media (max-width: 520px) {
  .dp-body { grid-template-columns: 1fr; }
  .dp-times {
    border-left: 0;
    border-top: 1px solid var(--border-soft);
    grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
    max-height: none;
  }
  .dp-foot { flex-direction: column; align-items: stretch; }
  .dp-go { width: 100%; }
}

/* Seven columns cannot each be 44px on a 320px phone - a month grid is 7 wide by
   arithmetic, and 280px of panel divided seven ways is 40px before any gap. The
   side padding and one pixel of gap are the entire budget available, so they go
   to the cells: 35px becomes 38px, which is the most this layout can give. */
@media (max-width: 380px) {
  .dp-cal { padding: 12px 10px 14px; }
  .dp-dow,
  .dp-grid { gap: 2px; }
  .dp-times { padding: 12px 10px 16px; }
  .dp-foot { padding: 12px 12px 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .dp-cell, .dp-time, .dp-nav { transition: none; }
  .dp-msg.is-loading::after { animation: none; content: '...'; }
}
