/* =====================================================================
   MyAbhyas — THE LANDING PAGE, AND ONLY THE LANDING PAGE.
   ---------------------------------------------------------------------
   This is the TechMate design as delivered, carried over whole rather
   than re-expressed in the product stylesheet's own classes.

   Re-expressing it was tried first, and it is why this file exists.
   Porting a design section by section gets every piece nearly right and
   the page as a whole not right: what makes a design a design is the
   relationships between the pieces — a measure, a rhythm of space, one
   weight against another — and those are exactly what does not survive
   being translated one rule at a time.

   So index.php does NOT load style.css. It loads this, and nothing
   else. The two files share no selectors and never meet, which is what
   makes the generic names in here — .wrap, .btn, .card, .tile — safe:
   this stylesheet has one page, and that page has no other stylesheet.

   Change the product's look in style.css. Change the landing page's
   look here. They are deliberately not the same thing.
   ===================================================================== */

/* ------------------------------------------------------------------
   Tokens lifted from techm8.in's own stylesheet, so the two sites read
   as one house. Every word and figure comes from the live myabhyas.in.
   ------------------------------------------------------------------ */
/* [hidden] BELONGS HERE AND NOWHERE ELSE, and it has to outweigh a class.
   The attribute's own rule is `display: none` at the weakest specificity
   there is, so any class setting a display beats it — .bp sets one, and
   .chip is display:inline-flex. With this missing, el.hidden on two of the
   three blueprint tables did nothing at all and every subject's chapters
   showed at once: sixty chips under a heading that says fourteen.
   style.css carries the same line for the same reason; this page does not
   load style.css, which is how it was lost. */
[hidden] { display: none !important; }

:root {
  --navy:        #1B2F5E;
  --navy-dark:   #111E3F;
  --navy-light:  #243D73;
  --red:         #D42B2B;
  --red-dark:    #B02020;
  --white:       #FFFFFF;
  --offwhite:    #F4F6FA;
  /* DARKENED FROM THE PALETTE'S OWN #8A93A8, at the owner's instruction.
     That grey measures 3.08:1 on white and 2.85:1 on the pale ground, where
     text at 14.5px wants 4.5:1. This is 6.20:1 and 5.73:1. The same value
     is written into every illustration on the page, because the drawings
     are pictures of the product and their captions have to read as the
     grey the page is set in. */
  --grey:        #606163;
  --text:        #1A1A2E;
  --ink-soft:    #4A5570;

  /* THE FESTIVAL PALETTE, two values. --fest-line draws every outline --
     the lantern, the toran, the diya bowl -- and --fest-flame is the only
     thing that glows. They have to read on BOTH grounds the rails cross:
     the navy hero at the top and the white sections below it. That rules
     out the pale golds the still artwork uses on its dark cards, which
     vanish on white, and it is why the outline is the page own red
     rather than a warmer one. */
  --fest-line:   #C2452F;
  --fest-flame:  #E8A33F;
  --fest-flame-hot: #FFE9A8;  /* the pale heart of a flame */
  --fest-clay:   #B55A3C;     /* the diya bowl */
  --fest-clay-lo: #8C3F28;    /* its rim and foot, in shadow */

  /* THE BURST PALETTE. A firework is not one colour -- the whole reason a
     real one reads as a celebration is that the sparks disagree. These six
     are dealt round the twelve sparks of every burst, so each burst carries
     all six twice and no two neighbours match. They are saturated rather
     than pastel because the rails cross white sections, where a pale spark
     is invisible. */
  --fest-c1:     #E8A33F;   /* gold    */
  --fest-c2:     #E23B3B;   /* red     */
  --fest-c3:     #E85BA8;   /* pink    */
  --fest-c4:     #7B5BD6;   /* violet  */
  --fest-c5:     #2BA8A0;   /* teal    */
  --fest-c6:     #4C8DEE;   /* blue    */

  --shadow:      0 8px 40px rgba(27,47,94,.10);
  --shadow-lift: 0 18px 60px rgba(17,30,63,.16);
  --shadow-deep: 0 26px 70px rgba(17,30,63,.30);
  --r:           14px;
  --r-sm:        8px;
  --ease:        .35s cubic-bezier(.4,0,.2,1);

  --head: 'Montserrat', 'Segoe UI', system-ui, sans-serif;
  --body: 'DM Sans', 'Segoe UI', system-ui, sans-serif;

  --gut: clamp(16px, 4vw, 56px);
  /* THE TOP BAR'S HEIGHT, IN ONE PLACE. It is the bar's own min-height, the
     padding that pushes the page clear of it, and the pair of values that
     pull the hero back UNDER it so the bar can be transparent over it. The
     moment those disagree the hero's top edge slides out from behind the bar
     and a band of page shows through, so they read the same token. */
  --bar: 91px;
  --max: 1180px;
}

* { box-sizing: border-box; }
img, svg { max-width: 100%; }

body {
  margin: 0; background: var(--white); color: var(--text);
  font-family: var(--body); font-size: 16px; line-height: 1.7;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}

h1, h2, h3, h4 { font-family: var(--head); color: var(--navy); margin: 0; line-height: 1.14; text-wrap: balance; letter-spacing: -.01em; overflow-wrap: break-word; }
p { margin: 0; overflow-wrap: break-word; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 3px solid var(--red); outline-offset: 3px; border-radius: 4px; }

.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }

/* ------------------------------------------------------------------
   EVERY BOX IN A ROW IS THE SAME HEIGHT, AND THE TEXT DECIDES IT ONCE.

   .lines sets the block to an exact number of lines: min-height so a
   short paragraph cannot make its card shorter than its neighbours, and
   line-clamp so a long one cannot make it taller. The copy in each row
   is written to fill that box, and the clamp is the guarantee rather
   than the plan — it is what holds the row level when a word is edited
   later, or when a font falls back to something wider.
   ------------------------------------------------------------------ */
.lines {
  display: -webkit-box; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.65;
}
/* THE TESTIMONIALS ARE THE ONE PLACE A CUT-OFF IS WANTED. Those are a
   student's own words and cannot be reworded to fit, so they end in an
   ellipsis. Every other block on the page is written to its budget and
   never reaches its limit, so no ellipsis ever appears on one. */
.l7 { -webkit-line-clamp: 7; min-height: 11.55em; }

/* And the rest bite only where there is a ROW to keep level. One card per
   row on a phone has nothing to line up with, so there the copy flows.

   THE FOUR STAGES MOVED FROM l6 TO l4 WHEN THEY WENT 2x2. min-height is
   what keeps two cards in a row the same height, and at four across the
   copy really did run to six lines; at two across the card is 522px and the
   same words come to three, so six reserved lines were three empty ones
   under every paragraph. The budget has to follow the column width. */
@media (min-width: 620px) {
  .l2 { -webkit-line-clamp: 2; min-height: 2.28em; line-height: 1.14; }
  .l3 { -webkit-line-clamp: 3; min-height: 4.95em; }
  .l4 { -webkit-line-clamp: 4; min-height: 6.6em; }
  .l5 { -webkit-line-clamp: 5; min-height: 8.25em; }
  .l6 { -webkit-line-clamp: 6; min-height: 9.9em; }
}

/* ---------------------------------------------------------- eyebrow */
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--head); font-size: clamp(10px, 2.4vw, 12px); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  padding: 7px 15px; border-radius: 999px;
  background: rgba(212,43,43,.10);
  /* THE DARKER RED, and only for the letters. The chip is the red at 10%,
     which composites to about #FDECEC, and the bright red on it measures
     4.40:1 on white and 4.00:1 on the pale ground — under the 4.5 a 12px
     label wants. --red-dark on the same chip is 5.98:1. The tint is left
     exactly as the design sets it; it is one step on the type. */
  color: var(--red-dark);
  text-align: left;
}
.eyebrow::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--red); flex: none; }
.eyebrow.on-dark { background: rgba(255,255,255,.07); color: #FFF; border: 1px solid rgba(255,255,255,.14); }

/* ---------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: var(--head); font-size: 14px; font-weight: 700;
  padding: 14px 24px; border-radius: 999px; border: 2px solid transparent;
  cursor: pointer; text-align: center;
  /* A label never wraps and a button never shrinks: in a flex row both
     end with the words jammed against the edges of their own box. */
  white-space: nowrap; flex: none;
  transition: background var(--ease), color var(--ease), border-color var(--ease), transform var(--ease);
}
.btn-red { background: var(--red); color: #FFF; }
.btn-red:hover { background: var(--red-dark); transform: translateY(-2px); }
.btn-line { border-color: rgba(255,255,255,.28); color: #FFF; }
.btn-line:hover { border-color: #FFF; background: rgba(255,255,255,.08); }
.btn-outline { border-color: rgba(27,47,94,.22); color: var(--navy); }
.btn-outline:hover { border-color: var(--navy); background: rgba(27,47,94,.05); }
.btn-sm { padding: 10px 20px; font-size: 13px; }

/* --------------------------------------------------------------- nav
   The live site's own menu, item for item: How it works · Subjects ·
   Testimonials · Pricing · Sign in, then Signup Free as the one button.
   The four section links hide on a small screen exactly as its
   .is-hide-sm does, leaving the two that matter on a phone. */
/* THE HEADER SITS ON THE HERO, which is what makes the top of the page one
   navy field rather than a white strip with a navy block under it. Measured
   on techm8.in: transparent over the hero with white links, and at
   rgba(255,255,255,.96) with navy links once the page has scrolled past it.

   FIXED, not sticky. Sticky leaves the header in the flow, so the hero would
   begin underneath it and the first 70px of navy would be the header's own
   row rather than the hero's. Fixed takes it out, and .hero carries the
   padding that keeps its content clear of it.

   The transparent state is the DEFAULT and .is-solid is added by script, so
   a page with no JavaScript gets the readable one. It is the other way round
   from how it reads, and deliberately: the failure a reader can see is white
   letters on a white bar, and that is the state that must not be reachable
   by accident. */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background .25s ease, border-color .25s ease, box-shadow .25s ease;
}
.nav.is-solid {
  background: rgba(255,255,255,.96);
  backdrop-filter: blur(10px);
  border-bottom-color: rgba(27,47,94,.08);
  box-shadow: 0 2px 24px rgba(27,47,94,.10);
}

/* ON THE HERO the menu is white; once the bar is solid it is navy. The logo
   is dark artwork, so over the navy it is printed in white the way the rail
   and the sign-in aside print it. */
/* 65px TALL, the owner's own size. The live site sets 80 — probed on
   www.myabhyas.in, where the top bar carries
   `.lp-top .lp-logo img { height: 80px }` — and he took it down from there
   after seeing it. One size at every width now: 65 clears the bar on a
   phone as easily as on a laptop, so the second rule that live needs is
   not needed here.

   width:auto keeps the artwork's own proportions; at 769 by 300 that
   draws it about 167px across. */
/* TWO MARKS STACKED, cross-fading with the bar. The dark one sets the
   layout; the light one lies over it and is brought up when the bar goes
   solid. It was one image with brightness(0) invert(1) on it, which forced
   the red target at the end of the mark to white along with everything
   else. */
.nav .logo { position: relative; }
.nav .logo img { height: 65px; width: auto; display: block; transition: opacity .25s ease; }
.nav .logo .logo-on-light { position: absolute; inset: 0; opacity: 0; }
.nav.is-solid .logo-on-dark { opacity: 0; }
.nav.is-solid .logo-on-light { opacity: 1; }

.nav-menu > a:not(.btn) { color: #FFF; }
.nav.is-solid .nav-menu > a:not(.btn) { color: var(--navy); }

/* The body is pushed down by exactly the bar's height, because the bar no
   longer takes any room of its own. */
body { padding-top: var(--bar); }
.hero { margin-top: calc(var(--bar) * -1); padding-top: var(--bar); }
/* 6px of padding, not 11. The bar is min-height: var(--bar) PLUS whatever
   its contents need, so taking the logo to the live site's 80px pushed the
   bar from 92 to 103 — a change nobody asked for, on top of the 30 per cent
   the owner had already set. Six pixels each side keeps the bar where he put
   it and still clears the taller mark; the live site runs four. */
.nav-in { display: flex; align-items: center; gap: 10px 14px; min-height: var(--bar); padding-block: 6px; flex-wrap: wrap; }
.logo { font-family: var(--head); font-weight: 900; font-size: clamp(19px, 4.6vw, 23px); letter-spacing: -.02em; color: var(--navy); display: inline-flex; align-items: center; gap: 2px; flex: none; }
.logo b { background: var(--red); color: #FFF; padding: 2px 8px 3px; border-radius: var(--r-sm); font-weight: 900; }
.nav-menu {
  display: flex; align-items: center; gap: clamp(14px, 2.4vw, 28px);
  margin-left: auto; font-family: var(--head); font-size: 14px; font-weight: 600;
}
.nav-menu > a:not(.btn) { padding: 4px 0; border-bottom: 2px solid transparent; white-space: nowrap; flex: none; }
.nav-menu > a:not(.btn):hover { color: var(--red); border-color: var(--red); }
@media (max-width: 860px) { .nav-menu .hide-sm { display: none; } }
@media (max-width: 420px) { .nav-menu { gap: 12px; } }
@media (max-width: 360px) { .nav-menu { gap: 10px; } .nav-menu .btn { padding: 10px 16px; font-size: 12.5px; } }

/* -------------------------------------------------------------- hero */
.hero { position: relative; background: var(--navy-dark); color: #FFF; overflow: hidden; }
/* THE CHECKED TEXTURE — ruled squares across the whole hero.
   This was a field of dots before, which is the same ink as the two dotted
   patches below and left the hero reading as one undifferentiated speckle.
   Lines for the ground, dots for the patches: two textures that can be told
   apart, which is what makes a patch read as a patch.
   Faint on purpose — at anything above about 6 per cent white the grid starts
   competing with the headline instead of sitting behind it. The mask fades it
   out towards the edges so it never meets the section below as a hard line. */
.hero::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image:
    linear-gradient(to right,  rgba(255,255,255,.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,.055) 1px, transparent 1px);
  background-size: 62px 62px;
  -webkit-mask-image: radial-gradient(135% 100% at 32% 18%, #000 48%, transparent 88%);
          mask-image: radial-gradient(135% 100% at 32% 18%, #000 48%, transparent 88%);
}

/* THE TWO DOTTED PATCHES — top-right, as techm8.in has it, and lower-left.
   The SAME SIZE as each other, deliberately: two patches of one texture at
   two sizes read as one being nearer than the other, which is a depth cue
   nothing else on this page makes.

   EACH ONE IS ITS OWN BOX, AND THAT IS WHAT KEEPS THE DOTS WHOLE. The first
   version painted the lattice across the entire hero and cropped it to two
   rectangles with a mask. A crop knows nothing about where the dots are, so
   its edge fell mid-dot and the top row and the left column came out as
   halves — which is what the owner saw and sent back. Inside a box of its
   own, the lattice starts at that box's corner, so a size that is a WHOLE
   NUMBER OF TILES can never slice one: 11 across and 7 down of a 17px tile,
   187 x 119. Change the size and keep it a multiple of 17, or the halves
   come back.

   It also retires a mask that had to be written twice for Safari and failed
   OPEN when it did not apply — dots over the whole hero. Nothing to fail now. */
.hero-dots {
  position: absolute; pointer-events: none;
  width: 187px; height: 119px;          /* 11 x 7 tiles of 17px */
  background-image: radial-gradient(rgba(255,255,255,.30) 1.7px, transparent 1.8px);
  background-size: 17px 17px;
}
/* Both moved at the owner's eye: the top-right one 100px further in from the
   right, the lower-left one 100px up and 150px towards the centre. */
.hero-dots-tr { top: 20%;   right: 150px; }
.hero-dots-bl { top: 75.5%; left: 169px; }

/* BOTH PATCHES GO ONCE THE HERO RUNS OUT OF ROOM FOR THEM.
   They live in the space beside and below a two-column layout; .hero-in
   collapses that grid at 960, and once the artwork stacks under the words
   there is no such space at all -- measured at 900px before this rule
   existed, the top-right patch landed squarely on the headline.
   1024 AND NOT 960, which is the measurement rather than the obvious number:
   between 961 and 1000 the two columns are still there but already tight
   enough that the buttons drop into the band the lower-left patch occupies.
   1024 is the last width at which both patches have room, so it is where
   they stop.
   The ground grid stays, tightened, because a texture is still a texture on a
   phone -- it is only the patches that need somewhere to sit. */
@media (max-width: 1024px) {
  .hero-dots { display: none; }
  .hero::before { background-size: 46px 46px; }
}
.hero-in {
  position: relative; display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
  gap: clamp(30px, 5vw, 64px); align-items: center;
  padding-block: clamp(44px, 8vw, 100px);
}
@media (max-width: 960px) { .hero-in { grid-template-columns: minmax(0, 1fr); } }
.hero h1 { color: #FFF; font-weight: 900; font-size: clamp(29px, 6.2vw, 58px); margin: 20px 0 16px; text-transform: uppercase; letter-spacing: -.015em; }
.hero-sub { color: rgba(255,255,255,.72); font-size: clamp(14.5px, 1.6vw, 17.5px); font-weight: 300; max-width: 54ch; }
.hero-acts { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }
.hero-trust { margin-top: 24px; font-size: 13px; color: rgba(255,255,255,.55); }
.hero-trust b { color: #FFF; font-weight: 500; }

.art { position: relative; }
.art svg { display: block; width: 100%; height: auto; filter: drop-shadow(0 26px 60px rgba(0,0,0,.42)); }
.pin { position: absolute; background: #FFF; border-radius: var(--r); box-shadow: 0 14px 38px rgba(8,16,36,.32); padding: 12px 16px; display: flex; align-items: center; gap: 11px; }
.pin-ico { width: 36px; height: 36px; flex: none; border-radius: 10px; display: grid; place-items: center; }
.pin-k { font-family: var(--head); font-size: 9.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--grey); line-height: 1.4; }
.pin-v { font-family: var(--head); font-size: 15px; font-weight: 800; color: var(--navy); line-height: 1.2; }
.pin-a { top: 4%; right: -2%; }
.pin-b { bottom: 6%; left: -4%; }
@media (max-width: 620px) {
  .art { display: flex; flex-direction: column; gap: 12px; }
  .pin { position: static; box-shadow: var(--shadow); }
  .pin-a, .pin-b { inset: auto; }
}

/* ----------------------------------------------------------- marquee */
.marquee { background: var(--offwhite); border-block: 1px solid rgba(27,47,94,.07); overflow: hidden; padding-block: 14px; }
.marquee-track { display: flex; width: max-content; animation: slide 64s linear infinite; }
/* FULL NAVY, at the owner's instruction. It was navy at 62 per cent, which
   on the pale ground reads as a grey and made the line look like a caption
   rather than like the claims it is making. The dot between the claims keeps
   its own softening — the opacity moved off the span so it no longer fades
   the dot a second time. */
.marquee-track span {
  font-family: var(--head); font-size: 12px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--navy);
  padding-inline: 22px; display: inline-flex; align-items: center; gap: 22px; white-space: nowrap;
}
.marquee-track span::after { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--red); opacity: .75; }
@keyframes slide { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .marquee-track { animation: none; } }

/* -------------------------------------------------------- red band */
.band { background: linear-gradient(100deg, var(--red-dark), var(--red)); color: #FFF; }
.band-in { display: grid; grid-template-columns: repeat(4, 1fr); padding-block: clamp(32px, 4.5vw, 52px); }
.band-cell { text-align: center; padding: 6px 8px; }
.band-cell + .band-cell { border-left: 1px solid rgba(255,255,255,.22); }
.band-n { font-family: var(--head); font-weight: 900; font-size: clamp(24px, 4.4vw, 44px); line-height: 1; font-variant-numeric: tabular-nums; }
.band-k { font-family: var(--head); font-size: clamp(9px, 1.8vw, 10.5px); font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: rgba(255,255,255,.84); margin-top: 9px; }
@media (max-width: 760px) { .band-in { grid-template-columns: repeat(2, 1fr); gap: 26px 0; } .band-cell:nth-child(3) { border-left: 0; } }

/* ---------------------------------------------------------- sections */
section { padding-block: clamp(48px, 8vw, 100px); }
.tint { background: var(--offwhite); }
.head-c { text-align: center; max-width: 720px; margin-inline: auto; }
/* ONE LINE. "The Topper is the real thing." needs 731px set in Montserrat 900
   at 42px and the head is 720 — eleven pixels short, so it broke over two
   lines with one word on the second. The HEAD widens and the standfirst under
   it keeps its own measure, because a line of running text at 780px is a
   worse read than the same text at 720 and this is about the heading only.
   It still wraps where it has to: the size is a clamp, so below about 1000px
   the type comes down and the line fits inside whatever is there. */
.head-c.is-wide { max-width: 800px; }
.head-c.is-wide p { max-width: 720px; margin-inline: auto; }
.head-c h2 { font-weight: 900; font-size: clamp(23px, 4.2vw, 42px); margin: 16px 0 14px; text-transform: uppercase; }
.head-c p { color: var(--grey); font-weight: 300; font-size: clamp(14.5px, 1.7vw, 16.5px); }

/* -------------------------------------------------------------- grid */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); gap: 22px; margin-top: clamp(34px, 5vw, 52px); }
.card {
  background: #FFF; border: 1px solid rgba(27,47,94,.07); border-radius: var(--r);
  box-shadow: var(--shadow); padding: clamp(24px, 3vw, 32px) clamp(22px, 2.6vw, 30px) 28px;
  transition: transform var(--ease), box-shadow var(--ease);
  display: flex; flex-direction: column;
}
.card:hover { transform: translateY(-6px); box-shadow: var(--shadow-lift); }
.card-ico { width: 56px; height: 56px; border-radius: 15px; background: var(--offwhite); display: grid; place-items: center; margin-bottom: 20px; flex: none; }
.card h3 { font-size: clamp(16px, 2.1vw, 18px); font-weight: 800; margin-bottom: 11px; text-transform: uppercase; }
.card p { color: var(--grey); font-weight: 300; font-size: 14.5px; }
.card .more { display: inline-flex; align-items: center; gap: 7px; margin-top: 18px; font-family: var(--head); font-size: 11.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--red); }
.card .more span { transition: transform var(--ease); }
.card:hover .more span { transform: translateX(5px); }
.card .ready { display: inline-block; font-family: var(--head); font-size: 10px; font-weight: 800; letter-spacing: .14em; color: #1E7A4F; background: rgba(30,122,79,.11); padding: 4px 11px; border-radius: 999px; margin-bottom: 13px; align-self: flex-start; }

/* Two by two, as chosen: the paragraphs here are the longest on the page
   and a four-across row squeezes them into a column of single words. */
.why { grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr)); max-width: 900px; margin-inline: auto; }

/* -------------------------------------------------------- the steps */
.steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 24px; margin-top: clamp(34px, 5vw, 54px); }
.step { position: relative; padding-top: 28px; }
.step::before { content: ""; position: absolute; top: 12px; left: 0; right: 0; height: 2px; background: repeating-linear-gradient(90deg, rgba(27,47,94,.16) 0 7px, transparent 7px 14px); }
.step-n { font-family: var(--head); font-size: 38px; font-weight: 900; color: rgba(27,47,94,.13); line-height: 1; font-variant-numeric: tabular-nums; }
.step h3 { font-size: 15.5px; font-weight: 800; margin: 10px 0 8px; text-transform: uppercase; }
.step p { color: var(--grey); font-weight: 300; font-size: 14.5px; }

/* ------------------------------------------------- blueprint + chips */
.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(30px, 5vw, 60px); align-items: center; margin-top: clamp(32px, 4vw, 46px); }
@media (max-width: 900px) { .split { grid-template-columns: minmax(0, 1fr); } }
.split h2 { font-weight: 900; font-size: clamp(21px, 3.4vw, 36px); text-transform: uppercase; margin-bottom: 16px; }
.split p { color: var(--grey); font-weight: 300; }

.bp { background: #FFF; border-radius: var(--r); box-shadow: var(--shadow); overflow: hidden; border: 1px solid rgba(27,47,94,.07); }
.bp-head { background: var(--navy); color: #FFF; display: grid; grid-template-columns: 44px 1fr 60px; font-family: var(--head); font-size: 10px; font-weight: 700; letter-spacing: .12em; padding: 13px clamp(14px, 2.4vw, 20px); }
.bp-row { display: grid; grid-template-columns: 44px 1fr 60px; padding: 11px clamp(14px, 2.4vw, 20px); font-size: clamp(13px, 1.7vw, 14.5px); align-items: center; }
.bp-row + .bp-row { border-top: 1px solid rgba(27,47,94,.07); }
.bp-sec { font-family: var(--head); font-weight: 800; color: var(--red); }
.bp-m { text-align: right; font-family: var(--head); font-weight: 700; color: var(--navy); font-variant-numeric: tabular-nums; }
.bp-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--offwhite); padding: 14px clamp(14px, 2.4vw, 20px); border-top: 2px solid rgba(27,47,94,.08); }
.bp-foot small { color: var(--grey); font-size: 12.5px; }
.bp-total { margin-left: auto; font-family: var(--head); font-weight: 900; font-size: 24px; color: var(--navy); }

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 24px; }
.chip { display: inline-flex; align-items: center; gap: 8px; border: 1px solid rgba(27,47,94,.14); border-radius: 999px; padding: 7px 7px 7px 14px; font-size: 13px; color: var(--ink-soft); background: #FFF; }
.chip b { font-family: var(--head); font-size: 11px; font-weight: 800; background: var(--offwhite); color: var(--navy); border-radius: 999px; padding: 3px 9px; flex: none; }
.chip.on { background: var(--navy); border-color: var(--navy); color: #FFF; }
.chip.on b { background: var(--red); color: #FFF; }
.meter { margin-top: 24px; background: #FFF; border: 1px solid rgba(27,47,94,.1); border-radius: var(--r); padding: 20px 22px; box-shadow: var(--shadow); }
.meter-n { font-family: var(--head); font-weight: 900; font-size: 32px; color: var(--navy); line-height: 1; }
.meter-k { font-size: 13px; color: var(--grey); }
.meter-bar { height: 9px; border-radius: 999px; background: var(--offwhite); overflow: hidden; margin-top: 13px; }
.meter-fill { height: 100%; width: 21.25%; background: linear-gradient(90deg, var(--red-dark), var(--red)); border-radius: 999px; }

/* --------------------------------------------------------- the stages */
/* TWO BY TWO, AND THE DRAWING IS THE POINT. Four across, each card was
   about 280px and the illustration inside it perhaps 240 — a picture of a
   screen, at a size where nothing on that screen can be made out, which is
   the one thing the section exists to show. Two across doubles the width of
   every drawing and costs one screen of scrolling.
   The panel also loses its padding cap and gains more room, because the art
   is now large enough to be looked at rather than glanced past. */
.stages { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-top: clamp(34px, 5vw, 52px); }
@media (max-width: 760px) { .stages { grid-template-columns: minmax(0, 1fr); } }
.stage { background: #FFF; border: 1px solid rgba(27,47,94,.07); border-radius: var(--r); box-shadow: var(--shadow); overflow: hidden; display: flex; flex-direction: column; }
.stage-art { background: var(--offwhite); border-bottom: 1px solid rgba(27,47,94,.07); padding: 26px 30px; }
.stage-art svg { display: block; width: 100%; height: auto; }
.stage-body { padding: 22px 24px 26px; }
.stage-k { font-family: var(--head); font-size: 10.5px; font-weight: 800; letter-spacing: .14em; color: var(--red); }
.stage h3 { font-size: 17px; font-weight: 800; margin: 8px 0 10px; text-transform: uppercase; }
.stage p { color: var(--grey); font-weight: 300; font-size: 14.5px; }

/* The line that closes the Subjects section. Louder than a footnote,
   because it answers the question the three cards raise: must I buy them
   one at a time? Red, so it reads as the offer it is. */
.covers-all {
  text-align: center; margin-top: clamp(30px, 4vw, 40px);
  font-family: var(--head);
  font-size: clamp(15px, 2.4vw, 20px);
  font-weight: 900; letter-spacing: .05em; text-transform: uppercase;
  color: var(--red);
}

/* ------------------------------------------------------ testimonials */
/* A SLIDER, AS ON techm8.in. The wall was nine cards in a grid; this is the
   same nine, three at a time, moved by transform.

   The card itself is untouched — .quote below keeps every rule it had. All
   that changes is the box it sits in.

   OVERFLOW HIDDEN ON THE WRAP, NOT ON THE TRACK. The track is the thing
   being moved, and a moving element cannot also be the one clipping itself.
   And the wrap needs its own padding, because .quote's shadow is cast
   outside its box: clipped flush to the card, every slide looked like it
   had been cut out with scissors. */
.quotes-wrap { overflow: hidden; margin-top: clamp(34px, 5vw, 52px); padding: 6px; }
.quotes {
  display: flex;
  gap: 22px;
  transform: translateX(0);
  transition: transform .5s cubic-bezier(.4, 0, .2, 1);
}
/* Three to a view. The subtraction is the gap's share: two gaps of 22px
   spread over three cards is 14.667px each, and without it the third card
   is pushed out of the view by exactly that much. */
.quotes > .quote { flex: 0 0 calc(33.3333% - 14.667px); }
@media (max-width: 1000px) { .quotes > .quote { flex: 0 0 calc(50% - 11px); } }
@media (max-width: 640px)  { .quotes > .quote { flex: 0 0 100%; } }

/* An arrow, a dot per page, an arrow. */
.testi-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 26px;
}
.tc-btn {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: #FFF;
  color: var(--navy);
  cursor: pointer;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.tc-btn:hover:not(:disabled) { background: var(--navy); border-color: var(--navy); color: #FFF; }
/* AT THE END OF THE ROW THE ARROW IS SPENT, and it says so rather than
   going on looking pressable and doing nothing. */
.tc-btn:disabled { opacity: .35; cursor: not-allowed; }
.tc-btn svg { width: 18px; height: 18px; }
.tc-dots { display: flex; align-items: center; gap: 8px; }
/* A DOT IS 8px AND A THUMB IS NOT.
   These were 8px square, which is what a mouse needs and a third of what a
   finger needs — the guidance is 24px, and on a phone a miss here jumps you
   to the wrong testimonial rather than doing nothing. The DRAWING stays 8px;
   the dot sits inside a 24px box that is invisible and catches the tap.
   ::before rather than padding, because padding would grow the dot itself. */
.tc-dot {
  position: relative;
  width: 8px; height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(27,47,94,.22);
  cursor: pointer;
  transition: background var(--ease), width var(--ease);
}
.tc-dot::before {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 24px; height: 24px;
  transform: translate(-50%, -50%);
}
.tc-dot.active { background: var(--red); width: 24px; border-radius: 999px; }
.quote {
  position: relative; overflow: hidden;
  background: #FFF; border: 1px solid rgba(27,47,94,.07); border-radius: var(--r);
  box-shadow: var(--shadow); padding: 30px 26px 24px; margin: 0;
  display: flex; flex-direction: column;
}
.quote::before {
  content: "\201C"; position: absolute; top: -34px; left: 6px;
  font-family: var(--head); font-size: 190px; font-weight: 900; line-height: 1;
  color: rgba(212,43,43,.09); pointer-events: none;
}
.quote > * { position: relative; }
.quote blockquote { margin: 0 0 15px; font-size: 14.5px; font-weight: 300; color: var(--ink-soft); }
.quote figcaption { display: flex; align-items: center; gap: 12px; margin-top: auto; padding-top: 18px; border-top: 1px solid rgba(27,47,94,.08); }
.quote img { width: 46px; height: 46px; border-radius: 50%; object-fit: cover; flex: none; background: var(--offwhite); }
.quote-name { font-family: var(--head); font-weight: 800; font-size: 14.5px; color: var(--navy); line-height: 1.3; }
.quote-city { font-size: 12.5px; color: var(--grey); }

/* ----------------------------------------------------------- pricing */
.plans { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; margin-top: clamp(44px, 5vw, 60px); align-items: center; }
/* TWO cards, when a plan is off the shelf. The three-column grid leaves a
   hole in the row rather than re-centring, and an empty third of the pricing
   section reads as a page that failed to load. Narrowed as well as halved, so
   two cards do not stretch to the width three had.

   INSIDE A min-width QUERY, and that is the whole point of it. Written as a
   bare `.plans.is-two` it carried two classes to the phone rule's one, so it
   won at every width and put two cards side by side on a 375px screen
   whatever the breakpoint below said. A more specific selector does not stop
   being more specific inside a media query; it has to be kept out of the
   range it must not apply to. */
@media (min-width: 881px) {
  .plans.is-two { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 880px; margin-inline: auto; }
}
.plan { background: #FFF; border: 1px solid rgba(27,47,94,.09); border-radius: var(--r); box-shadow: var(--shadow); padding: 32px 28px 28px; display: flex; flex-direction: column; position: relative; }
.plan.is-pick { background: var(--navy-dark); color: #FFF; border-color: var(--navy-dark); box-shadow: var(--shadow-deep); padding: 46px 30px 38px; }
.plan.is-pick h3 { color: #FF8D7A; }
.plan.is-pick .plan-now { color: #FFF; }
.plan.is-pick .plan-was { color: rgba(255,255,255,.45); }
.plan.is-pick .plan-term { color: rgba(255,255,255,.55); }
.plan.is-pick .plan-line { color: rgba(255,255,255,.78); border-bottom-color: rgba(255,255,255,.14); }
.plan.is-pick li { color: rgba(255,255,255,.86); }
@media (max-width: 880px) {
  .plans { grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr)); align-items: stretch; }
  .plan.is-pick { order: -1; padding: 38px 28px 30px; }
}
/* DIWALI CRACKERS on the Topper card, down its right edge.

   DECORATION ONLY, and the three declarations that make it safe are
   pointer-events, z-index and the absence of overflow:hidden on .plan.
   The card holds the one button on this section that takes money; a
   decorative layer that can swallow a tap on it is worse than no decoration.
   It sits UNDER the content, so a long feature line crosses it rather than
   being pushed about by it.

   .plan is already position:relative, and .plan must NOT be given
   overflow:hidden to contain this -- .plan-off hangs 16px above the card's
   top edge and would be cut in half. The artwork is kept inside the box
   instead.

   Colour literals, like the rest of this file: landing.css is the public
   page's own stylesheet and carries no theme tokens. */
/* THE FESTIVAL PANEL on the Topper card: the right-hand band, top to bottom,
   filled rather than sprinkled. The owner drew the area he wanted and it is
   about a third of the card, which is wider than the gutter the text leaves
   -- so this sits BEHIND some words and is masked away before it reaches
   them. See includes/festive.php.

   It stops short of the button: the button is full width and red, and a
   lantern across it is mess rather than decoration.

   No overflow:hidden on .plan to contain it -- .plan-off hangs 16px above
   the card's top edge and would be cut in half -- so the panel carries the
   card's own corner radius on the one corner it touches. */
.festive-art {
  position: absolute;
  top: 0; right: 0; bottom: 92px;
  width: 38%;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  border-top-right-radius: var(--r);
  opacity: .5;
  /* To nothing before it reaches the words. Prefixed as well as plain: the
     unprefixed property is newer than some of the phones this is read on,
     and the fallback for no mask at all is simply the whole panel, which is
     the state this replaced. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 46%, #000 100%);
          mask-image: linear-gradient(to right, transparent 0%, #000 46%, #000 100%);
}
.festive-art svg { display: block; width: 100%; height: 100%; }

/* The palette comes from whatever it is sitting on. Coral and gold on the
   navy card; see style.css for the crimson banners, where coral would
   disappear. */
.plan.is-pick .festive-art { --fa-ink: #FF8D7A; --fa-warm: #FFC56B; }

/* Everything the reader is meant to read sits above it. A positioned element
   paints over a static one, so the card's own content -- which is all static
   -- would otherwise sit UNDER the artwork.

   .plan-off IS EXCLUDED, and leaving it in is what broke the card once: the
   rule set position:relative on every direct child, which overrode the
   badge's own position:absolute and turned a pill hugging its three words
   into a red bar across the whole top of the box. Anything already
   positioned keeps what it had. */
.plan.is-pick > *:not(.festive-art):not(.plan-off) { position: relative; z-index: 1; }

@media (max-width: 880px) {
  /* The card is full width, so the band is a smaller share of it and the
     button sits closer to the foot. */
  .festive-art { width: 42%; bottom: 84px; opacity: .42; }
}
@media (max-width: 340px) {
  /* Narrower than any phone in common use, and the price row really does
     reach across the card below this. */
  .festive-art { display: none; }
}

/* The saving is the loudest thing on the box, so it is set loud. */
/* IT IS ABSOLUTELY POSITIONED AND IT SITS OVER THE HEADING, so it can only
   ever be a few words. "DiwaliFest · 80% OFF" wrapped to two lines on a
   phone and printed itself across "TOPPER PLAN". The campaign's name belongs
   on the card, not in here -- it is already in the term line under the price
   and in the section's opening sentence. */
.plan-off {
  position: absolute; top: -16px; right: 22px; max-width: calc(100% - 44px);
  background: var(--red); color: #FFF;
  font-family: var(--head); font-size: 14px; font-weight: 900; letter-spacing: .08em;
  padding: 8px 18px; border-radius: 999px;
  box-shadow: 0 8px 20px rgba(212,43,43,.35);
}
.plan h3 { font-size: 15.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--red); }
/* WHAT IT COSTS COMES FIRST AND THE OLD PRICE SITS BEHIND IT. Reading
   order is left to right, so the struck-through number was the first
   thing being read — on the one box that is meant to look like value. */
.plan-price { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 13px 0 4px; }
.plan-now { font-family: var(--head); font-weight: 900; font-size: clamp(34px, 4.6vw, 42px); color: var(--navy); line-height: 1; }
.plan-was { font-family: var(--head); font-size: 19px; font-weight: 700; color: var(--grey); text-decoration: line-through; }
.plan-term { font-family: var(--head); font-size: 10.5px; font-weight: 800; letter-spacing: .13em; color: var(--grey); }
.plan-line { color: var(--ink-soft); font-size: 14px; font-weight: 300; margin-top: 13px; padding-bottom: 18px; border-bottom: 1px solid rgba(27,47,94,.08); }
.plan ul { list-style: none; margin: 18px 0 24px; padding: 0; display: grid; gap: 10px; }
.plan li { display: flex; gap: 10px; font-size: 14px; font-weight: 300; color: var(--ink-soft); align-items: flex-start; }
.plan li svg { flex: none; margin-top: 4px; }
.plan li.no { color: var(--grey); }
.plan.is-pick li.no { color: rgba(255,255,255,.45); }
.plan .btn { width: 100%; margin-top: auto; }

/* --------------------------------------------------------- dark CTA */
.cta { background: var(--navy-dark); color: #FFF; position: relative; overflow: hidden; }
.cta::before { content: ""; position: absolute; inset: 0; background-image: radial-gradient(rgba(255,255,255,.09) 1px, transparent 1px); background-size: 26px 26px; mask-image: radial-gradient(90% 100% at 75% 40%, #000 30%, transparent 75%); }
.cta-in { position: relative; text-align: center; }
.cta h2 { color: #FFF; font-weight: 900; font-size: clamp(23px, 4.3vw, 44px); margin: 18px 0 14px; text-transform: uppercase; }
.cta p { color: rgba(255,255,255,.68); font-weight: 300; max-width: 58ch; margin-inline: auto; }
.cta-acts { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 30px; }

/* ----------------------------------------------------------- footer */
.foot { background: var(--navy); color: rgba(255,255,255,.62); font-size: 13px; font-weight: 300; }
.foot-in { display: flex; flex-wrap: wrap; gap: 12px 26px; align-items: center; padding-block: 26px; }
.foot a:hover { color: #FFF; }
.foot-links { margin-left: auto; display: flex; flex-wrap: wrap; gap: 18px; }
@media (max-width: 640px) { .foot-links { margin-left: 0; } }


/* =====================================================================
   THE THREE THINGS THE LIVE PAGE HAS AND THE DEMO HAS NOT.
   ---------------------------------------------------------------------
   Everything above this line is the design as delivered. These three are
   here because the live page carries real data and real behaviour where
   the demo carried a picture of them, and each is built out of the
   design's OWN parts rather than invented beside it.
   ===================================================================== */

/* 1. THE SUBJECT SWITCH over the blueprint. The demo draws one table,
      Mathematics; the product has a real blueprint per subject, so the
      table needs a way to change. It is the demo's own .chip, so it
      belongs to the design rather than sitting next to it. */
.bp-switch { margin-top: 0; margin-bottom: 18px; }
.bp-switch .chip { cursor: pointer; font-family: var(--head); font-weight: 700; }

/* 2. WHAT THE METER IS COUNTING. The demo's meter states a total; the
      live one is added up from chapters somebody has just picked, and a
      number with no working is a number you cannot check. Same rows as
      the blueprint table above it, quieter. */
.meter-list:not(:empty) { margin-top: 16px; border-top: 1px solid var(--line); }
.meter-row {
  display: flex; gap: 12px; align-items: baseline;
  padding: 8px 0; font-size: 13.5px; color: var(--ink-soft);
  border-bottom: 1px solid rgba(27,47,94,.07);
}
.meter-row:last-child { border-bottom: 0; }
.meter-row span:last-child {
  margin-left: auto; font-family: var(--head); font-weight: 700;
  color: var(--navy); font-variant-numeric: tabular-nums;
}

/* 3. A FACE THAT HAS NOT ARRIVED YET. Every testimonial has a photograph
      today; this is what holds the shape if one is ever added without
      one, so the row does not collapse round a missing image. */
.quote-slot {
  width: 46px; height: 46px; flex: none; border-radius: 50%;
  background: var(--offwhite); display: grid; place-items: center;
  font-family: var(--head); font-weight: 800; font-size: 17px; color: var(--grey);
}

/* The chips are BUTTONS on the live page, where the demo drew them as
   spans. A button brings its own font, its own padding and a border the
   demo's chip does not want. */
button.chip {
  font-family: var(--body);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}





/* ================================================= the festival, in the margins
   The two empty gutters either side of the content, lit and moving the whole
   way down the page: lanterns swinging on their cords with flames that will
   not sit still, crackers arriving from different directions and opening into
   wide ray-bursts, a diya at the foot, a toran across the top. Printed by
   index.php only while a campaign is running.

   IT IS FIXED, AND THE GUTTER IS WHY IT CAN BE. The content column is --max
   and centred, so a wide screen carries a couple of hundred empty pixels
   either side. The rails sit in exactly that space, so they stay in view as
   the page scrolls past and they never cross a word.

   THE WIDTH IS A PERCENTAGE AND NOT 100vw. 100vw INCLUDES the scrollbar while
   the centred column is laid out in the width without it, so a rail sized from
   100vw ate 15 of the 10 pixels meant to separate it from the content and left
   2.5. A percentage on a fixed element resolves against the viewport WITHOUT
   the scrollbar -- the same width the column was centred in. Measured: 10px.

   THE TWO RAILS ARE NOT MIRROR IMAGES, AND THEY WERE. The right one used to be
   the left one under scaleX(-1), which saved a second set of coordinates and
   made the page perfectly symmetrical -- which the owner saw at once. There
   are four ARRANGEMENTS at the foot of this block and festiveRails() deals a
   different one to each rail on every page load.

   THEY CROSS TWO GROUNDS AND HAVE TO READ ON BOTH. The hero is --navy-dark and
   everything below it is white. The first drawing was pale gold at low
   opacity, right for the dark half and invisible on the white sections that
   are most of the page. Everything is at full strength now.

   EVERYTHING ANIMATED IS transform OR opacity: composited, no layout.
   -------------------------------------------------------------------- */
.fest-rail {
  position: fixed; top: 0; bottom: 0; z-index: 5;
  width: calc((100% - var(--max)) / 2 - 10px);
  overflow: hidden; pointer-events: none;
}
.fest-rail--l { left: 0; }
.fest-rail--r { right: 0; }
.fest-rail span { position: absolute; display: block; }


/* --- lanterns ---------------------------------------------------------- */
/* The artwork is SVG (includes/festive.php). It replaced a border-radius
   barrel with a gradient for ribs, which the owner called primitive and was.
   Every colour is set HERE rather than as a fill attribute, so the drawing is
   themed by the page tokens and the flame can be animated by the stylesheet. */
.fest-lamps { inset: 0; }
.fest-lamp  { width: 0; height: 0; }
.fest-lamp-art {
  position: absolute; left: -24px; top: 0; width: 48px; height: 92px;
  /* max-width:none BECAUSE OF THE RESET. Line 102 sets `img, svg {
     max-width: 100% }`, and the parent is a zero-width POINT, so 100% of
     it is 0 and the whole lantern was clamped out of existence -- the
     element was there, positioned correctly, measuring 0px wide. */
  max-width: none; overflow: visible;
  transform-origin: 50% 11px;             /* it swings from the ring, not itself */
  animation: fest-sway 5.6s ease-in-out infinite;
}
.fest-ink-l  { stroke: var(--fest-line); stroke-width: 1.5; opacity: .62; fill: none; }
.fest-cap    { fill: var(--fest-line); opacity: .85; }
.fest-shell  { fill: rgba(232, 163, 63, .16); stroke: var(--fest-line); stroke-width: 1.7; }
.fest-glow   { fill: rgba(232, 163, 63, .34); }
.fest-rib    { stroke: var(--fest-line); stroke-width: 1; opacity: .4; fill: none; }
.fest-tassel { stroke: var(--fest-line); stroke-width: 1.4; opacity: .6; stroke-linecap: round; fill: none; }
.fest-lamp-flame {
  fill: var(--fest-flame);
  filter: blur(2.5px);
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: fest-flicker 1.9s ease-in-out infinite;
}

/* --- the diya ---------------------------------------------------------- */
/* ONE DIYA IN EACH BOTTOM CORNER OF THE PAGE, by the owner. This is the one
   piece of the festival that is NOT part of the random arrangement: a diya
   is put down in a corner, not scattered, and two of them placed
   symmetrically is the point rather than a thing to avoid. The left rail
   hangs its own from the left edge and the right rail from the right, so
   they sit at the outer corners of the window rather than both drifting
   towards the middle. 54px was set by eye: 34 is half the drawing plus a
   margin, which put them hard against the window edge, and both were then
   moved 20px inwards from there. */
.fest-diya { width: 0; height: 0; }
.fest-rail--l .fest-diya { left: 54px; bottom: 30px; }
.fest-rail--r .fest-diya { left: auto; right: 54px; bottom: 30px; }
.fest-diya-art {
  /* 52x40, down from 68x52 at the owner's eye. The viewBox is unchanged,
     so the drawing is scaled rather than redrawn. */
  position: absolute; left: -26px; top: -40px; width: 52px; height: 40px;
  max-width: none; overflow: visible;   /* see the lantern, same reason */
}
.fest-halo { fill: rgba(232, 163, 63, .22); }
.fest-wick { stroke: var(--fest-line); stroke-width: 2; opacity: .8; }
.fest-clay { fill: var(--fest-clay); }
.fest-clay-rim {
  stroke: var(--fest-clay-lo); stroke-width: 2.4; fill: none; stroke-linecap: round;
}
.fest-clay-foot { fill: var(--fest-clay-lo); }
.fest-diya-flame {
  fill: var(--fest-flame);
  filter: blur(1px);
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: fest-flicker 1.4s ease-in-out infinite;
}
.fest-diya-core {
  fill: var(--fest-flame-hot);
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: fest-flicker 1.1s ease-in-out infinite;
}

/* --- crackers ---------------------------------------------------------- */
/* A .fest-shot is a POINT, at the apex. The trail flies TO it and the burst
   happens AT it, so one coordinate places both.

   THEY ARRIVE FROM DIFFERENT DIRECTIONS, which is what --sx and --sy are for.
   The first version sent all of them straight up, and identical vertical
   streaks read as a machine rather than a festival. Each shot names where it
   starts relative to its own apex, and --rot leans the streak along the line
   it is travelling -- a vertical streak moving diagonally looks like a falling
   stick. ONE keyframe serves all of them: the custom properties resolve per
   element before anything interpolates, so a keyframe block per direction
   would just be copies to keep in step (convention 19k).

   THE RAYS ARE A CONIC GRADIENT, AND THAT IS THE WHOLE TRICK. The burst wanted
   is thirty-odd fine tapering rays; a span per ray is some 200 separately
   animated elements on a page that also has to scroll. repeating-conic-
   gradient draws the entire fan in ONE element, and the radial mask is what
   turns a wheel of wedges into rays: it hollows the middle so they do not meet
   in a disc, and fades the ends so they taper instead of stopping dead. */
.fest-shots { inset: 0; }
.fest-shot  { width: 0; height: 0; }

.fest-trail {
  left: -1.5px; top: -26px; width: 3px; height: 52px; border-radius: 2px;
  background: linear-gradient(to top, transparent, var(--fest-flame));
  opacity: 0; transform-origin: 50% 100%;
  animation: fest-climb 6.5s cubic-bezier(.15, .55, .3, 1) infinite;
  animation-delay: var(--d);
}
.fest-burst { left: 0; top: 0; width: 0; height: 0; }
/* the white-hot bloom at the instant it opens */
.fest-burst::after {
  content: ""; position: absolute; left: -13px; top: -13px;
  width: 26px; height: 26px; border-radius: 50%;
  background: radial-gradient(circle, #FFF 0%, var(--fest-flame) 42%, transparent 72%);
  opacity: 0;
  animation: fest-flash 6.5s cubic-bezier(.1, .8, .3, 1) infinite;
  animation-delay: var(--d);
}
.fest-rays {
  left: 0; top: 0; width: 230px; height: 230px; margin: -115px 0 0 -115px;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
              currentColor 0deg 0.75deg, transparent 0.75deg 11.25deg);
  -webkit-mask-image: radial-gradient(closest-side,
              transparent 5%, #000 17%, #000 46%, transparent 94%);
          mask-image: radial-gradient(closest-side,
              transparent 5%, #000 17%, #000 46%, transparent 94%);
  opacity: 0;
  animation: fest-fan 6.5s cubic-bezier(.08, .72, .28, 1) infinite;
  animation-delay: var(--d);
}
/* the inner, denser, shorter fan -- offset half a wedge so its rays fall
   between the long ones rather than on top of them */
.fest-rays--b {
  width: 150px; height: 150px; margin: -75px 0 0 -75px;
  background: repeating-conic-gradient(from 3.75deg,
              currentColor 0deg 0.6deg, transparent 0.6deg 7.5deg);
}
/* the loose sparkles that drift on past the edge of the fan */
.fest-spark { left: 0; top: 0; width: 0; height: 0; }
.fest-spark:nth-child(3)  { transform: rotate(22deg);  --tr: 126px; }
.fest-spark:nth-child(4)  { transform: rotate(74deg);  --tr:  98px; }
.fest-spark:nth-child(5)  { transform: rotate(121deg); --tr: 134px; }
.fest-spark:nth-child(6)  { transform: rotate(168deg); --tr: 104px; }
.fest-spark:nth-child(7)  { transform: rotate(206deg); --tr: 130px; }
.fest-spark:nth-child(8)  { transform: rotate(254deg); --tr:  94px; }
.fest-spark:nth-child(9)  { transform: rotate(299deg); --tr: 138px; }
.fest-spark:nth-child(10) { transform: rotate(338deg); --tr: 110px; }
.fest-spark::after {
  content: ""; position: absolute; left: -3px; top: -3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 9px 3px currentColor;
  opacity: 0;
  animation: fest-fly 6.5s cubic-bezier(.08, .72, .28, 1) infinite;
  animation-delay: var(--d);
}

/* --- the four arrangements --------------------------------------------- */
/* Each rail is dealt one of these at random by festiveRails(), and the two are
   always different, so the margins are neither a mirror of each other nor the
   same on the next visit. An arrangement moves the two lanterns, the three
   crackers, the diya, and the speed of the flames and the toran -- the last of
   those matters as much as the positions, because two rails whose flames
   flicker on the same beat still read as one thing drawn twice. */
.fest-rail--a1 .fest-lamps > .fest-lamp:nth-child(1) { left: 28%; top:  8%; }
.fest-rail--a1 .fest-lamps > .fest-lamp:nth-child(2) { left: 70%; top: 33%; }
.fest-rail--a1 .fest-shots > .fest-shot:nth-child(1) { --d: 0.3s; --sx: -70px; --sy: 210px; --rot: 18deg; left: 46%; top: 23%; color: var(--fest-c1); }
.fest-rail--a1 .fest-shots > .fest-shot:nth-child(2) { --d: 1.9s; --sx: 110px; --sy: 170px; --rot: -30deg; left: 32%; top: 52%; color: var(--fest-c2); }
.fest-rail--a1 .fest-shots > .fest-shot:nth-child(3) { --d: 3.5s; --sx: -120px; --sy: 130px; --rot: 42deg; left: 66%; top: 74%; color: var(--fest-c6); }
.fest-rail--a1 .fest-shots > .fest-shot:nth-child(4) { --d: 5.1s; --sx: 78px; --sy: 198px; --rot: -21deg; left: 52%; top: 38%; color: var(--fest-c3); }

.fest-rail--a2 .fest-lamps > .fest-lamp:nth-child(1) { left: 66%; top: 13%; }
.fest-rail--a2 .fest-lamps > .fest-lamp:nth-child(2) { left: 24%; top: 41%; }
.fest-rail--a2 .fest-shots > .fest-shot:nth-child(1) { --d: 0.9s; --sx: 124px; --sy: 150px; --rot: -36deg; left: 30%; top: 30%; color: var(--fest-c3); }
.fest-rail--a2 .fest-shots > .fest-shot:nth-child(2) { --d: 2.5s; --sx: -96px; --sy: 196px; --rot: 24deg; left: 62%; top: 60%; color: var(--fest-c1); }
.fest-rail--a2 .fest-shots > .fest-shot:nth-child(3) { --d: 4.1s; --sx: 64px; --sy: 224px; --rot: -15deg; left: 40%; top: 80%; color: var(--fest-c5); }
.fest-rail--a2 .fest-shots > .fest-shot:nth-child(4) { --d: 5.7s; --sx: -110px; --sy: 140px; --rot: 39deg; left: 70%; top: 18%; color: var(--fest-c2); }
.fest-rail--a2 .fest-lamp-art    { animation-duration: 6.4s; }
.fest-rail--a2 .fest-lamp-flame  { animation-duration: 2.3s; }

.fest-rail--a3 .fest-lamps > .fest-lamp:nth-child(1) { left: 38%; top: 18%; }
.fest-rail--a3 .fest-lamps > .fest-lamp:nth-child(2) { left: 76%; top: 48%; }
.fest-rail--a3 .fest-shots > .fest-shot:nth-child(1) { --d: 0.6s; --sx: -134px; --sy: 110px; --rot: 50deg; left: 72%; top: 36%; color: var(--fest-c4); }
.fest-rail--a3 .fest-shots > .fest-shot:nth-child(2) { --d: 2.2s; --sx: 80px; --sy: 206px; --rot: -20deg; left: 36%; top: 66%; color: var(--fest-c6); }
.fest-rail--a3 .fest-shots > .fest-shot:nth-child(3) { --d: 3.8s; --sx: -58px; --sy: 228px; --rot: 13deg; left: 56%; top: 16%; color: var(--fest-c2); }
.fest-rail--a3 .fest-shots > .fest-shot:nth-child(4) { --d: 5.4s; --sx: 118px; --sy: 158px; --rot: -33deg; left: 28%; top: 50%; color: var(--fest-c1); }
.fest-rail--a3 .fest-lamp-art    { animation-duration: 4.8s; }
.fest-rail--a3 .fest-lamp-flame  { animation-duration: 1.6s; }

.fest-rail--a4 .fest-lamps > .fest-lamp:nth-child(1) { left: 72%; top:  6%; }
.fest-rail--a4 .fest-lamps > .fest-lamp:nth-child(2) { left: 30%; top: 27%; }
.fest-rail--a4 .fest-shots > .fest-shot:nth-child(1) { --d: 1.2s; --sx: 98px; --sy: 182px; --rot: -28deg; left: 34%; top: 44%; color: var(--fest-c5); }
.fest-rail--a4 .fest-shots > .fest-shot:nth-child(2) { --d: 2.8s; --sx: -112px; --sy: 142px; --rot: 38deg; left: 68%; top: 20%; color: var(--fest-c3); }
.fest-rail--a4 .fest-shots > .fest-shot:nth-child(3) { --d: 4.4s; --sx: 52px; --sy: 232px; --rot: -11deg; left: 48%; top: 70%; color: var(--fest-c1); }
.fest-rail--a4 .fest-shots > .fest-shot:nth-child(4) { --d: 6.0s; --sx: -88px; --sy: 190px; --rot: 26deg; left: 60%; top: 86%; color: var(--fest-c6); }
.fest-rail--a4 .fest-lamp-art    { animation-duration: 7.1s; }
.fest-rail--a4 .fest-lamp-flame  { animation-duration: 2.9s; }

/* Within one rail the second lantern is always a beat off the first, whatever
   arrangement it was dealt. */
.fest-lamps > .fest-lamp:nth-child(2) .fest-lamp-flame { animation-delay: .7s; }
.fest-lamps > .fest-lamp:nth-child(2) .fest-lamp-art   { animation-delay: 1.3s; }

/* --- the keyframes ----------------------------------------------------- */
/* The cycle is 6.5s and the whole event occupies its first third: the climb
   to 13%, the flash at 13, the fan open and gone by 40 -- about 2.6s of
   visible cracker, then a pause. It was 9s with three crackers a rail and
   the owner asked for more; it is four crackers at 6.5s now, staggered 1.6s
   apart within a rail and offset between the two, so bursts land somewhere
   on the page roughly every eight tenths of a second. Shortening the cycle
   alone would have made each burst FASTER rather than more frequent, which
   is not the same thing. Measured at a real instant after the first pass:
   the LAUNCH RATE had nearly doubled, 0.67 bursts a second to 1.23, while
   the number lit AT ONCE had barely moved -- a third of a shorter cycle is
   less time on screen, so each burst was also gone sooner. Two dials, not
   one. The fan runs to 40% instead of 34 now, which is the dwell, and that
   takes the average lit at once from about 1.3 to about 2.2. */
@keyframes fest-climb {
  0%        { transform: translate(var(--sx), var(--sy)) rotate(var(--rot)); opacity: 0; }
  1%        { opacity: .95; }
  11%       { transform: translate(0, 0) rotate(var(--rot));                 opacity: .95; }
  13%, 100% { transform: translate(0, 0) rotate(var(--rot));                 opacity: 0; }
}
@keyframes fest-flash {
  0%, 11%   { transform: scale(.2);  opacity: 0; }
  13%       { transform: scale(1);   opacity: .95; }
  22%, 100% { transform: scale(3.4); opacity: 0; }
}
@keyframes fest-fan {
  0%, 11%   { transform: scale(.08) rotate(0deg);   opacity: 0; }
  13%       { transform: scale(.30) rotate(2deg);   opacity: 1; }
  24%       { transform: scale(.92) rotate(7deg);   opacity: .95; }
  40%, 100% { transform: scale(1.06) rotate(10deg); opacity: 0; }
}
@keyframes fest-fly {
  0%, 12%   { transform: translateY(0); opacity: 0; }
  14%       { transform: translateY(-12px); opacity: 1; }
  40%, 100% { transform: translateY(calc(var(--tr) * -1)); opacity: 0; }
}
@keyframes fest-sway {
  0%, 100% { transform: rotate(-2.8deg); }
  50%      { transform: rotate(2.8deg); }
}
@keyframes fest-flicker {
  0%, 100% { opacity: .62; transform: scale(1); }
  12%      { opacity: .98; transform: scale(1.16); }
  26%      { opacity: .58; transform: scale(.93); }
  41%      { opacity: .92; transform: scale(1.10); }
  58%      { opacity: .70; transform: scale(1.00); }
  73%      { opacity: 1;   transform: scale(1.20); }
  88%      { opacity: .66; transform: scale(.96); }
}

/* THE RAILS GO THE MOMENT THERE IS NO GUTTER TO PUT THEM IN. At --max plus
   220 the gutter is 110px a side, the narrowest that holds a lantern without
   it touching the content. Below that there is no empty margin at all. */
@media (max-width: 1400px) { .fest-rail { display: none; } }

/* SOMEBODY WHO HAS ASKED FOR LESS MOTION GETS THE FESTIVAL WITHOUT THE
   MOVEMENT, not a blank margin: the lanterns, the toran and the diya stay, lit
   and still, and only the crackers go. A firework that cannot move is a smudge
   rather than a decoration. */
@media (prefers-reduced-motion: reduce) {
  .fest-shots { display: none; }
  .fest-bulb, .fest-lamp-art, .fest-lamp-flame,
  .fest-diya-flame, .fest-diya-core { animation: none; }
  .fest-lamp-flame, .fest-diya-flame, .fest-diya-core { opacity: .9; }
}

/* --- the garland across the top of the hero ---------------------------- */
/* SIX SWAGS, SEVEN BULBS EACH, at the owner's asking. It replaced a single
   sagging line of seven drops inside one 233px gutter, which is all a rail can
   hold -- a rail only owns the empty margin beside the content.

   IT IS NOT FIXED, UNLIKE EVERYTHING ELSE IN THE FESTIVAL, and that is the one
   judgement here. The rails can follow the reader down the page because they
   live where nothing else does. A full-width strip has no such guarantee:
   pinned under the top bar it would hang across every heading on the way down.
   So it is strung across the hero and scrolls away with it, which is where a
   garland goes in the real world anyway -- over the doorway, once.

   THE SWAGS SHARE THE WIDTH WITH flex, so there are six of them at any window
   size and each wire is a border-radius arc that stretches with its swag. The
   bulbs sit at fixed PERCENTAGES across a swag and fixed PIXELS down it: the
   arc's height never changes and only its width does, so one set of seven
   offsets is correct at every width. Seven rules, not forty-two. */
.fest-string {
  /* left/right -4%: a string is TILTED below, and a strip exactly as wide
     as the hero lifts one end clear of the corner when it turns. */
  position: absolute; left: -4%; right: -4%; top: calc(var(--bar) - 10px);
  height: 72px; display: flex; z-index: 0; pointer-events: none;
}
/* ONE ROW, AND THE CROSSING HAPPENS INSIDE IT. It was built as two rows at
   different heights, which is not what was asked for: the six strings that
   make up the ONE row are what cross each other.
   Two things do it, and neither is a rotation. The strings OVERLAP
   sideways -- a negative inline margin makes each one wider than its share
   of the row, so it reaches past the point its neighbour hangs from -- and
   every second one hangs a few pixels LOWER, so the two wires meet at an
   angle instead of lying on top of each other. Overlap without the drop
   gives one thick wire; the drop without the overlap gives a zigzag with
   no crossing at all. */
.fest-swag {
  position: relative; flex: 1 1 0; height: 100%;
  margin-inline: -3.6%;
}
.fest-swag:nth-child(even) { transform: translateY(9px); }
/* THE WIRE IS THE CORD OF A REAL LIGHT STRING, not a thin coloured line.
   It was 2px of --fest-line, the same warm red as the lanterns, which read
   as a drawn stroke rather than as something holding bulbs up. It is a
   neutral cord now, pale enough to show against the navy hero and no more,
   so the light comes from the bulbs and not from the wire. */
.fest-swag::before {
  content: ""; position: absolute; left: 0; right: 0; top: 2px; height: 39px;
  border-bottom: 2.5px solid rgba(255, 255, 255, .34);
  border-radius: 0 0 50% 50% / 0 0 100% 100%;
}
/* the collar each bulb is fixed to the cord by */
.fest-bulb::before {
  content: ""; position: absolute; left: 50%; top: -4px;
  width: 5.5px; height: 5px; margin-left: -2.75px; border-radius: 2px 2px 1px 1px;
  background: rgba(255, 255, 255, .42);
}
/* BIGGER BULBS WITH A REAL HALO. The glow is what makes a bulb read as lit
   rather than as a coloured dot, and at 10x15 with a 10px shadow there was
   not enough of either. */
.fest-bulb {
  position: absolute; display: block; width: 10.5px; height: 15px;
  margin-left: -5.25px;
  border-radius: 48% 48% 50% 50% / 32% 32% 68% 68%;
  background: currentColor;
  box-shadow: 0 0 14px 5.5px currentColor;
  animation: fest-twinkle 2.4s ease-in-out infinite;
}
/* EVERY SECOND BULB SITS ABOVE THE CORD INSTEAD OF HANGING BELOW IT, which
   is how a real string looks once it is hung: the bulbs do not all fall the
   same way. Rotating it also carries the collar round, so it still meets
   the cord. */
/* IT IS TWO CUSTOM PROPERTIES AND NOT A transform, because the twinkle
   animation sets transform too -- and an animated transform REPLACES a
   static one outright, so the flipped bulbs would have snapped back down
   the moment they started twinkling. The keyframes compose both. */
.fest-bulb:nth-child(even) { --flip-y: -20px; --flip-r: 180deg; }
/* Seven positions along the droop, reused by every swag on both strings. The tops follow
   the ellipse the wire is drawn with, which is why they are not evenly spaced
   vertically: 22, 34, 39, 41, 39, 34, 22 -- 15 per cent deeper than they
   were, recomputed from the ellipse rather than scaled by eye. */
.fest-bulb:nth-child(1) { left:  7%; top: 22px; }
.fest-bulb:nth-child(2) { left: 21%; top: 34px; }
.fest-bulb:nth-child(3) { left: 35%; top: 39px; }
.fest-bulb:nth-child(4) { left: 50%; top: 41px; }
.fest-bulb:nth-child(5) { left: 65%; top: 39px; }
.fest-bulb:nth-child(6) { left: 79%; top: 34px; }
.fest-bulb:nth-child(7) { left: 93%; top: 22px; }

/* THE COLOURS RUN ACROSS THE WHOLE STRING, NOT WITHIN EACH SWAG. Seven bulbs
   cycling six colours means every swag starts on a different one, so the
   sequence walks along the garland instead of repeating six times -- which is
   what a real string of lights does, and it costs nothing extra. */
.fest-bulb:nth-child(6n + 1) { color: var(--fest-c1); }
.fest-bulb:nth-child(6n + 2) { color: var(--fest-c2); }
.fest-bulb:nth-child(6n + 3) { color: var(--fest-c3); }
.fest-bulb:nth-child(6n + 4) { color: var(--fest-c4); }
.fest-bulb:nth-child(6n + 5) { color: var(--fest-c5); }
.fest-bulb:nth-child(6n + 6) { color: var(--fest-c6); }

/* Staggered so the string ripples along rather than blinking as one slab.
   The stagger is per POSITION and the swags are offset from each other below,
   so no two adjacent bulbs anywhere on the string are in step. */
.fest-bulb:nth-child(1) { animation-delay: 0s;    }
.fest-bulb:nth-child(2) { animation-delay: .18s;  }
.fest-bulb:nth-child(3) { animation-delay: .36s;  }
.fest-bulb:nth-child(4) { animation-delay: .54s;  }
.fest-bulb:nth-child(5) { animation-delay: .72s;  }
.fest-bulb:nth-child(6) { animation-delay: .9s;   }
.fest-bulb:nth-child(7) { animation-delay: 1.08s; }
.fest-swag:nth-child(2) .fest-bulb { animation-duration: 2.9s; }
.fest-swag:nth-child(3) .fest-bulb { animation-duration: 2.1s; }
.fest-swag:nth-child(4) .fest-bulb { animation-duration: 3.2s; }
.fest-swag:nth-child(5) .fest-bulb { animation-duration: 2.6s; }
.fest-swag:nth-child(6) .fest-bulb { animation-duration: 1.9s; }

@keyframes fest-twinkle {
  0%, 100% { opacity: .45; transform: translateY(var(--flip-y, 0px)) rotate(var(--flip-r, 0deg)) scale(.88); }
  45%      { opacity: 1;   transform: translateY(var(--flip-y, 0px)) rotate(var(--flip-r, 0deg)) scale(1.1); }
  70%      { opacity: .62; transform: translateY(var(--flip-y, 0px)) rotate(var(--flip-r, 0deg)) scale(.96); }
}

/* A GARLAND NEEDS ROOM. The first threshold, 760, was guessed and wrong,
   and the second answer -- hide it below 960 -- was wrong in the other
   direction: it left a phone and a tablet with no lights at all, which is
   what the owner found. It shows at every width now, with FEWER STRINGS as
   the window narrows (see the narrow-screen block at the end of this
   file), which keeps the bulbs their proper size instead of crowding them.
   The history, since both earlier numbers came from measuring: The gap between the
   lowest bulb and the eyebrow under it closes as the window narrows, and
   at 760 the string was already sitting on the text -- measured, not
   reasoned about. 960 is not a round number picked to be safe: .hero-in
   collapses its two columns there, and that is what lifts the words into
   the band the swags occupy.
   Re-measured after the swags went 15 per cent deeper and a second string
   was added, both of which eat into that gap, and after the pair moved up
   10px, which gives it back: 23px clear at 1680, 27 at 1280, 17 at 1100,
   10 at 1000 and 7 at 961. Never negative above the breakpoint, and the
   10px rise is what keeps the last of those positive. Measure the LOWEST
   BULB and not the strip: the strips are tilted, so their bounding boxes
   hang 21px below anything actually drawn in them and read as a collision
   that is not there. */

/* ========================================================== the hero deck
   Four slides where there was one: the Diwali offer, the original screen, and
   two for the things a printed sample-paper book cannot do.

   THEY ARE STACKED IN ONE GRID CELL, not positioned absolutely. Every slide
   occupies the same cell, so the deck is as tall as its TALLEST slide and the
   page below never jumps as it turns. Absolutely positioning them would
   collapse the hero to nothing and need a height typed in — a number that goes
   stale the first time a headline is reworded, on the one page a visitor sees
   before they have an account.

   A SLIDE THAT IS OFF IS visibility:hidden AND NOT JUST TRANSPARENT. At
   opacity 0 alone it still takes clicks, still answers to the keyboard, and a
   screen reader still reads all four. visibility:hidden takes it out of the
   tab order and out of the accessibility tree while keeping the space it needs
   to size the deck, which is the whole reason the slides are stacked this way.
   -------------------------------------------------------------------- */
.hero-deck { display: grid; }
.hero-slide {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden;
  transform: translateY(14px);
  transition: opacity .55s var(--ease), transform .55s var(--ease), visibility 0s linear .55s;
}
/* Until main.js marks the deck live, the FIRST slide shows: a reader with a
   blocked script gets the offer rather than an empty navy band. */
.hero-deck:not(.is-live) .hero-slide:first-child {
  opacity: 1; visibility: visible; transform: none;
}
.hero-slide.is-on {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .55s var(--ease), transform .55s var(--ease), visibility 0s;
}

/* --- the dots ---------------------------------------------------------- */
/* BUTTONS, not spans: this is the only control a reader has over the deck and
   a span cannot be tabbed to or pressed. main.js fills the row in, one per
   slide it finds, because the deck is four slides during a campaign and three
   outside one. */
.hero-nav { padding-bottom: clamp(20px, 3vw, 34px); }
/* CENTRED IN THE HERO, at the owner eye. They sat at the left edge of the
   content column, under the headline, which reads as part of the copy
   rather than as a control for the whole deck. */
.hero-dotsrow { display: flex; gap: 10px; align-items: center; justify-content: center; }
.hero-dot {
  width: 30px; height: 5px; border-radius: 999px; border: 0; padding: 0;
  background: rgba(255, 255, 255, .26); cursor: pointer;
  transition: background .3s var(--ease), width .3s var(--ease);
}
.hero-dot:hover { background: rgba(255, 255, 255, .45); }
.hero-dot.is-on { width: 46px; background: var(--red); }
.hero-dot:focus-visible { outline: 2px solid #FFF; outline-offset: 3px; }

/* --- the offer slide --------------------------------------------------- */
.hero-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 14px; margin: 4px 0 2px; }
.hero-price-was {
  font-family: var(--head); font-size: clamp(17px, 2vw, 21px); font-weight: 600;
  color: rgba(255, 255, 255, .5); text-decoration: line-through;
}
.hero-price-now {
  font-family: var(--head); font-size: clamp(38px, 5.4vw, 56px); font-weight: 900;
  color: #FFF; line-height: 1;
}
.hero-price-off {
  font-family: var(--head); font-size: 12px; font-weight: 800; letter-spacing: .1em;
  background: var(--red); color: #FFF; padding: 7px 13px; border-radius: 999px;
}
.hero-code { font-size: 14px; color: rgba(255, 255, 255, .7); margin: 14px 0 0; }
/* The code is the one string a reader has to copy exactly, so it is set apart
   rather than merely bolded. */
.hero-code b {
  font-family: var(--head); font-weight: 800; letter-spacing: .14em; color: #FFF;
  border: 1px dashed rgba(255, 255, 255, .45); border-radius: 7px;
  padding: 4px 10px; margin: 0 2px;
}

/* --- pin icons, as classes --------------------------------------------- */
/* The original hero's two pins carry style="background: …" on their icon,
   which predates house rule 13. New ones use these. */
.pin-ico--navy { background: rgba(27, 47, 94, .10); }
.pin-ico--red  { background: rgba(212, 43, 43, .11); }

/* SOMEBODY WHO HAS ASKED FOR LESS MOTION GETS THE DECK WITHOUT THE CROSSFADE.
   main.js also stops turning it by itself for them — a carousel that advances
   on its own is the thing that setting is mostly about — so the dots become
   the only way through, which is why they are real buttons. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide { transition: none; transform: none; }
  .hero-dot { transition: none; }
}


/* ============================== the festival on a screen with no margins
   The rails beside the content need an empty gutter to live in, and below
   1400px there is not one — so a phone and a tablet had no Diwali on them at
   all, which is what the owner found when he opened the site on his.

   THIS LAYER IS ABSOLUTE INSIDE THE HERO, not fixed. The hero is --navy-dark,
   a thousand pixels tall once it stacks, carries nothing across most of that,
   and is already overflow:hidden. Being absolute it scrolls away with the
   hero rather than hanging over the sections below, which is the thing a
   full-width fixed layer cannot do safely.

   IT PAINTS BEHIND THE WORDS — .hero-in comes after it and both are
   positioned — so the crackers can sit wherever they look best instead of
   being fitted around a headline whose length changes with the slide.

   It reuses the rails' own classes, so a lantern is one lantern and a diya is
   one diya across the site; only the positions below differ.
   -------------------------------------------------------------------- */
.fest-rail.fest-rail--m {
  display: none;
  position: absolute; inset: 0; width: auto; z-index: 0;
}
/* TWO CLASSES IN THE SELECTOR, deliberately: the rule that hides .fest-rail
   below 1400px is also in a media query, so a single-class selector here
   would be a coin toss decided by which rule came last in the file. */
@media (max-width: 1400px) {
  .fest-rail.fest-rail--m { display: block; }
}

/* IN PIXELS AND NOT PER CENT, because a percentage of the hero is a
   different number on a phone and on a tablet -- the hero is a thousand
   pixels tall when it stacks and six hundred when it does not -- and at 1%
   of either the lantern hangs behind the logo. These clear the top bar at
   every width the layer is shown at. */
.fest-rail--m .fest-lamps > .fest-lamp:nth-child(1) { left:  7%; top: 150px; }
.fest-rail--m .fest-lamps > .fest-lamp:nth-child(2) { left: 92%; top: 238px; }

.fest-rail--m .fest-shots > .fest-shot:nth-child(1) { --d: 0.5s; --sx:  -60px; --sy: 180px; --rot:  17deg; left: 18%; top: 26%; color: var(--fest-c1); }
.fest-rail--m .fest-shots > .fest-shot:nth-child(2) { --d: 2.2s; --sx:   90px; --sy: 160px; --rot: -26deg; left: 82%; top: 44%; color: var(--fest-c3); }
.fest-rail--m .fest-shots > .fest-shot:nth-child(3) { --d: 3.9s; --sx: -104px; --sy: 120px; --rot:  40deg; left: 74%; top: 72%; color: var(--fest-c6); }
.fest-rail--m .fest-shots > .fest-shot:nth-child(4) { --d: 5.4s; --sx:   70px; --sy: 190px; --rot: -18deg; left: 24%; top: 60%; color: var(--fest-c2); }

.fest-rail--m .fest-diya--l { left: 12%;  bottom: 4%; }
.fest-rail--m .fest-diya--r { left: auto; right: 12%; bottom: 4%; }

/* A BURST 230px ACROSS IS HALF A PHONE. The rays are sized for a 233px gutter
   on a desktop; at 375px they would cover the headline they sit behind. */
@media (max-width: 620px) {
  .fest-rail--m .fest-rays    { width: 150px; height: 150px; margin: -75px 0 0 -75px; }
  .fest-rail--m .fest-rays--b { width: 100px; height: 100px; margin: -50px 0 0 -50px; }
  .fest-rail--m .fest-lamp-art { width: 36px; height: 69px; left: -18px; }
  .fest-rail--m .fest-diya-art { width: 42px; height: 32px; left: -21px; top: -32px; }
}

/* THE TOP BAR WRAPS TO TWO ROWS BELOW 410px, and the garland was strung
   straight through the Sign in and Signup buttons on it. --bar says 91px
   and is right down to 410, where the bar measures 92; at 390 it measures
   134, because the menu drops under the logo. So --bar is not the height
   of the bar at every width, and a decoration positioned from it has to
   know that. 420 rather than 410 for a margin either side of the flip. */
@media (max-width: 420px) { .fest-string { top: 144px; } }

/* --- the garland, narrow ------------------------------------------------ */
/* IT USED TO GO ENTIRELY BELOW 960px. Six strings across a 375px phone is one
   bulb every nine pixels, so the answer is fewer strings rather than none:
   three below 760 and two below 480, each one correspondingly wider, which
   keeps the bulbs the size they are everywhere else. flex does the rest. */
@media (max-width: 760px) {
  .fest-swag:nth-child(n + 4) { display: none; }
}
@media (max-width: 480px) {
  .fest-swag:nth-child(n + 3) { display: none; }
}

/* THE WORDS PAINT OVER THE DECORATION, AND THEY DID NOT. The garland sat at
   z-index 2 while .hero-in carried none, so a positioned decoration beat the
   positioned content and the lights were drawn ACROSS the headline. Invisible
   on a wide screen, where the garland is above the text and never meets it,
   and obvious the moment the hero stacked on a phone and the two occupied the
   same band. Both decorations sit at 0 now and the content at 1. */
.hero .hero-in { position: relative; z-index: 1; }
/* THE DOTS COME UP 50px, AND THE HERO LOSES 50px WITH THEM. A negative top
   margin does both at once: the row moves up and, being in flow, takes the
   height it vacates with it. The space it is eating is .hero-in own bottom
   padding, which is 100px on a wide screen -- measured 110px between the
   slide buttons and the dots, so 60 is left.
   NOT 50 ONCE THE HERO STACKS, where that padding is smaller and the dots
   would be pulled up into the artwork above them. 960 is where .hero-in
   collapses its two columns, which is what changes the space available --
   measured at 768 with the breakpoint set to 760 and the gap came out at
   11px, which is why it is the layout boundary and not a round number. */
.hero .hero-nav { position: relative; z-index: 1; margin-top: -50px; }
@media (max-width: 960px) { .hero .hero-nav { margin-top: -18px; } }
