/* Nextstep4u v1 — homepage layout.
 *
 * Sits on top of app.css, which owns the tokens, the base reset, the type
 * rules and the motion layer. Nothing here declares a colour: every value is a
 * token, or dark mode breaks silently and nobody notices until a candidate
 * screenshots it.
 *
 * Class names are prefixed `v1-` where a collision with the old stylesheet was
 * possible. The old site is in old/frontend and shares this stylesheet's
 * ancestors, so the prefix is cheap insurance rather than fussiness.
 */

/* ── skip link ──────────────────────────────────────────────────────────── */
/* Off-screen until focused, then a real, visible target. Moved here because it
   was never defined in app.css — the markup referenced a class that did not
   exist, so the link simply sat at the top of the page in plain view. */
.skip{
  position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--page);color:var(--ink);
  border:1px solid var(--line-3);border-radius:var(--r-sm);
  padding:12px 18px;font-weight:600;
}
.skip:focus{left:12px;top:12px;}

/* The `hidden` attribute has to beat a class that sets `display`. Without
   this, `.v1-stats{display:flex}` won on specificity and the empty, unverified
   stats strip still drew its top border under the hero buttons — a stray rule
   across the left column and 72px of space that pushed the headline off centre
   against the report card. Any component hidden by attribute is affected, so
   the reset belongs here rather than on one selector. */
[hidden]{display:none !important;}

/* ── shell ──────────────────────────────────────────────────────────────── */
.v1-wrap{max-width:1200px;margin:0 auto;padding:0 20px;}

/* 16px gutter on a phone, per the design brief. Anything less and the text
   touches the bezel on a 360px screen. */
@media (max-width:560px){ .v1-wrap{padding:0 16px;} }

/* ── the sale bar ───────────────────────────────────────────────────────
   A promotional surface, not part of the product palette. Its red lives here
   and nowhere else in the site: nothing else on the page uses it, so it never
   competes with blue for "this is the action" or with green for "this is
   free". It sits on the same navy as the report card and the footer, which is
   why it reads as a band across the top rather than as a broken component.

   Above the header and not sticky. The header keeps top:0 and slides over it.

   Every colour here is literal rather than a token, deliberately: adding
   --sale-* to the palette would invite the red into the rest of the site.
   Literal does not mean invented, though. These are all approved canvas
   colours. The bar was drafted in a hotter orange-red gradient that exists
   nowhere in the design, and scripts/check-palette.py refused the push --
   which is the check doing exactly its job. The palette holds one red, so the
   badge and the button wear it flat rather than as a gradient between two
   reds that do not exist. (Hex values are deliberately not quoted in this
   comment: the palette check reads comments too, and rightly so.) */
.v1-sale{
  position:relative;overflow:hidden;
  background:linear-gradient(120deg,#171F2C 0%,#1B2431 45%,#3A1F22 100%);
  border-bottom:1px solid rgba(255,255,255,.08);
  color:#fff;
}
/* A slow band of light crossing the bar. Decorative, behind everything, and
   the first thing prefers-reduced-motion switches off. */
.v1-sale-sweep{
  position:absolute;top:0;bottom:0;left:0;width:22%;pointer-events:none;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.10),transparent);
  animation:saleSweep 4.5s ease-in-out infinite;
}
@keyframes saleSweep{
  0%{transform:translateX(-120%);}
  60%,100%{transform:translateX(560%);}
}

.v1-sale-in{
  position:relative;max-width:1200px;margin:0 auto;
  padding:8px 20px;display:flex;align-items:center;gap:14px;min-width:0;
}
.v1-sale-lead{display:flex;align-items:center;gap:12px;min-width:0;flex:1;}

.v1-sale-badge{
  display:inline-flex;align-items:center;gap:6px;flex:none;
  background:#D93843;color:#fff;
  font-size:10px;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;
  padding:5px 9px;border-radius:5px;white-space:nowrap;
  box-shadow:0 2px 12px rgba(217,56,67,.45);
}
.v1-sale-dot{
  width:5px;height:5px;border-radius:50%;background:#fff;
  animation:salePulse 1s ease-in-out infinite;
}
@keyframes salePulse{0%,100%{opacity:1;}50%{opacity:.25;}}

.v1-sale-head{
  font-size:14px;font-weight:700;line-height:1.3;letter-spacing:-.01em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;
}
.v1-sale-seats{
  flex:none;white-space:nowrap;
  font-size:10.5px;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:#FBE49B;
  border-left:1px solid rgba(255,255,255,.14);padding-left:12px;
}

.v1-sale-clock{display:flex;align-items:center;gap:9px;flex:none;}
.v1-sale-ends{
  font-size:10px;font-weight:700;
  letter-spacing:.12em;text-transform:uppercase;color:#FBE49B;
}
.v1-sale-digits{
  display:flex;align-items:center;gap:4px;
  font-variant-numeric:tabular-nums;
  font-size:13.5px;font-weight:700;color:#fff;
}
.v1-sale-unit{
  min-width:28px;padding:4px 7px;text-align:center;border-radius:5px;
  background:rgba(217,56,67,.16);border:1px solid rgba(217,56,67,.45);
}
.v1-sale-sec{animation:saleTick 1s ease-in-out infinite;}
@keyframes saleTick{0%,100%{border-color:rgba(217,56,67,.45);}50%{border-color:rgba(217,56,67,.95);}}
.v1-sale-colon{color:#FBBC04;}
.v1-sale-days[hidden]{display:none;}

.v1-sale-end{display:flex;align-items:center;gap:6px;flex:none;}
.v1-sale-cta{
  display:inline-flex;align-items:center;
  background:#D93843;color:#fff;
  font-size:13px;font-weight:700;padding:9px 16px;border-radius:7px;
  white-space:nowrap;
  animation:saleBreathe 2.2s ease-in-out infinite;
  transition:filter .16s var(--ease);
}
.v1-sale-cta:hover{color:#fff;filter:brightness(1.08);}
@keyframes saleBreathe{
  0%,100%{box-shadow:0 0 0 0 rgba(217,56,67,.45);}
  50%{box-shadow:0 0 0 7px rgba(217,56,67,0);}
}
.v1-sale-x{
  color:#94A0AE;font-size:18px;line-height:1;padding:8px;border-radius:6px;
  transition:color .16s var(--ease),background .16s var(--ease);
}
.v1-sale-x:hover{color:#fff;background:rgba(255,255,255,.1);}

/* On a phone the bar keeps the offer and the button and drops the rest: the
   seat line, the words "Ends in" and the seconds are the first things to go,
   because a 360px screen has room for a claim and a way to act on it. */
@media (max-width:780px){
  .v1-sale-in{padding:7px 16px;gap:10px;}
  .v1-sale-seats,.v1-sale-ends{display:none;}
  /* The claim wraps rather than ellipsing. On one line it ends at "Group 4
     Unlimite...", which loses the price -- and the price is the offer. Two
     lines carry the whole sentence; a third would mean the headline in
     claims.js needs rewriting, not that the bar needs more room. */
  .v1-sale-head{
    font-size:13px;white-space:normal;line-height:1.25;
    display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
  }
  .v1-sale-digits{font-size:12.5px;}
  .v1-sale-unit{min-width:24px;padding:3px 5px;}
  .v1-sale-cta{padding:8px 12px;font-size:12.5px;}
}
@media (max-width:520px){
  /* The badge goes too. It is a label for the offer, and the offer's own
     words begin "Launch offer:" -- so keeping it here cost the claim its
     room and a 390px phone read "Launch offe...", which is the one thing
     this bar exists to say. Dropping the label leaves the sentence whole. */
  .v1-sale-badge{display:none;}
  .v1-sale-clock{display:none;}
}

/* The bar is an offer, not an alarm. Nothing here pulses under reduced
   motion; the countdown still counts, because that is information. */
@media (prefers-reduced-motion:reduce){
  .v1-sale-sweep{display:none;}
  .v1-sale-dot,.v1-sale-sec,.v1-sale-cta{animation:none;}
}

/* ── the fixed top ──────────────────────────────────────────────────────
   The offer bar and the header stick as one block, so neither has to know the
   other's height. */
.v1-top{position:sticky;top:0;z-index:40;}

/* ── header ─────────────────────────────────────────────────────────────── */
/* Positioned, but no longer sticky in its own right — the wrapper above does
   that now. It stays `relative` because the mobile menu is absolutely placed
   against it at top:68px, and would otherwise measure from the top of the
   offer bar and land over the logo. */
.v1-head{
  position:relative;z-index:1;
  background:var(--page);border-bottom:1px solid var(--line);
}
.v1-head-in{display:flex;align-items:center;gap:28px;height:68px;}
.v1-logo{display:flex;align-items:center;gap:8px;font-weight:800;font-size:19px;color:var(--ink);letter-spacing:-.02em;}
.v1-logo-text span{color:var(--blue);}
/* Height-constrained, width auto, so a mark of any aspect ratio drops in
   without redesigning the header. The explicit width/height on the element
   reserves the space so the nav does not jump when it loads. */
.v1-logo-img{height:34px;width:auto;}

.v1-nav{display:flex;align-items:center;gap:26px;margin-left:8px;}
/* The whole nav is green. --green-deep and not the brighter --green-ink,
   because 15px at weight 500 is not large text: green-deep clears AA on white
   at 6.6:1, green-ink sits at 3.8:1 and would fail. The lighter one is the
   hover, where it reads as the link brightening. */
.v1-nav a{color:var(--green-deep);font-weight:500;font-size:15px;}
.v1-nav a:hover{color:var(--green-ink);}

/* Now that every link is green, the current page needs something other than
   colour to mark it — a weight bump and a rule under the word.
   Either value of aria-current is marked the same way: "page" is the tab
   itself, "true" is a page that lives under it, such as one listing under
   Job Openings. A reader wants the same answer from the header in both. */
.v1-nav a[aria-current]{
  color:var(--green-deep);font-weight:700;
  box-shadow:inset 0 -2px 0 0 var(--green);
}

/* Nothing but aria-current is allowed to bold a nav link: a second bold item
   reads as a second current page. The free half is said in the page, not here. */
.v1-head-end{margin-left:auto;display:flex;align-items:center;gap:16px;}

/* ── the exam menu ──────────────────────────────────────────────────────
   One trigger standing in for every exam family. The panel is never removed
   from the DOM: it is hidden with visibility, which keeps it out of the
   accessibility tree and out of the tab order while still letting the open
   and close animate. */
.v1-drop{position:relative;}
.v1-drop-btn{
  display:inline-flex;align-items:center;gap:6px;
  color:var(--green-deep);font-weight:500;font-size:15px;font-family:inherit;
  padding:8px 0;transition:color .14s var(--ease);
}
.v1-drop-btn:hover,.v1-drop.open .v1-drop-btn{color:var(--green-ink);}
.v1-drop-caret{stroke:currentColor;transition:transform .22s var(--ease);}
.v1-drop.open .v1-drop-caret{transform:rotate(180deg);}

.v1-drop-panel{
  position:absolute;top:calc(100% + 12px);left:-16px;z-index:50;
  width:330px;padding:8px;
  background:var(--page);border:1px solid var(--line);
  border-radius:var(--r-lg);box-shadow:var(--shadow-2);
  opacity:0;visibility:hidden;transform:translateY(-8px) scale(.985);
  transform-origin:top left;
  transition:opacity .18s var(--ease),transform .22s var(--ease),visibility .22s;
}
.v1-drop.open .v1-drop-panel{opacity:1;visibility:visible;transform:none;}

/* A small notch tying the panel to the trigger it belongs to. */
.v1-drop-panel::before{
  content:"";position:absolute;top:-5px;left:24px;width:9px;height:9px;
  background:var(--page);border-left:1px solid var(--line);border-top:1px solid var(--line);
  transform:rotate(45deg);border-radius:2px 0 0 0;
}

.v1-drop-item{
  display:block;padding:11px 12px;border-radius:var(--r-md);
  transition:background .16s var(--ease),transform .16s var(--ease);
  /* Each row arrives a beat after the one above it. The delay only applies
     on the way in; closing takes everything with it at once. */
  opacity:0;transform:translateY(-4px);
}
.v1-drop.open .v1-drop-item{
  opacity:1;transform:none;
  transition:opacity .2s var(--ease),transform .2s var(--ease),background .16s var(--ease);
}
.v1-drop.open .v1-drop-item:nth-child(1){transition-delay:.03s;}
.v1-drop.open .v1-drop-item:nth-child(2){transition-delay:.07s;}
.v1-drop.open .v1-drop-item:nth-child(3){transition-delay:.11s;}
/* ── the shine ─────────────────────────────────────────────────────────
   A single sweep across the panel as it opens. It is blue rather than white
   because the panel is already white and a white shine on white is nothing --
   what reads as "shine" on a light surface is a faint tint passing over it,
   not a highlight.

   It runs once per open, not on a loop: the panel is a menu somebody is
   reading three lines of, and something moving in it while they read is a
   thing to wait out rather than a thing to enjoy. pointer-events:none so it
   can never sit between the cursor and a row. */
.v1-drop-panel::after{
  content:'';position:absolute;inset:0;border-radius:inherit;
  pointer-events:none;opacity:0;
  /* The band MOVES AS A BACKGROUND, not as a transform. A transformed overlay
     would slide out past the panel's rounded corners, and the only way to clip
     it -- overflow:hidden on the panel -- would cut off the notch above, which
     is a real part of the design. A background position has nowhere to bleed
     to. */
  background:linear-gradient(105deg,
    rgba(2,101,224,0) 40%,
    rgba(2,101,224,.11) 48%,
    rgba(0,182,150,.10) 54%,
    rgba(2,101,224,0) 62%);
  background-size:300% 100%;
  background-position:100% 0;
}
.v1-drop.open .v1-drop-panel::after{animation:drop-shine .85s var(--ease) .14s 1;}
@keyframes drop-shine{
  0%   {opacity:0; background-position:100% 0;}
  30%  {opacity:1;}
  100% {opacity:0; background-position:0 0;}
}

/* ── a row ─────────────────────────────────────────────────────────────
   A bar that grows from the left, which is the same gesture the top-level
   links use -- so the header behaves one way throughout instead of two. It
   replaces the accidental underline rather than restoring it. */
.v1-drop-item{position:relative;overflow:hidden;}
.v1-drop-item::before{
  content:'';position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;
  background:var(--blue);
  transform:scaleY(0);transform-origin:center;
  transition:transform .2s var(--ease);
}
.v1-drop-item:hover::before,.v1-drop-item:focus-visible::before{transform:scaleY(1);}
.v1-drop-item:hover{background:var(--blue-tint);}
/* The exam being read keeps the bar out, so an opened menu answers "which of
   these am I in" without the reader having to remember. Both values of
   aria-current: "page" is the family's own page, "true" is a page under it
   such as a syllabus or a cut-off. */
.v1-drop-item[aria-current]::before{transform:scaleY(1);}
.v1-drop-item[aria-current]{background:var(--blue-tint);}
.v1-drop-item[aria-current] .v1-drop-name{color:var(--blue);}
/* The row nudges toward the reader rather than only changing colour. */
.v1-drop-item:hover .v1-drop-row,.v1-drop-item:hover .v1-drop-note{transform:translateX(3px);}
.v1-drop-row,.v1-drop-note{transition:transform .2s var(--ease);}
.v1-drop-row{display:flex;align-items:center;gap:9px;}
.v1-drop-name{font-weight:700;font-size:15px;color:var(--ink);letter-spacing:-.01em;}
.v1-drop-item:hover .v1-drop-name{color:var(--blue);}
.v1-drop-note{display:block;margin-top:3px;font-size:12.5px;line-height:1.45;color:var(--ink-3);}

/* Not .v1-chip: that name is already the exam card's count pill further down
   this file, and the later rule would quietly win. */
.v1-drop-chip{
  font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  padding:3px 8px;border-radius:999px;
  background:var(--chip);color:var(--ink-4);
}
.v1-drop-chip-live{
  background:var(--green-bg);color:var(--green-deep);
  position:relative;overflow:hidden;
}
/* The one chip that says something is happening right now, so it is the one
   thing here that keeps moving -- but only while the panel is open, and
   slowly. A badge that shimmers on a page nobody opened is a badge nobody
   sees and a battery nobody agreed to spend. */
.v1-drop-chip-live::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,
    rgba(0,182,150,0) 40%,
    rgba(0,182,150,.55) 50%,
    rgba(0,182,150,0) 60%);
  transform:translateX(-100%);
}
.v1-drop.open .v1-drop-chip-live::after{animation:chip-shine 2.6s var(--ease) .5s infinite;}
@keyframes chip-shine{
  0%   {transform:translateX(-100%);}
  45%  {transform:translateX(100%);}
  100% {transform:translateX(100%);}
}

/* Every control is at least 44px tall. The design brief makes this a rule and
   not a preference — this audience is one-handed on a phone on a bus. */
.v1-btn{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 20px;border-radius:var(--r-md);
  font-weight:600;font-size:15px;white-space:nowrap;
  transition:transform .14s var(--ease),box-shadow .14s var(--ease),background .14s var(--ease);
}
.v1-btn:active{transform:translateY(1px);}
.v1-btn-primary{background:var(--blue-fill);color:#fff;box-shadow:var(--shadow-1);}
.v1-btn-primary:hover{background:var(--blue-deep);color:#fff;box-shadow:var(--shadow-3);}
.v1-btn-ghost{background:var(--page);color:var(--ink);border:1px solid var(--line-3);}
.v1-btn-ghost:hover{color:var(--ink);border-color:var(--ink-4);}
/* Amber is a SURFACE under near-black text, never text and never on dark.
   1.75:1 on white is the reason. */
.v1-btn-amber{background:var(--amber);color:var(--night);}
.v1-btn-amber:hover{color:var(--night);filter:brightness(.96);}
/* Near-black on green, not white. White on #00B696 is 2.58:1 — well under the
   4.5:1 body text has to clear, and this is the primary call to action on the
   WhatsApp band. Near-black takes it to 7.15:1 and keeps the vivid brand green,
   which is the same trade the amber button already makes: a bright brand colour
   is a SURFACE, and the text on it goes dark. */
.v1-btn-green{background:var(--green);color:var(--night);}
.v1-btn-green:hover{color:var(--night);filter:brightness(.95);}
.v1-link{color:var(--ink-2);font-weight:500;font-size:15px;}

/* Login inside the menu. Only exists for the stacked menu at 1100px and
   below, where the header's own .v1-link is hidden -- on wide screens the
   header link is the one people use, and two Logins side by side is noise. */
.v1-nav-login{display:none;}

.v1-burger{display:none;margin-left:auto;padding:10px;border-radius:var(--r-sm);min-height:44px;min-width:44px;}
.v1-burger svg{stroke:var(--ink);}

@media (max-width:900px){
  .v1-nav{
    display:none;position:absolute;top:68px;left:0;right:0;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--page);border-bottom:1px solid var(--line);padding:8px 20px 16px;
  }
  .v1-nav.open{display:flex;}
  .v1-nav > a{padding:13px 0;border-bottom:1px solid var(--line-2);}
  /* A button, not a row: it is the one action a returning candidate opened
     the menu for, so it should not read as just another page link. */
  .v1-nav > a.v1-nav-login{
    display:block;margin-top:14px;padding:13px 0;border:1.5px solid var(--blue);
    border-radius:var(--r-md);text-align:center;font-weight:700;color:var(--blue);
  }

  /* No room to float a panel over a 360px screen, so the exam menu becomes an
     ordinary accordion inside the stacked menu. */
  .v1-drop{border-bottom:1px solid var(--line-2);}
  .v1-drop-btn{width:100%;justify-content:space-between;padding:13px 0;}
  .v1-drop-panel{
    position:static;width:auto;padding:0 0 10px;
    border:0;border-radius:0;box-shadow:none;transform:none;
    display:none;
  }
  .v1-drop-panel::before{display:none;}
  .v1-drop.open .v1-drop-panel{display:block;}
  .v1-drop-item{background:var(--sunk);margin-bottom:6px;}
  .v1-burger{display:inline-flex;align-items:center;justify-content:center;}
  .v1-head-end .v1-link{display:none;}
  /* margin-left:auto stays. Cancelling it here left the button and the burger
     sitting against the logo with the right half of the header empty, which
     reads as a broken header rather than a compact one. */
  .v1-head-end{gap:10px;}
}

/* ── hero ───────────────────────────────────────────────────────────────── */
.v1-hero{background:var(--shell);border-bottom:1px solid var(--line);}
/* WHY padding-top/padding-bottom AND NOT THE SHORTHAND, here and in
   .v1-band-in and .v1-close-in below. Every one of these elements also
   carries .v1-wrap, and .v1-wrap is what puts the 20px -- 16px under 560px --
   side gutter on the page. `padding:72px 0 76px` sets all four sides, so the
   0 in the middle silently deleted that gutter.

   On a wide screen nothing showed, because .v1-wrap is capped at 1200px and
   centred, so there was slack either side anyway. On a phone the wrap is the
   full width of the screen and that gutter is the only thing holding text off
   the glass: the eyebrow, the headline, the Tamil line and both buttons all
   sat flush at x=0 while the header above them and every section below kept
   their 16px. Reported as the mobile alignment being off, and it was -- the
   hero was the one block on the page with no margin at all.

   Longhand keeps the vertical rhythm these rules are actually setting and
   leaves the horizontal padding to the one class whose job it is. */
.v1-hero-grid{
  display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:center;
  padding-top:72px;padding-bottom:76px;
}
.v1-eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  background:var(--green-bg);color:var(--green-deep);
  border:1px solid var(--green-line);border-radius:999px;
  padding:7px 15px;font-size:12px;
  letter-spacing:.08em;text-transform:uppercase;font-weight:600;
}
.v1-h1{
  font-size:clamp(34px,5.2vw,58px);line-height:1.06;letter-spacing:-.035em;
  font-weight:800;margin:22px 0 0;color:var(--ink);
}
.v1-sub{font-size:clamp(16px,1.6vw,18px);color:var(--ink-3);margin-top:20px;max-width:35ch;}
.v1-cta-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:32px;}

.v1-stats{
  display:flex;gap:48px;margin-top:44px;padding-top:28px;
  border-top:1px solid var(--line);
}
.v1-stat-n{
  font-variant-numeric:tabular-nums;
  font-size:30px;font-weight:600;color:var(--ink);letter-spacing:-.02em;
}
.v1-stat-l{font-size:13px;color:var(--ink-4);margin-top:2px;}

@media (max-width:900px){
  .v1-hero-grid{grid-template-columns:1fr;gap:36px;padding-top:44px;padding-bottom:52px;}
  .v1-sub{max-width:none;}
  .v1-stats{gap:28px;flex-wrap:wrap;}
}

/* ── the report card ────────────────────────────────────────────────────── */
/* The single most important object on this page. It is the product, shown
   rather than described, so it is rendered at full fidelity and not as a
   screenshot — a screenshot cannot be read by a screen reader and goes blurry
   on the devices this audience actually holds. */
.v1-report{
  background:var(--night);border-radius:var(--r-xl);padding:28px;
  box-shadow:var(--shadow-2);color:#E6E9ED;
}
/* ── the proof card ─────────────────────────────────────────────────────
   One candidate's score crossing a cut-off. It works because a cut-off is the
   only number in this audience's life that matters: 87 and 118 mean nothing on
   their own, and everything either side of 112. */
.v1-proof-top{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;}
.v1-proof-pill{
  background:var(--green);color:#FFFFFF;border-radius:999px;
  padding:6px 13px;font-size:11px;
  letter-spacing:.09em;text-transform:uppercase;font-weight:600;
  font-variant-numeric:tabular-nums;
}
/* Centred in the card. The pair is the argument — 87 alone means nothing and
   118 alone means nothing — so it sits as one centred object rather than being
   left-aligned against everything else. */
.v1-jump{display:flex;align-items:center;justify-content:center;gap:24px;margin-top:22px;flex-wrap:wrap;}
.v1-jump-col{text-align:center;}
.v1-jump-col .n{
  font-variant-numeric:tabular-nums;
  font-size:46px;line-height:1;font-weight:700;letter-spacing:-.04em;
}
.v1-jump-col .l{font-size:13px;margin-top:8px;}
.v1-jump-from .n{color:#94A0AE;}
.v1-jump-from .l{color:#94A0AE;}
.v1-jump-to   .n{color:var(--amber);font-size:58px;}
.v1-jump-to   .l{color:var(--green);}
/* The arrow is the verb in this card — it is the thing that says "moved". It
   is sized to sit between the two numbers rather than beneath them, and it
   glows, because amber on near-black is the one place on this site where a
   glow reads as light rather than as a effect bolted on.
   Built from --amber-rgb so there is no second copy of the hex to drift. */
.v1-jump-arrow{
  color:var(--amber);
  font-size:40px;
  line-height:1;
  /* Two shadows: a tight one that reads as the glyph being lit, and a wide
     soft one that reads as the light falling on the panel around it. */
  text-shadow:
    0 0 10px rgba(var(--amber-rgb), .55),
    0 0 26px rgba(var(--amber-rgb), .30);
}

/* The bar reads left to right as the journey, with the cut-off marked where it
   actually falls. The marker is a line and a label, not a colour change, so it
   survives both themes and a monochrome screenshot. */
.v1-cut{margin-top:26px;}
.v1-cut-track{
  position:relative;height:12px;border-radius:999px;
  background:var(--night-3);overflow:visible;
}
.v1-cut-fill{
  display:block;height:100%;border-radius:999px;width:0;
  background:linear-gradient(90deg,var(--amber),var(--green));
  transition:width .8s var(--ease);
}
.v1-cut-track.in .v1-cut-fill{width:var(--w);}
.v1-cut-mark{
  position:absolute;top:-4px;bottom:-4px;width:2px;background:#FFFFFF;
  border-radius:2px;
}
.v1-cut-label{
  display:block;margin-top:10px;font-size:13px;color:#E6E9ED;
  font-variant-numeric:tabular-nums;
}

/* ── the review ticker ──────────────────────────────────────────────────
   One review at a time, rising out of the bottom. Only two rows exist in the
   DOM at once regardless of how many reviews there are, so 100 entries cost
   the same as 3.

   The viewport is a fixed height with overflow hidden: without it a longer
   quote would push the amber call to action down the card every few seconds,
   which is the kind of motion that makes a page feel broken rather than
   alive. */
.v1-reviews{
  margin-top:24px;padding-top:22px;border-top:1px solid var(--night-3);
}
.v1-reviews-port{position:relative;height:66px;overflow:hidden;}
.v1-reviews-port .v1-proof-quote{
  position:absolute;inset:0;margin:0;padding:0;border:0;
}
/* Entering from below, leaving upward. */
.v1-rev-enter{animation:rev-in .55s var(--ease) both;}
.v1-rev-exit{animation:rev-out .55s var(--ease) both;}
@keyframes rev-in{from{opacity:0;transform:translateY(100%);}to{opacity:1;transform:none;}}
@keyframes rev-out{from{opacity:1;transform:none;}to{opacity:0;transform:translateY(-100%);}}

/* The label and the count on one line, both 11px on the same 18px line box,
   so they sit on a single optical line rather than being centred against each
   other. */
.v1-reviews-foot{
  display:flex;align-items:center;gap:10px;margin-top:12px;
}
.v1-reviews-label,.v1-reviews-count{
  line-height:18px;height:18px;display:inline-flex;align-items:center;
}
.v1-reviews-label{
  font-size:11px;letter-spacing:.07em;font-weight:600;
  text-transform:uppercase;color:#94A0AE;
}
/* Says what the rows above it are, in the same line as the word "reviews",
   so a crop of this card cannot read as a testimonial. It is the only thing
   standing between a placeholder and a fabricated review -- see the note on
   REVIEWS.samples in assets/reviews.js. */
.v1-reviews-stamp{
  line-height:18px;height:18px;display:inline-flex;align-items:center;
  padding:0 8px;border-radius:999px;
  border:1px solid var(--amber);color:var(--amber);
  font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
}

/* The count keeps the right edge; everything else stays left, against the
   quote it belongs to. */
.v1-reviews-foot .v1-reviews-count{margin-left:auto;}

/* Progress dots would need 100 of them. A count is honest and fits. */
.v1-reviews-count{
  font-variant-numeric:tabular-nums;
  font-size:11px;color:#94A0AE;
}

.v1-proof-quote{
  display:flex;gap:13px;align-items:flex-start;
  margin-top:24px;padding-top:22px;border-top:1px solid var(--night-3);
}
.v1-avatar{
  flex:0 0 auto;width:42px;height:42px;border-radius:999px;
  background:var(--blue-fill);color:#FFFFFF;
  display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:14px;letter-spacing:.02em;
}
.v1-proof-quote blockquote{font-size:14px;color:#E6E9ED;line-height:1.5;}
.v1-proof-quote figcaption{font-size:13px;color:#94A0AE;margin-top:5px;}

@media (max-width:560px){
  .v1-jump{gap:14px;}
  .v1-jump-col .n{font-size:38px;}
  .v1-jump-to .n{font-size:46px;}
}

.v1-report-top{display:flex;justify-content:space-between;align-items:baseline;gap:16px;flex-wrap:wrap;}
.v1-report-kicker{
  font-size:11px;letter-spacing:.1em;font-weight:600;
  text-transform:uppercase;color:#94A0AE;
}
.v1-report-meta{font-size:13px;color:#94A0AE;}
.v1-score-row{display:flex;align-items:baseline;gap:12px;margin-top:14px;}
.v1-score{
  font-variant-numeric:tabular-nums;
  font-size:58px;line-height:1;font-weight:700;color:var(--amber);letter-spacing:-.04em;
}
.v1-score-of{font-size:15px;color:#94A0AE;font-variant-numeric:tabular-nums;}

/* ── a price is not a score ─────────────────────────────────────────────
   --amber is the score colour. On the navy report panel it is the brightest
   thing on the screen and that is the whole point of it.

   The same two classes carry the PRICE on the pricing and TNPSC cards, and
   those cards are white. #FBBC04 on #ffffff is about 1.7:1 against a 4.5:1
   minimum, so the figure a candidate is being asked to pay was the least
   legible number on the page -- and it disappears outright on a phone held
   outdoors, which is where this audience reads. The grey beside it was no
   better at 15px.

   On a light card the price is --ink: the plainest, strongest thing there,
   which is also what you want a price to look like when the page's argument
   is that we would rather say so. It stops competing with the blue button
   for "this is the action", too.

   Scoped by .v1-card rather than given a modifier class because every light
   use of .v1-score sits inside a card and every dark one (.v1-report) does
   not. Amber keeps the deadline note and the warning pill, which is the one
   job it is good at on white. */
.v1-card .v1-score{color:var(--ink);}
.v1-card .v1-score-of{color:var(--ink-3);}

/* ── a launch offer, said three different ways ──────────────────────────
   Three pieces of information, three colours, and none of them borrowed
   from another job:

   the price you pay          --ink       the plainest thing on the card
   the price you are not      --ink-3     struck, smaller, clearly secondary
   what you save              green       green reads as "saving" in India
   when it ends               amber       the one thing amber is good at here

   Red is deliberately absent. On a price it reads as an error or a warning,
   and the sale bar at the top of the page is already the only red on the
   site. Two reds arguing about which one is the offer is worse than one.

   The struck figure is a price this site actually charges and returns to on
   a stated date -- not an invented "original" that nothing was ever sold
   for. That distinction is the whole of the CCPA guidance on reference
   prices, and it is also just the About page's promise applied to a number. */
/* On a 360px screen the struck price and "for 90 days" would push the row
   past the card edge, so the row wraps rather than scrolling sideways. */
@media (max-width:560px){
  .v1-score-row,.v1-price{flex-wrap:wrap;}
  .v1-was{font-size:21px;}
}

.v1-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:22px;}
.v1-tile{background:var(--night-2);border:1px solid var(--night-3);border-radius:var(--r-md);padding:14px 16px;}
.v1-tile-l{font-size:12px;color:#94A0AE;}
.v1-tile-v{
  font-variant-numeric:tabular-nums;
  font-size:23px;font-weight:600;color:#E6E9ED;margin-top:4px;
}

.v1-weak-h{
  font-size:11px;letter-spacing:.1em;font-weight:600;text-transform:uppercase;
  color:#94A0AE;margin:26px 0 14px;
}
.v1-weak{display:flex;flex-direction:column;gap:14px;}
.v1-weak-row{display:grid;grid-template-columns:1fr auto;gap:6px 12px;align-items:baseline;}
.v1-weak-n{font-size:14px;color:#E6E9ED;}
.v1-weak-p{font-variant-numeric:tabular-nums;font-size:14px;font-weight:600;}
.v1-bar{grid-column:1/-1;height:5px;border-radius:999px;background:var(--night-3);overflow:hidden;}
.v1-bar i{display:block;height:100%;border-radius:999px;width:0;transition:width .7s var(--ease);}
.v1-bar.in i{width:var(--w);}
/* The weakest topic's percentage stays near-white rather than red. The only
   red in the approved palette is #D93843, which is about 3.9:1 on this near
   black panel — under the 4.5:1 that text has to clear. The bar carries the
   red instead, where 3:1 is the bar to clear and it passes. The number is
   still the thing that says 34%, so nothing depends on colour alone. */
.is-low .v1-weak-p{color:#E6E9ED;}   .is-low  .v1-bar i{background:#D93843;}
.is-mid .v1-weak-p{color:var(--amber);} .is-mid  .v1-bar i{background:var(--amber);}
.is-good .v1-weak-p{color:#00B696;}  .is-good .v1-bar i{background:#00B696;}

.v1-next{
  display:flex;align-items:center;justify-content:center;gap:8px;
  margin-top:24px;min-height:52px;border-radius:var(--r-md);
  background:var(--amber);color:var(--night);font-weight:700;font-size:15px;
}
.v1-next:hover{color:var(--night);filter:brightness(.96);}

/* The line that stands where the review ticker used to rotate. It says what
   the reader gets, since there is no candidate to quote yet -- and it keeps
   the card the same height either way, so swapping a real review in later
   does not move the button. */
.v1-proof-line{
  margin-top:20px;padding-top:18px;border-top:1px solid var(--night-3);
  font-size:14px;font-weight:600;color:#94A0AE;
}

@media (max-width:560px){
  .v1-report{padding:20px;}
  .v1-score{font-size:46px;}
  .v1-tiles{grid-template-columns:1fr;gap:8px;}
  .v1-tile{display:flex;justify-content:space-between;align-items:baseline;}
  .v1-tile-v{margin-top:0;}
}

/* ── WhatsApp capture band ──────────────────────────────────────────────── */
.v1-band{background:var(--night);color:#E6E9ED;}
.v1-band-in{
  display:grid;grid-template-columns:1fr 1fr;gap:40px;align-items:center;
  padding-top:40px;padding-bottom:40px;
}
.v1-band h2{font-size:clamp(20px,2.4vw,26px);font-weight:700;letter-spacing:-.02em;color:#fff;}
.v1-band p{color:#94A0AE;font-size:15px;margin-top:6px;}
.v1-form{display:flex;gap:10px;}
.v1-field{
  display:flex;align-items:center;flex:1;min-height:52px;
  background:var(--page);border-radius:var(--r-md);padding:0 6px 0 14px;gap:8px;
}
.v1-field span{color:var(--ink-3);font-size:15px;}
.v1-field input{flex:1;min-height:48px;border:0;outline:none;background:none;font-size:15px;}
.v1-ticks{display:flex;flex-wrap:wrap;gap:18px;margin-top:12px;font-size:13px;color:#94A0AE;}
.v1-ticks b{font-weight:500;}

@media (max-width:900px){ .v1-band-in{grid-template-columns:1fr;gap:22px;padding-top:32px;padding-bottom:32px;} }
@media (max-width:480px){ .v1-form{flex-direction:column;} .v1-form .v1-btn{width:100%;} }

/* ── sections ───────────────────────────────────────────────────────────── */
.v1-sec{padding:64px 0;}
.v1-sec-sunk{background:var(--shell);}
.v1-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:20px;margin-bottom:8px;}
.v1-h2{font-size:clamp(24px,3vw,34px);font-weight:800;letter-spacing:-.03em;color:var(--ink);}
.v1-lede{color:var(--ink-3);font-size:15px;margin-top:6px;}
.v1-more{font-weight:600;font-size:15px;white-space:nowrap;}

.v1-grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:28px;}
.v1-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:28px;}
.v1-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:40px;}

@media (max-width:900px){
  .v1-grid-3,.v1-grid-4,.v1-grid-2{grid-template-columns:1fr;gap:16px;}
  .v1-sec{padding:44px 0;}
}

/* Minimal by subtraction: no border at rest, just a hairline shadow to lift the
   card off the panel. The border comes back only on hover, which is when the
   card is actually being considered. Fewer lines on screen at rest means the
   content is what the eye lands on. */
.v1-card{
  background:var(--page);border:1px solid transparent;border-radius:var(--r-lg);
  padding:28px;box-shadow:var(--shadow-1);
  transition:transform .22s var(--ease),box-shadow .22s var(--ease),border-color .22s var(--ease);
}
.v1-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-2);border-color:var(--line);}

/* ── the step cards ─────────────────────────────────────────────────────
   Colour arrives per step rather than being painted over the whole card: a
   tinted numeral and a rule that draws itself across the top as the card
   comes into view. The number is the label, so the colour is decoration and
   never the only thing distinguishing one step from the next. */
.v1-step{position:relative;overflow:hidden;}
.v1-step::before{
  content:'';position:absolute;top:0;left:0;height:3px;width:0;
  background:var(--step, var(--blue-fill));
  transition:width .6s var(--ease);transition-delay:var(--d,0ms);
}
.v1-step.in::before{width:100%;}
.v1-step .n{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:34px;height:26px;padding:0 9px;border-radius:999px;
  font-size:12px;font-weight:600;
  background:var(--step-bg, var(--blue-bg));color:var(--step-ink, var(--blue-deep));
}
.v1-step:nth-child(1){--step:var(--blue-fill);  --step-bg:var(--blue-bg);  --step-ink:var(--blue-deep);}
.v1-step:nth-child(2){--step:var(--amber);      --step-bg:var(--amber-bg); --step-ink:var(--amber-deep);}
.v1-step:nth-child(3){--step:var(--green);      --step-bg:var(--green-bg); --step-ink:var(--green-deep);}
.v1-step:nth-child(4){--step:var(--night);      --step-bg:var(--chip);     --step-ink:var(--ink);}

/* ── the loop band ──────────────────────────────────────────────────────
   The same four steps as a dark band instead of four cards. Two things carry
   it: the numeral, which is the label, and a hairline that runs from each
   numeral to the next one in the reading direction.

   The rule is horizontal and there is no column divider, because the four
   steps are one sequence. A vertical line between them says "four separate
   things"; a line along them says "this, then this". Every connector is the
   same blue at the same weight and the same length — a rule that changes
   colour or texture partway along reads as a state change, and nothing here
   changes state. The last step has no connector: there is nothing after it.

   The line is drawn by clip-path rather than by scaleX, so the dot at each
   end stays a circle instead of being squashed into an ellipse while it
   animates. It is unclipped by default and only hidden once `.js` confirms
   the observer is running, so a JavaScript failure leaves four visible
   connectors rather than none. */
.v1-loop{
  --loop-gap:34px;
  /* The space between a numeral and the hairline beside it. The same value
     is used at both ends of every connector, so a line sits centred in the
     space between two numerals instead of hugging the one it points at. */
  --loop-n-gap:20px;
  /* An optical correction, not a layout value. Centring the hairline on the
     numeral's line box leaves it about two pixels below the middle of the
     digits themselves, because a line box is taller than the figures inside
     it — it carries room for descenders the numerals never use. Two pixels
     is small enough to look like a mistake rather than a style. */
  --loop-link-rise:2px;
  background:var(--night);
  background-image:radial-gradient(900px 340px at 18% -10%, #0E2A4E, transparent 70%);
  color:#E6E9ED;
  padding:76px 0 84px;
}
.v1-loop-head{
  display:flex;justify-content:space-between;align-items:flex-end;
  gap:40px;margin-bottom:52px;
}
.v1-loop-intro{max-width:620px;}
.v1-loop-tag{
  font-size:12px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:#3B86EE;margin-bottom:14px;
}
.v1-loop-h2{
  font-size:clamp(28px,4vw,42px);line-height:1.05;letter-spacing:-.03em;
  font-weight:800;color:#FFFFFF;
}
.v1-loop-lede{margin-top:12px;font-size:17px;line-height:1.5;color:#94A0AE;}
.v1-loop-count{
  font-size:13px;font-weight:500;letter-spacing:.06em;color:#94A0AE;
  white-space:nowrap;padding-bottom:6px;
}

.v1-loop-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--loop-gap);}
.v1-loop-step > h3{font-size:19px;font-weight:700;color:#FFFFFF;margin-bottom:10px;}
.v1-loop-step > p{font-size:15px;line-height:1.6;color:#94A0AE;}
/* The last step is the payoff, so its copy sits a shade brighter than the
   three that describe machinery. */
.v1-loop-last > p{color:#E6E9ED;}

.v1-loop-n{display:flex;align-items:center;gap:var(--loop-n-gap);margin-bottom:28px;}
.v1-loop-n b{
  font-size:52px;line-height:1;font-weight:800;letter-spacing:-.04em;
  color:var(--night-3);
  /* Every numeral occupies the same width, so the three connectors come out
     the same length. Proportional figures make "01" narrower than "02" and
     the lines then differ by a few pixels each — small, but visible when
     they are stacked in a row and meant to read as one sequence. */
  font-variant-numeric:tabular-nums;
}
.v1-loop-last .v1-loop-n b{color:#3B86EE;}

.v1-loop-link{
  position:relative;flex:1;min-width:20px;height:1px;
  background:#3B86EE;opacity:.55;
  /* Reaches across the grid gap so the line arrives at the next numeral
     rather than stopping at the edge of its own column — but stops the same
     distance short of it as it started from its own, which is what keeps the
     run even. Pulling the full gap made every line touch the numeral ahead
     of it while leaving a gap behind, and the row read as misaligned. */
  margin-right:calc(var(--loop-n-gap) - var(--loop-gap));
  top:calc(var(--loop-link-rise) * -1);
}
.v1-loop-link::before,.v1-loop-link::after{
  content:'';position:absolute;top:50%;margin-top:-2.5px;
  width:5px;height:5px;border-radius:50%;background:#3B86EE;
}
.v1-loop-link::before{left:0;}
.v1-loop-link::after{right:0;}

.js .v1-loop-link{
  clip-path:inset(-6px 100% -6px 0);
  transition:clip-path .7s var(--ease);transition-delay:var(--d,0ms);
}
.js .v1-loop-step.in .v1-loop-link{clip-path:inset(-6px 0 -6px 0);}

/* Two across on a tablet, one on a phone. The connector is dropped at both:
   pointing right at the end of a row points at nothing. */
@media (max-width:900px){
  .v1-loop{padding:52px 0 58px;}
  .v1-loop-head{margin-bottom:36px;}
  .v1-loop-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:30px;}
  .v1-loop-link{display:none;}
  .v1-loop-n{margin-bottom:18px;}
  .v1-loop-n b{font-size:42px;}
}
@media (max-width:560px){
  .v1-loop-grid{grid-template-columns:1fr;gap:26px;}
  .v1-loop-head{flex-direction:column;align-items:flex-start;gap:14px;}
  .v1-loop-count{padding-bottom:0;}
}

@media (prefers-reduced-motion:reduce){
  .js .v1-loop-link{clip-path:none;transition:none;}
}

/* ── the exam cards ─────────────────────────────────────────────────────
   Each card carries one accent, declared once as --ac and then used by the
   top edge, the icon tile, the wash and the link. Adding a fourth exam family
   is three lines here, not a new set of rules for every part.

   Colour is decoration throughout. The exam name is the label and the state
   is a word — "Live now", "Coming soon" — so nothing on the card is carried
   by hue alone. */
.v1-exam{
  --ac:var(--line-3); --ac-bg:var(--chip); --ac-line:var(--line); --ac-deep:var(--ink);
  position:relative;overflow:hidden;isolation:isolate;
  display:flex;flex-direction:column;
  border-top:3px solid var(--ac);
}
.v1-exam.a-blue {--ac:var(--blue-fill);--ac-bg:var(--blue-bg); --ac-line:var(--blue-line); --ac-deep:var(--blue-deep);}
.v1-exam.a-amber{--ac:var(--amber);     --ac-bg:var(--amber-bg);--ac-line:var(--amber-line);--ac-deep:var(--amber-deep);}
.v1-exam.a-green{--ac:var(--green);     --ac-bg:var(--green-bg);--ac-line:var(--green-line);--ac-deep:var(--green-deep);}

/* Two layers of accent, both decorative, both under the text.

   The veil is a wash of the card's colour across the whole face, strongest at
   the top corner where the icon and the state pill are, fading out before it
   reaches the blurb. It is what makes the card read as belonging to its
   accent at rest instead of only when a cursor is on it.

   The wash is a soft disc on top of that, which is what gives the corner its
   depth. Both deepen together on hover. */
.v1-exam::after{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(158deg,
      color-mix(in srgb,var(--ac) 9%,transparent) 0%,
      color-mix(in srgb,var(--ac) 3%,transparent) 38%,
      transparent 72%);
  opacity:.85;transition:opacity .3s var(--ease);
}
.v1-exam:hover::after{opacity:1;}

.v1-exam-wash{
  position:absolute;top:-70px;right:-70px;width:220px;height:220px;z-index:-1;
  border-radius:50%;
  background:radial-gradient(circle,var(--ac-bg) 0%,transparent 70%);
  opacity:.8;transform:scale(.92);
  transition:opacity .35s var(--ease),transform .45s var(--ease);
}
.v1-exam:hover .v1-exam-wash{opacity:1;transform:scale(1.15);}

/* The card carries its accent as a halo at rest too, not only under a cursor.
   Kept faint — this is three cards side by side, and three loud halos is a
   traffic light rather than a highlight. */
.v1-exam{
  box-shadow:
    var(--shadow-1),
    0 8px 26px color-mix(in srgb,var(--ac) 11%,transparent);
}
/* The one that is actually on sale gets a little more of it. Colour is not
   carrying the message — "Live now" is written on the card — it is only
   making the eye land there first. */
.v1-exam.is-live{
  box-shadow:
    var(--shadow-1),
    0 10px 32px color-mix(in srgb,var(--ac) 20%,transparent);
}

.v1-exam-head{display:flex;align-items:center;justify-content:space-between;gap:12px;}
.v1-exam-tile{
  display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:13px;
  background:var(--ac-bg);border:1px solid var(--ac-line);
  transition:transform .3s var(--spring),box-shadow .3s var(--ease);
}
.v1-exam-tile svg{stroke:var(--ac-deep);}
.v1-exam:hover .v1-exam-tile{transform:translateY(-2px) rotate(-4deg);box-shadow:var(--shadow-1);}

.v1-exam-state{
  display:inline-flex;align-items:center;gap:6px;
  padding:5px 11px;border-radius:999px;
  background:var(--chip);color:var(--ink-3);
  font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
}
.v1-exam-state.is-live{background:var(--green-bg);color:var(--green-deep);}
.v1-exam-dot{
  width:6px;height:6px;border-radius:50%;background:var(--green);
  box-shadow:0 0 0 0 rgba(0,182,150,.55);animation:pulseDot 2.2s ease-out infinite;
}
@keyframes pulseDot{
  0%{box-shadow:0 0 0 0 rgba(0,182,150,.55);}
  70%{box-shadow:0 0 0 7px rgba(0,182,150,0);}
  100%{box-shadow:0 0 0 0 rgba(0,182,150,0);}
}

.v1-exam-title{
  display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;
  font-size:23px;font-weight:800;letter-spacing:-.025em;margin-top:18px;
  transition:color .2s var(--ease);
}
.v1-exam:hover .v1-exam-title{color:var(--ac-deep);}
/* The full name, so "TNPSC" is not the only way in for someone who does not
   yet know the acronym. */
.v1-exam-name{font-size:13px;font-weight:600;letter-spacing:0;color:var(--ink-4);}
.v1-exam-blurb{color:var(--ink-3);font-size:14px;margin-top:7px;line-height:1.55;}
.v1-exam-line{
  display:inline-flex;align-self:flex-start;align-items:center;
  margin-top:12px;padding:6px 11px;border-radius:var(--r-sm);
  background:var(--sunk);border:1px solid var(--line-2);
  color:var(--ink-2);font-size:12.5px;font-weight:600;line-height:1.4;
}
.v1-exam.is-live .v1-exam-line{background:var(--ac-bg);border-color:var(--ac-line);color:var(--ac-deep);}

.v1-chips{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap;}
/* The catalogue fills this. Until it answers there is nothing in it, and an
   empty row still reserving its margin leaves a hole in the middle of the
   card. */
.v1-chips:empty{display:none;}
.v1-chip{
  font-size:12px;font-variant-numeric:tabular-nums;
  background:var(--chip);color:var(--ink-2);border-radius:999px;padding:5px 11px;
}
.v1-chip-free{background:var(--green-bg);color:var(--green-deep);}

/* Pushed to the bottom edge so three cards of different text lengths still
   line their links up. */
.v1-exam-go{
  display:inline-flex;align-items:center;gap:8px;align-self:flex-start;
  margin-top:auto;padding-top:18px;
  font-weight:700;font-size:15px;min-height:44px;color:var(--ac-deep);
}
.v1-exam-arrow{display:inline-block;transition:transform .22s var(--spring);}
.v1-exam:hover .v1-exam-arrow{transform:translateX(5px);}

/* The link covers the card, so the whole thing is one target on a phone. The
   text stays the accessible name; this only widens where a tap counts. */
.v1-exam-go::after{content:'';position:absolute;inset:0;}

.v1-step h3{font-size:18px;font-weight:700;margin-top:14px;}
.v1-step p{color:var(--ink-3);font-size:14px;margin-top:6px;}

/* ── "What the report changed for them" ─────────────────────────────────
   The gain leads, in green, because a number is what a candidate scans for.
   The quote carries the reason. The attribution sits at the bottom of the
   card via margin-top:auto, so all three align however long the quotes run. */
.v1-voice{display:flex;flex-direction:column;gap:14px;}
.v1-voice-gain{font-weight:700;font-size:15px;color:var(--green-deep);}
.v1-voice-q{font-size:14.5px;line-height:1.7;color:var(--ink-2);text-wrap:pretty;}
.v1-voice-by{display:flex;align-items:center;gap:11px;margin-top:auto;}
.v1-voice-av{
  flex:0 0 auto;width:34px;height:34px;border-radius:999px;
  display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:13px;color:#FFFFFF;background:var(--blue-fill);
}
.v1-voice.a-green .v1-voice-av{background:var(--green);}
.v1-voice.a-night .v1-voice-av{background:var(--night);}
.v1-voice-name{font-weight:600;font-size:13.5px;color:var(--ink);}
.v1-voice-ctx{font-weight:500;font-size:12px;color:var(--ink-4);}

/* On every card while the grid is showing its sample state, so a card that
   travels alone — a screenshot, a crop — still says what it is. */
.v1-voice{position:relative;}
.v1-voice-stamp{
  position:absolute;top:12px;right:12px;
  padding:3px 9px;border-radius:999px;
  background:var(--amber-bg);border:1px solid var(--amber-line);
  color:var(--amber-deep);
  font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
}
.v1-voice-stamp + .v1-voice-gain{padding-right:72px;}

/* Above the grid whenever the cards below it are not real people. */
.v1-sample-note{
  margin-top:14px;padding:10px 14px;border-radius:var(--r-sm);
  background:var(--amber-bg);border:1px solid var(--amber-line);
  color:var(--amber-deep);font-size:13px;font-weight:600;
}

/* ── free strip: current affairs + jobs ─────────────────────────────────── */
.v1-row{
  display:flex;align-items:center;gap:14px;justify-content:space-between;
  background:var(--page);border:1px solid var(--line);border-radius:var(--r-md);
  padding:14px 16px;margin-top:10px;min-height:44px;
}
.v1-row:hover{border-color:var(--line-3);}
.v1-row-kick{
  font-size:11px;letter-spacing:.07em;font-weight:600;text-transform:uppercase;
  color:var(--ink-4);
}
.v1-row-t{font-weight:600;font-size:15px;color:var(--ink);margin-top:3px;}
.v1-row-m{font-size:13px;color:var(--ink-3);margin-top:3px;}
.v1-pill{
  font-size:12px;font-variant-numeric:tabular-nums;
  border-radius:999px;padding:5px 10px;white-space:nowrap;
}
.v1-pill-warn{background:var(--amber-bg);color:var(--amber-deep);}
.v1-pill-soon{background:var(--red-bg);color:var(--red);}
.v1-pill-new{background:var(--green-bg);color:var(--green-deep);}
.v1-badge-free{
  font-size:11px;letter-spacing:.07em;font-weight:600;text-transform:uppercase;
  background:var(--green-bg);color:var(--green-deep);border-radius:999px;padding:5px 11px;
}

/* The empty state is a first-class screen here, not an afterthought: until a
   person publishes a real job or a real article, this is what visitors see. */
.v1-empty{
  border:1px dashed var(--line-3);border-radius:var(--r-md);
  padding:22px;color:var(--ink-3);font-size:14px;background:var(--sunk);margin-top:10px;
}

/* ── closing band and footer ────────────────────────────────────────────── */
.v1-close{background:var(--night);color:#E6E9ED;}
.v1-close-in{display:flex;align-items:center;justify-content:space-between;gap:28px;padding-top:44px;padding-bottom:44px;flex-wrap:wrap;}
.v1-close h2{font-size:clamp(22px,2.8vw,30px);font-weight:800;letter-spacing:-.025em;color:#fff;}
.v1-close p{color:#94A0AE;margin-top:8px;font-size:15px;}

.v1-foot{background:var(--night-2);color:#94A0AE;padding:48px 0 28px;}
.v1-foot-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:32px;}
.v1-foot h4{
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--amber);margin-bottom:14px;font-weight:600;
}
.v1-foot a{display:block;color:#94A0AE;font-size:14px;padding:6px 0;}
.v1-foot a:hover{color:#fff;}
.v1-foot-tag{font-size:14px;margin-top:14px;max-width:30ch;}
.v1-foot-end{
  display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
  border-top:1px solid var(--night-3);margin-top:36px;padding-top:20px;font-size:13px;
}
.v1-foot-end nav{display:flex;gap:20px;flex-wrap:wrap;}

@media (max-width:900px){ .v1-foot-grid{grid-template-columns:1fr 1fr;gap:24px;} }
/* Two columns on a phone, not one. One column plus the 44px tap targets the
   links need made the footer 989px on a 390px screen -- taller than the whole
   viewport, and a sixth of the page, entirely links. The brand line spans
   both so it still reads as the top of the footer rather than a column. */
@media (max-width:560px){
  .v1-foot-grid{grid-template-columns:1fr 1fr;gap:10px 20px;}
  .v1-foot-grid > :first-child{grid-column:1 / -1;margin-bottom:6px;}
}

/* ── motion, end to end ─────────────────────────────────────────────────
   Everything below is transform and opacity only, so none of it triggers
   layout. The whole layer collapses under prefers-reduced-motion at the
   bottom of this file. */

/* Nav: a rule that grows from the left rather than a colour change, so the
   hover reads without the label shifting weight. */
.v1-nav > a{position:relative;}
/* DIRECT CHILDREN ONLY. This used to be `.v1-nav a`, which also matched the
   three rows inside the exams dropdown -- they are anchors, and they are
   inside the nav. Each row grew this underline in the nav's own green, six
   pixels below a row with rounded corners, which read as a stray teal line
   hanging under TNPSC rather than as anything anybody had designed. The
   dropdown has its own hover treatment below. */
.v1-nav > a::after{
  content:'';position:absolute;left:0;right:0;bottom:-6px;height:2px;
  background:currentColor;border-radius:2px;
  transform:scaleX(0);transform-origin:left;
  transition:transform .24s var(--ease);
}
.v1-nav > a:hover::after,.v1-nav > a:focus-visible::after{transform:scaleX(1);}

/* NOT ON THE TAB YOU ARE ALREADY ON. That one already carries a rule -- the
   inset box-shadow up at .v1-nav a[aria-current] -- drawn tight under the
   word. This bar sits six pixels lower, so hovering the current tab drew a
   SECOND green line below the first, and two parallel underlines read as a
   rendering fault rather than as a hover.
   The hover still answers, in colour: [aria-current] pins the link to
   green-deep at equal specificity to the plain :hover rule, so the current
   tab needs its own hover to brighten like every other one. */
.v1-nav > a[aria-current]::after{content:none;}
.v1-nav a[aria-current]:hover{color:var(--green-ink);}

@media (max-width:900px){ .v1-nav > a::after{display:none;} }

/* The mark settles in rather than appearing. 380ms, once, on load. */
.v1-logo-img{animation:logo-in .38s var(--ease) both;}
@keyframes logo-in{from{opacity:0;transform:translateY(-4px) scale(.98);}to{opacity:1;transform:none;}}

/* Buttons lift toward the pointer and press back down. */
.v1-btn-primary:hover,.v1-btn-green:hover,.v1-btn-amber:hover{transform:translateY(-1px);}

/* List rows slide a little on hover, which is cheaper to read than a colour
   change and says "this is a link" on a touch device that has no hover at all
   because the transform also plays on :active. */
.v1-row{transition:transform .2s var(--ease),border-color .2s var(--ease),box-shadow .2s var(--ease);}
.v1-row:hover,.v1-row:active{transform:translateX(3px);box-shadow:var(--shadow-1);}

/* Exam cards lose their resting border too; the coloured top edge is the only
   line they need. */
.v1-exam{border-left:0;border-right:0;border-bottom:0;}
/* The shared card hover repaints every border; the exam card only ever has a
   top one, and it keeps its accent. */
.v1-exam:hover{border-color:transparent;border-top-color:var(--ac);}
/* A deeper, accent-tinted lift than the plain card gets. */
.v1-exam:hover{
  transform:translateY(-5px);
  box-shadow:
    var(--shadow-1),
    0 16px 40px color-mix(in srgb,var(--ac) 30%,transparent);
}

/* The arrow nudges forward and breathes. The two run on different periods —
   1.9s and 2.6s — so they drift against each other instead of locking into
   one mechanical beat. */
.v1-jump-arrow{animation:nudge 1.9s var(--ease) infinite, glow 2.6s ease-in-out infinite;}
@keyframes nudge{0%,62%,100%{transform:translateX(0);}76%{transform:translateX(6px);}}
@keyframes glow{
  0%,100%{
    text-shadow:
      0 0 8px  rgba(var(--amber-rgb), .40),
      0 0 20px rgba(var(--amber-rgb), .20);
  }
  50%{
    text-shadow:
      0 0 16px rgba(var(--amber-rgb), .80),
      0 0 40px rgba(var(--amber-rgb), .45);
  }
}

/* The eyebrow pill breathes once on entry so the eye starts at the top left. */
.v1-eyebrow{animation:pill-in .5s var(--spring) both;}
@keyframes pill-in{from{opacity:0;transform:scale(.92);}to{opacity:1;transform:none;}}

/* ── motion ─────────────────────────────────────────────────────────────── */
/* Nothing is hidden by default in a way that a JavaScript failure could make
   permanent: .rv only becomes invisible once JS has confirmed it is running by
   setting .js on the root. */
.js .rv{opacity:0;transform:translateY(14px);}

/* The hero's entrance, in CSS alone.
 *
 * `.rv` is the scroll reveal: JavaScript adds `.js` to the root, which hides
 * every `.rv`, and an IntersectionObserver adds `.in` when the element is
 * reached. That is right for the sections below the fold and wrong for the
 * one above it -- the hero is already on screen, so the observer buys nothing
 * and the wait for the script costs the first impression on a slow phone.
 *
 * This animation runs from first paint, script or no script. `both` holds the
 * start frame, so the delay is a stagger rather than a flash of finished text
 * that then re-animates. */
.v1-in{animation:v1In .46s var(--ease) both;animation-delay:var(--d,0ms);}
@keyframes v1In{from{opacity:0;transform:translateY(12px);}to{opacity:1;transform:none;}}

@media (prefers-reduced-motion:reduce){
  .v1-in{animation:none;}
}
.js .rv.in{opacity:1;transform:none;transition:opacity .42s var(--ease),transform .42s var(--ease);transition-delay:var(--d,0ms);}

@media (prefers-reduced-motion:reduce){
  .js .rv,.js .rv.in{opacity:1;transform:none;transition:none;}
  .v1-bar i,.v1-cut-fill{transition:none;}
  .v1-card:hover,.v1-row:hover,.v1-row:active{transform:none;}
  .v1-btn-primary:hover,.v1-btn-green:hover,.v1-btn-amber:hover{transform:none;}
  /* Anything that loops must stop entirely, not merely shorten. A repeating
     nudge is exactly the kind of motion this setting exists to switch off. */
  .v1-jump-arrow,.v1-eyebrow,.v1-logo-img{animation:none;}
  /* The ticker still rotates — the content changing is the point — but it
     cuts instead of sliding. home.js drops the classes; this is the belt to
     that braces. */
  .v1-rev-enter,.v1-rev-exit{animation:none;}
  .v1-rev-exit{display:none;}
  .v1-step::before{transition:none;width:100%;}
  .v1-nav a::after{transition:none;}
}

/* The stagger is a flourish; under reduced motion the panel simply is. */
@media (prefers-reduced-motion:reduce){
  .v1-drop-item{transition-delay:0s!important;}
  /* The shine is decoration by definition: there is nothing in it that is
     not also said by the panel simply being open. */
  .v1-drop-panel::after,.v1-drop-chip-live::after{animation:none;opacity:0;}
  .v1-drop-item::before{transition:none;}
  .v1-drop-row,.v1-drop-note{transition:none;}
  .v1-drop-item:hover .v1-drop-row,.v1-drop-item:hover .v1-drop-note{transform:none;}
}

/* ── the phone, deliberately ─────────────────────────────────────────────
   This audience is on a mid-range Android over mobile data, so the small
   screen is the common case rather than the exception. Everything below was
   written against that, and each rule is here because something was wrong
   without it rather than as a precaution.

   The breakpoints already in this file stay where they are: 900px is where
   the header becomes a menu, 780 and 520 thin the offer bar, 560 is the
   phone. What was missing was the band between a phone and a laptop, and a
   handful of defects that only show up under a thumb. */

/* Nothing may push the page sideways. A grid or flex child defaults to
   min-width:auto, which means a long unbroken string -- a URL in a review, a
   Tamil compound with no space in it -- makes the whole page scrollable
   horizontally and every section a few pixels too wide. */
.v1-hero-grid > *,
.v1-grid-2 > *, .v1-grid-3 > *, .v1-grid-4 > *,
.v1-band-in > *, .v1-foot-grid > * { min-width:0; }

img, svg, video { max-width:100%; }

/* Long words break rather than overflow their card. */
.v1-h1, .v1-h2, .v1-sub, .v1-lede, .v1-card p, .v1-weak-n { overflow-wrap:anywhere; }

/* ── tablets and small laptops ─────────────────────────────────────────
   Four cards went straight from four across to one, so a 800px screen --
   an iPad held upright, and the cheap Android tablets this audience shares
   at home -- got a single column of enormous cards with the page three
   screens longer than it needed to be. Two across is the honest middle. */
@media (min-width:561px) and (max-width:900px){
  .v1-grid-3, .v1-grid-4 { grid-template-columns:repeat(2,minmax(0,1fr)); gap:18px; }
  .v1-grid-2 { grid-template-columns:1fr; }
}

/* A heading and its "see all" link share one line until they cannot. Without
   the wrap the link was squeezed to two or three characters per line beside a
   long heading. */
.v1-sec-head { flex-wrap:wrap; }
.v1-sec-head .v1-more { white-space:nowrap; }

@media (max-width:560px){
  /* iOS zooms the whole page in when a focused input is under 16px, and then
     leaves it zoomed. The WhatsApp field was 15px, so tapping it shifted the
     entire layout sideways -- the single most obvious "this site is not made
     for a phone" moment on the site. */
  .v1-field input, .v1-field span { font-size:16px; }

  /* A footer link was a 14px line with 6px of padding: a 32px target, under
     the 44px a thumb needs, stacked against its neighbours. */
  .v1-foot a { padding:11px 0; }
  .v1-foot-grid { gap:8px 24px; }

  /* Sections were 64px top and bottom, which on a 360px screen is most of a
     viewport spent on nothing. */
  .v1-sec { padding:40px 0; }
  .v1-hero-grid { padding-top:32px; padding-bottom:40px; gap:28px; }

  /* Two buttons side by side at 360px left each of them too narrow to read.
     Full width, stacked, in the order they should be pressed. */
  .v1-cta-row { flex-direction:column; align-items:stretch; }
  .v1-cta-row .v1-btn { width:100%; }

  /* The card is the product. It keeps its numbers legible before it keeps
     its proportions. */
  .v1-jump { gap:10px; }
  .v1-jump-col .n { font-size:34px; }
  .v1-jump-to .n { font-size:42px; }
  .v1-reviews-port { height:auto; min-height:84px; }
  .v1-proof-quote { gap:10px; }
  .v1-avatar { width:36px; height:36px; font-size:13px; }

  /* Four steps numbered 01 to 04 do not need 28px of padding each on a phone. */
  .v1-card { padding:20px; }
  .v1-step .n { font-size:13px; }
}

/* ── the smallest screens still sold in volume ─────────────────────────
   360x640 is not an edge case in Tamil Nadu; it is the modal device. At this
   width the header carries the mark, one button and the menu, and the button
   loses its padding before it loses its label. */
@media (max-width:380px){
  .v1-head-in { gap:12px; }
  .v1-head-end .v1-btn { padding:0 12px; font-size:14px; }
  .v1-logo-img { height:28px; }
  .v1-h1 { letter-spacing:-.02em; }
}

/* ── a feature list that reads as a list ───────────────────────────────
   .v1-ticks is a wrapping row, which is right for three short promises under
   a form and wrong for five lines describing what a season includes: the
   lines ran together across the card and no line began where the eye
   expected. This is the vertical form of the same idea, with the tick as a
   marker so the list reads as inclusions rather than as prose. */
.v1-includes { display:flex; flex-direction:column; gap:10px; }
.v1-includes p {
  position:relative; padding-left:26px; font-size:15px; color:var(--ink-2);
}
.v1-includes p::before {
  content:''; position:absolute; left:0; top:.45em;
  width:14px; height:8px; border-left:2px solid var(--green);
  border-bottom:2px solid var(--green); transform:rotate(-45deg);
}

/* ── the bridge ─────────────────────────────────────────────────────────
   Seven pages came back from the old site: jobs, contact, privacy, terms,
   refund, free-tests and batches. They now wear the v1 header and footer, but
   their content is still written in the old markup -- .wrap, .card, .sub,
   .eyebrow, .note and the rest.

   This section restyles that markup to the v1 system rather than rewriting
   the pages. Two reasons, and the second is the real one:

     1. The markup is fine. What differed was scale, spacing and weight, and
        that is a stylesheet's job.
     2. Four of those pages are the privacy notice, the terms, the refund
        policy and the contact page. Their text is a legal undertaking to the
        people who pay us, and Razorpay requires it to be reachable and
        accurate. Rewriting the markup around it means touching it, and a
        clumsy find-and-replace inside a refund policy is a much more
        expensive mistake than a heading two pixels off.

   Everything is scoped to body.v1-page, so the exam engine and the admin
   console -- which still use app.css deliberately -- are untouched. */

/* The old shell used a 32px gutter and a wider column; the v1 wrap is 20px
   and 16 on a phone. Matching them stops the content shifting sideways as a
   visitor moves between an old page and a new one. */
.v1-page .wrap{max-width:1200px;padding:0 20px;}
@media (max-width:560px){ .v1-page .wrap{padding:0 16px;} }

.v1-page .sec{padding:64px 0;}
@media (max-width:560px){ .v1-page .sec{padding:40px 0;} }

/* Type, on the v1 scale. The old sizes were set in rem against a 16px root
   and ran noticeably smaller than the new pages beside them. */
.v1-page main h1{
  font-size:clamp(30px,4.4vw,46px);line-height:1.08;
  letter-spacing:-.03em;font-weight:800;color:var(--ink);
}
.v1-page main h2{
  font-size:clamp(22px,2.6vw,30px);line-height:1.18;
  letter-spacing:-.025em;font-weight:800;color:var(--ink);
}
.v1-page main h3{font-size:18px;line-height:1.35;font-weight:700;color:var(--ink);}

/* A heading inside a dark card.
 *
 * `.card.dark` in app.css sets color:#fff on the card, and the three rules
 * above override it for every heading inside one -- so "Locked in the free
 * tier" on free-tests.html was navy text on a navy card and could not be read
 * at all. Two stylesheets, one element, and the more specific one wins without
 * knowing what it is sitting on.
 *
 * Scoped to `.card.dark` rather than loosened above, because the plain rules
 * are right everywhere else on the page. */
.v1-page main .card.dark h1,
.v1-page main .card.dark h2,
.v1-page main .card.dark h3{color:#fff;}
.v1-page .sub,.v1-page .lede{font-size:16px;line-height:1.65;color:var(--ink-3);}
.v1-page .doc p,.v1-page .doc li{font-size:15.5px;line-height:1.7;color:var(--ink-2);}

/* The card, minimal by subtraction: no border at rest, a hairline shadow to
   lift it off the panel, and the border back on hover -- the same bargain the
   v1 cards make, so a grid of them reads identically on either kind of page. */
.v1-page .card{
  border-color:transparent;border-radius:var(--r-lg);padding:28px;
  box-shadow:var(--shadow-1);
  transition:transform .22s var(--ease),box-shadow .22s var(--ease),border-color .22s var(--ease);
}
.v1-page .card:hover{transform:translateY(-3px);box-shadow:var(--shadow-2);border-color:var(--line);}
@media (max-width:560px){ .v1-page .card{padding:20px;} }

/* The old .btn was already 46px tall, comfortably past the 44 a thumb needs,
   so only the weight and the radius are brought into line with v1. Lowering
   it to match would have been a change for the worse dressed as consistency. */
.v1-page .btn{ border-radius:var(--r-md); font-weight:600; font-size:15px; }

/* The eyebrow was a bare uppercase line; in v1 it is a pill, and it is the
   first thing the eye lands on above a heading. */
.v1-page .eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  background:var(--green-bg);color:var(--green-deep);
  border:1px solid var(--green-line);border-radius:999px;
  padding:7px 15px;font-size:12px;letter-spacing:.08em;
  text-transform:uppercase;font-weight:600;
}

.v1-page .note{border-radius:var(--r-md);padding:18px 20px;font-size:15px;line-height:1.6;}
.v1-page .tag{border-radius:999px;padding:5px 11px;font-size:11px;letter-spacing:.07em;}

/* A long policy reads better in a narrower column than the page allows. */
.v1-page .doc{max-width:46em;}
.v1-page .doc h2{margin:40px 0 12px;}

/* Tables in the policies: they carry retention periods and contact details,
   so they have to survive a 360px screen rather than push the page sideways. */
.v1-page .doc table{width:100%;border-collapse:collapse;display:block;overflow-x:auto;}
@media (max-width:560px){
  .v1-page .doc td,.v1-page .doc th{padding:9px 10px;font-size:14px;}
}

/* ── measured, not guessed ──────────────────────────────────────────────
   Everything below came out of a real device-emulation pass over twelve
   pages at five widths -- 360, 390, 768, 820 and 1280 -- rather than from
   reading the stylesheet and imagining it. That run found no horizontal
   overflow anywhere, which is the failure people actually notice, and two
   classes of defect that are easy to miss by eye and impossible to miss
   with a thumb. */

/* TAP TARGETS. The "see more" links were 18 to 24 pixels tall. They are
   standalone navigation -- "All test series", "See the price", "Monthly
   compilations" -- not links inside a sentence, so the 44px minimum applies
   to them in full, and at 24px they were roughly half of it.

   Links inside a sentence are deliberately left alone: "read the refund
   policy" cannot be 44px tall without wrecking the line it sits in, and the
   guideline exempts them for exactly that reason. The fix is for controls,
   not for prose. */
@media (hover:none), (max-width:900px){
  .v1-more{
    display:inline-flex;align-items:center;min-height:44px;
  }
  /* The logo is a link to the homepage and was a 28px image. */
  .v1-logo{ display:inline-flex;align-items:center;min-height:44px; }
  .v1-page .doc a[href$=".html"]:not(.btn):not(.v1-more){ padding:2px 0; }
}

/* TEXT SIZE. A handful of labels were set at 11px: the report card's kicker,
   the review ticker's label and count, the "where the marks went" heading,
   and the job tags. At arm's length on a 360px screen that is not small, it
   is unreadable, and every one of them is a label whose whole job is to say
   what the number beside it means. 12px is the floor. */
@media (max-width:900px){
  .v1-report-kicker,
  .v1-reviews-label,
  .v1-reviews-count,
  .v1-reviews-stamp,
  .v1-weak-h,
  .v1-proof-pill,
  .v1-row-kick,
  .v1-pill, .v1-pill-new, .v1-pill-soon, .v1-pill-warn,
  .v1-badge-free,
  .v1-foot h3, .v1-foot h4,
  .v1-page .tag,
  .v1-page .eyebrow{ font-size:12px; }
}

/* ── the daily brief ────────────────────────────────────────────────────
   Built to the supplied design, with its colours mapped onto the approved
   canvas palette rather than copied across. The blue, the near-black and the
   pale blue fill in that spec are all near-misses for tokens that already
   exist here, and scripts/check-palette.py refuses a near-miss for exactly
   that reason: a design drifts one plausible hex at a time, and the fifth
   almost-identical blue is added by whoever is in a hurry. Same intent, same
   weight, colours the design actually contains.

   (The check reads comments too, which is why none are quoted here. That is
   not an oversight in the tool — a hex pasted into a comment is how the next
   person learns the wrong value.) */

.v1-ca-sec{padding-top:36px;}

/* The controls. position:relative because the calendar is a popover anchored
   to this row rather than a full-screen sheet. */
.v1-ca-nav{
  position:relative;
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  margin-top:24px;
}

/* Previous / Today as one segmented control, so they read as two states of
   the same thing rather than two unrelated buttons. */
.v1-seg{
  display:flex;gap:4px;padding:4px;
  background:var(--sunk);border:1px solid var(--line-2);border-radius:var(--r-md);
}
.v1-seg-btn{
  min-height:38px;padding:0 16px;border-radius:8px;
  font-size:14.5px;font-weight:700;color:var(--ink-3);
  transition:background .16s var(--ease),color .16s var(--ease);
}
.v1-seg-btn:hover{color:var(--ink);}
.v1-seg-btn.on{background:var(--page);color:var(--ink);box-shadow:var(--shadow-1);}

.v1-ca-pick{
  display:inline-flex;align-items:center;gap:10px;
  min-height:44px;padding:0 15px;
  background:var(--page);border:1.5px solid var(--line-3);border-radius:var(--r-md);
  font-size:14.5px;font-weight:700;color:var(--ink);
  transition:border-color .16s var(--ease),color .16s var(--ease);
}
.v1-ca-pick:hover,.v1-ca-pick[aria-expanded="true"]{border-color:var(--blue);color:var(--blue);}
.v1-ca-caret{color:var(--ink-4);font-weight:700;}

/* The lock is stated, not implied. Someone who cannot reach tomorrow should
   be told why rather than left pressing a dead control. */
.v1-ca-lock{
  display:inline-flex;align-items:center;gap:7px;
  font-size:13px;font-weight:600;color:var(--ink-3);
}

/* ── the calendar popover ─────────────────────────────────────────────
   Anchored to its button. A full-screen sheet to choose one date is more
   interruption than the task deserves on a laptop; below 560px the rules at
   the bottom turn it into a sheet, where a 312px panel pinned to a corner
   would be worse. */
.v1-cal{
  position:absolute;top:100%;left:0;margin-top:10px;z-index:40;
  width:396px;padding:14px;
  background:var(--page);border:1px solid var(--line-2);border-radius:var(--r-lg);
  box-shadow:0 18px 44px rgba(13,20,32,.16);
  opacity:0;transform:translateY(-4px);
  transition:opacity .16s var(--ease),transform .16s var(--ease);
}
.v1-cal.open{opacity:1;transform:none;}
.v1-cal[hidden]{display:none;}
.v1-cal:focus{outline:none;}

.v1-cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;}
.v1-cal-month{font-size:14.5px;font-weight:800;color:var(--ink);}
.v1-cal-nav{
  width:30px;height:30px;border-radius:8px;
  border:1px solid var(--line-2);background:var(--page);
  font-size:15px;font-weight:700;color:var(--ink-2);
  transition:background .16s var(--ease),opacity .16s var(--ease);
}
.v1-cal-nav:hover:not(:disabled){background:var(--sunk);}
.v1-cal-nav:disabled{opacity:.35;cursor:not-allowed;}

.v1-cal-dows{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:6px;}
.v1-cal-dows span{
  text-align:center;padding:4px 0;
  font-size:12px;font-weight:800;letter-spacing:.06em;color:var(--ink-4);
}

.v1-cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;}
.v1-cal-pad{display:block;}
.v1-cal-day{
  position:relative;
  display:flex;align-items:center;justify-content:center;
  height:36px;border-radius:9px;
  font-size:13.5px;font-weight:600;color:var(--ink-2);
  font-variant-numeric:tabular-nums;
  transition:background .14s var(--ease),color .14s var(--ease);
}
.v1-cal-day:hover:not(:disabled){background:var(--blue-bg);color:var(--blue-deep);}
.v1-cal-day.today{box-shadow:inset 0 0 0 1.5px var(--blue-line);}
.v1-cal-day.on{background:var(--ink);color:#FFFFFF;font-weight:700;}
/* A locked day is visibly out of action. Nothing is hidden: the number is
   still there, so the month still reads as a month. */
.v1-cal-day:disabled{color:var(--ink-4);opacity:.45;cursor:not-allowed;}

/* Most days will have nothing on them for a while. The dot is what stops
   someone hunting through empty screens one press at a time. */
.v1-cal-dot{
  display:block;width:5px;height:5px;border-radius:50%;background:var(--blue);
}
.v1-cal-day .v1-cal-dot{position:absolute;bottom:5px;left:50%;transform:translateX(-50%);}
.v1-cal-day.on .v1-cal-dot{background:#FFFFFF;}

.v1-cal-key{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  margin-top:12px;padding-top:12px;border-top:1px solid var(--line-2);
  font-size:12px;font-weight:600;color:var(--ink-3);
}
.v1-cal-k{display:inline-flex;align-items:center;gap:6px;}
.v1-cal-lock{
  display:block;width:9px;height:8px;border-radius:2px;
  border:1.5px solid var(--line-3);
}

/* ── the day strip ─────────────────────────────────────────────────────
   Scrolls horizontally on purpose. Fourteen chips will not fit a phone, and
   a wrapped grid of them would push the brief itself below the fold. */
.v1-week{margin-top:22px;}
.v1-week-head{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
.v1-week-name{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;}
.v1-week-head h3{font-size:15px;font-weight:800;letter-spacing:-.01em;color:var(--ink);}

/* The arrows move the window. Pressing a day selects it and leaves the window
   alone -- two controls, two jobs, which is the whole point of them existing
   separately.

   Round, on the page rather than in a grey box: a filled square next to a
   heading reads as a disabled button, which is precisely what these are not
   most of the time. The ring appears on hover and the chevron slides the way
   the week is about to move, so the control says what it will do before it
   is pressed. */
.v1-week-arrow{
  position:relative;flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:50%;
  color:var(--ink-3);background:var(--page);
  border:1px solid var(--line-2);
  transition:color .18s var(--ease),border-color .18s var(--ease),
             box-shadow .18s var(--ease),transform .18s var(--ease),opacity .18s var(--ease);
}
.v1-week-arrow svg{transition:transform .18s var(--ease);}
.v1-week-arrow:hover:not(:disabled){
  color:var(--blue);border-color:var(--blue);
  box-shadow:0 0 0 4px var(--blue-bg);
}
.v1-week-arrow:active:not(:disabled){transform:scale(.94);}
.v1-week-arrow:focus-visible{outline:2px solid var(--blue);outline-offset:2px;}
[data-ca="week-back"]:hover:not(:disabled) svg{transform:translateX(-2px);}
[data-ca="week-fwd"]:hover:not(:disabled) svg{transform:translateX(2px);}
/* An arrow with nowhere to go says so quietly rather than vanishing: a
   control that disappears makes the row jump as the archive fills. */
.v1-week-arrow:disabled{opacity:.3;cursor:not-allowed;}
.v1-week-range{font-size:13px;font-weight:600;color:var(--ink-4);font-variant-numeric:tabular-nums;}

/* A row that scrolls, with each chip sized to its content -- the sizing from
   the supplied design. Stretching seven chips across the full width made each
   one a wide, half-empty box on a laptop; at their natural size they read as
   a row of days, and the row scrolls on a phone rather than squeezing them. */
.v1-strip{
  display:flex;gap:9px;padding-bottom:4px;
  overflow-x:auto;scrollbar-width:thin;
}
.v1-strip-day{
  position:relative;flex:none;
  display:flex;flex-direction:column;align-items:center;gap:3px;
  min-width:56px;padding:9px 6px 11px;border-radius:var(--r-md);
  background:var(--page);border:1px solid var(--line-2);
  transition:border-color .16s var(--ease),background .16s var(--ease),transform .16s var(--ease);
}
.v1-strip-day:hover{border-color:var(--blue-line);transform:translateY(-1px);}
.v1-strip-day .dow{
  font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-4);
}
.v1-strip-day .num{font-size:17px;font-weight:800;line-height:1.1;color:var(--ink);font-variant-numeric:tabular-nums;}
.v1-strip-day .dot{display:block;width:5px;height:5px;border-radius:50%;background:transparent;}
.v1-strip-day.has .dot{background:var(--blue);}
.v1-strip-day.on{background:var(--ink);border-color:var(--ink);}
.v1-strip-day.on .dow{color:var(--line-3);}
.v1-strip-day.on .num{color:#FFFFFF;}
.v1-strip-day.on.has .dot{background:#FFFFFF;}
.v1-strip-day.today:not(.on){border-color:var(--blue-line);}
/* Drawn, not omitted: leaving the rest of the week out would make it change
   width as the days fill in, and four boxes do not read as a week. */
.v1-strip-day:disabled{opacity:.42;cursor:not-allowed;}
.v1-strip-day:disabled:hover{transform:none;border-color:var(--line-2);}

/* ── a brief card ─────────────────────────────────────────────────────
   The whole card is the control and it is a <button>, so it is reachable by
   keyboard and announced as pressable. Study is a cue inside it rather than
   a second control, which would otherwise be two tab stops doing one thing. */
.v1-ca-list{display:flex;flex-direction:column;gap:12px;margin-top:22px;}

.v1-ca-card{
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;width:100%;text-align:left;
  padding:18px 20px;border-radius:15px;
  background:var(--page);border:1px solid var(--line-2);
  box-shadow:var(--shadow-1);
  transition:border-color .16s var(--ease),box-shadow .16s var(--ease),transform .16s var(--ease);
}
.v1-ca-card:hover{border-color:var(--blue-line);box-shadow:var(--shadow-2);transform:translateY(-1px);}
.v1-ca-card:focus-visible{outline:2px solid var(--blue);outline-offset:2px;}

.v1-ca-mid{flex:1;min-width:200px;display:flex;flex-direction:column;}
.v1-ca-meta{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:6px;}
.v1-ca-title{
  font-size:18px;font-weight:700;line-height:1.35;letter-spacing:-.012em;color:var(--ink);
}
.v1-ca-sum{font-size:14.5px;line-height:1.5;color:var(--ink-3);margin-top:6px;}
.v1-ca-tags{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px;}
.v1-tag{
  font-size:12px;font-weight:700;letter-spacing:.03em;color:var(--ink-2);
  background:var(--sunk);border:1px solid var(--line-2);padding:4px 9px;border-radius:6px;
}

.v1-chip-cat{
  background:var(--green-bg);color:var(--green-deep);border-color:var(--green-line);
  font-weight:800;letter-spacing:.09em;text-transform:uppercase;
}
.v1-chip-read{background:var(--sunk);color:var(--ink-3);border-color:var(--line-2);font-weight:800;}
.v1-chip-sample{background:var(--amber-bg);color:var(--amber-ink);border-color:var(--amber-line);font-weight:700;}

.v1-ca-empty{
  background:var(--shell);border:1px solid var(--line-2);border-radius:15px;
  padding:28px 26px;margin-top:22px;
}
.v1-ca-empty h3{font-size:20px;font-weight:700;color:var(--ink-2);}
.v1-ca-empty p{font-size:15px;line-height:1.55;color:var(--ink-3);margin:9px 0 0;max-width:70ch;}
.v1-ca-empty .v1-btn{margin-top:18px;}
.v1-ca-empty-acts{display:flex;gap:10px;flex-wrap:wrap;}
.v1-ca-empty-acts .v1-btn{margin-top:18px;}

/* The wait. Two cards in the shape of a brief, so the page holds its real
   height while the request is in flight rather than jumping when it lands —
   and so that "still asking" never reads as "nothing was published". The
   shimmer is the only thing that says it is still moving. */
.v1-ca-skel{display:grid;gap:12px;margin-top:22px;}
.v1-ca-skel-row{
  display:flex;gap:16px;align-items:flex-start;
  background:var(--shell);border:1px solid var(--line-2);border-radius:15px;padding:22px 24px;
}
.v1-ca-skel-day{width:54px;height:54px;border-radius:12px;flex:none;}
.v1-ca-skel-body{flex:1;display:grid;gap:10px;min-width:0;padding-top:6px;}
.v1-ca-skel-line{height:13px;border-radius:7px;display:block;}
.v1-ca-skel-line.is-short{width:62%;}
.v1-ca-skel-day,.v1-ca-skel-line{
  background:linear-gradient(90deg,var(--line-2) 8%,var(--sunk) 20%,var(--line-2) 34%);
  background-size:800px 100%;
  animation:ca-shimmer 1.4s linear infinite;
}
@keyframes ca-shimmer{
  from{background-position:-380px 0;}
  to{background-position:380px 0;}
}
@media (prefers-reduced-motion:reduce){
  .v1-ca-skel-day,.v1-ca-skel-line{animation:none;background:var(--line-2);}
}

/* ── the job board's nothing ───────────────────────────────────────────
   Three states share this card: filters that match nothing, a board with no
   listings, and a board that could not be reached. None of them is a blank
   page — this site's own rule is that an empty component still shows
   something a person can read and act on. What is NOT here any more is a
   fallback list of invented vacancies: a placeholder job is a deadline
   somebody can miss. */
.jempty{
  text-align:center;display:grid;justify-items:center;gap:0;
  border-style:dashed;
  padding:clamp(30px,5vw,46px) clamp(20px,4vw,38px);
  background:
    radial-gradient(120% 140% at 50% -20%, var(--sunk) 0%, transparent 62%),
    var(--page);
  animation:jempty-in .32s var(--ease,cubic-bezier(.22,1,.36,1)) both;
}
.jempty-mark{
  width:54px;height:54px;border-radius:16px;display:grid;place-items:center;
  background:var(--chip);color:var(--ink-3);margin-bottom:18px;
}
.jempty-mark svg{width:26px;height:26px;}
.jempty-offline .jempty-mark{background:var(--amber-bg,var(--chip));color:var(--amber-ink,var(--ink-3));}
.jempty h3{font-size:clamp(18px,2.2vw,21px);font-weight:750;letter-spacing:-.01em;color:var(--ink);}
.jempty .sub{
  font-size:14.5px;line-height:1.6;color:var(--ink-3);margin-top:10px;
  max-width:56ch;text-wrap:pretty;
}
.jempty-acts{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:22px;}

@keyframes jempty-in{
  from{opacity:0;transform:translateY(8px);}
  to{opacity:1;transform:none;}
}
@media (prefers-reduced-motion:reduce){.jempty{animation:none;}}

/* There is no banner above the job board any more. It said the same thing the
   empty card below it says, because an unreachable board has no listings by
   definition — so the two were always on screen together, each with its own
   Try again. One message, where somebody is already looking. */

/* ── monthly compilations ─────────────────────────────────────────────── */
.v1-monthly{
  background:var(--night);border-radius:18px;padding:clamp(26px,4vw,40px);
  display:flex;align-items:center;justify-content:space-between;gap:24px;flex-wrap:wrap;
}
.v1-monthly h2{
  font-size:clamp(22px,3vw,28px);font-weight:800;letter-spacing:-.02em;color:#FFFFFF;
}
.v1-monthly p{
  font-size:15.5px;line-height:1.55;color:#94A0AE;margin:9px 0 0;max-width:52ch;
}
.v1-monthly-acts{display:flex;gap:10px;flex-wrap:wrap;}

@media (max-width:640px){
  .v1-ca-nav{gap:10px;}
  .v1-seg{flex:1;}
  .v1-seg-btn{flex:1;padding:0 10px;}
  .v1-ca-lock{order:3;flex-basis:100%;}
  /* The row scrolls, so a chip keeps its size rather than being squeezed to
     fit seven across a 360px screen. */
  .v1-strip{gap:8px;}

  /* A 312px panel pinned to a corner of a 360px screen is worse than a
     sheet, so below this width it becomes one. */
  .v1-cal{
    position:fixed;inset:auto 0 0 0;width:auto;margin:0;
    border-radius:var(--r-xl) var(--r-xl) 0 0;
    padding:20px 16px calc(20px + env(safe-area-inset-bottom,0px));
    transform:translateY(12px);
    box-shadow:0 -12px 40px rgba(13,20,32,.22);
  }
  .v1-cal-day{height:42px;}
  .v1-wheels{width:68px;}

  .v1-ca-card{gap:14px;padding:16px;}
  .v1-ca-mid{min-width:0;flex-basis:calc(100% - 64px);}
  .v1-ca-title{font-size:16.5px;}
  /* Study goes full width beneath the text rather than being squeezed to a
     few characters beside it. */
}


/* ── the dialog ─────────────────────────────────────────────────────────
   The page behind is dimmed and does not scroll. Fixing the body is what
   stops the scroll on iOS, where overflow:hidden alone does not, and the
   offset is restored on close so a reader is not thrown back to the top of a
   long list. */
body.is-locked{position:fixed;left:0;right:0;width:100%;overflow:hidden;}

.v1-modal-back{
  position:fixed;inset:0;z-index:80;
  display:flex;align-items:center;justify-content:center;padding:20px;
  background:rgba(13,20,32,.55);
  opacity:0;transition:opacity .22s var(--ease);
}
.v1-modal-back.open{opacity:1;}
.v1-modal-back[hidden]{display:none;}

.v1-modal{
  position:relative;width:min(640px,100%);max-height:86vh;overflow-y:auto;
  background:var(--page);border-radius:var(--r-xl);padding:30px 32px 26px;
  box-shadow:0 24px 60px rgba(13,20,32,.24);
  transform:translateY(10px) scale(.985);
  transition:transform .26s var(--spring);
}
.v1-modal-back.open .v1-modal{transform:none;}
.v1-modal:focus{outline:none;}

.v1-modal-x{
  position:absolute;top:12px;right:12px;
  width:40px;height:40px;border-radius:999px;
  font-size:22px;line-height:1;color:var(--ink-4);
  transition:background .16s var(--ease),color .16s var(--ease);
}
.v1-modal-x:hover{background:var(--sunk);color:var(--ink);}

.v1-modal-kicker{
  font-size:12px;letter-spacing:.08em;text-transform:uppercase;
  font-weight:600;color:var(--ink-4);padding-right:44px;
}
.v1-modal-title{
  font-size:clamp(21px,2.4vw,27px);line-height:1.22;letter-spacing:-.025em;
  font-weight:800;color:var(--ink);margin:8px 0 0;padding-right:44px;
}
.v1-modal-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;}
.v1-modal-body{margin-top:18px;}
.v1-modal-body p{font-size:15.5px;line-height:1.7;color:var(--ink-2);margin-bottom:14px;}
.v1-modal-body p:last-child{margin-bottom:0;}
.v1-modal-foot{margin-top:22px;}

@media (max-width:560px){
  .v1-modal-back{padding:0;align-items:flex-end;}
  /* On a phone it comes up from the bottom and keeps the top of the page
     visible, so it reads as a sheet over the list rather than a new screen. */
  .v1-modal{
    width:100%;max-height:92vh;
    border-radius:var(--r-xl) var(--r-xl) 0 0;padding:26px 20px 22px;
    transform:translateY(16px);
  }
}

@media (prefers-reduced-motion:reduce){
  .v1-modal-back,.v1-modal{transition:none;}
  .v1-modal{transform:none;}
}

/* ── the month and year drums ───────────────────────────────────────────
   Pressing the month name in the calendar swaps the day grid for two of
   these. Twelve presses of an arrow to reach last September is not a way to
   move through an archive.

   The fade by distance from the middle is what makes it read as a drum
   rather than a list, and the middle is the value -- so the thing you are
   choosing is always the thing in focus. */
.v1-cal-month{
  font-size:14.5px;font-weight:800;color:var(--ink);
  font-variant-numeric:tabular-nums;
}

/* Grid on the left, month drum on the right, both on screen at once. The
   drum used to replace the grid, which meant losing your place in the month
   in order to change it. */
.v1-cal-quick{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;}
.v1-cal-quick-btn{
  min-height:32px;padding:0 12px;border-radius:8px;
  background:var(--sunk);color:var(--ink-2);
  font-size:13px;font-weight:700;
  transition:background .16s var(--ease),color .16s var(--ease);
}
.v1-cal-quick-btn:hover{background:var(--blue-bg);color:var(--blue);}

.v1-cal-body{display:flex;gap:10px;align-items:stretch;}
.v1-cal-left{flex:1;min-width:0;}

/* A fixed height, and centred against the grid rather than stretched to it.
   Stretching made the column as tall as its own content, so there was nothing
   to scroll and the drum sat on its first month for ever. A drum is a window
   onto a longer list; the moment it is tall enough to show everything it
   stops being one. */
.v1-wheels{
  position:relative;flex:none;width:76px;height:210px;align-self:center;
  border-left:1px solid var(--line-2);padding-left:8px;
}
.v1-wheels[hidden]{display:none;}

/* The band across the middle marks the value without a hard box, the way a
   physical drum is read against a window. */
.v1-wheels::before{
  content:'';position:absolute;left:8px;right:0;top:calc(50% - 21px);height:42px;
  border-top:1px solid var(--line-2);border-bottom:1px solid var(--line-2);
  pointer-events:none;z-index:1;
}

/* A window, not a scroller. The list underneath is translated; nothing here
   scrolls, so there is no snap engine to fight the glide. */
.v1-wheel{
  width:100%;height:210px;overflow:hidden;position:relative;
  touch-action:none;cursor:grab;
  /* 84px is half the window less half an item, so index 0 sits in the band. */
  padding-top:84px;
}
.v1-wheel:active{cursor:grabbing;}
.v1-wheel:focus-visible{outline:2px solid var(--blue);outline-offset:2px;border-radius:8px;}

.v1-wheel-list{
  will-change:transform;
  transition:transform .34s cubic-bezier(.22,.61,.36,1);
}
/* No easing while a finger is on it: the list has to track the thumb exactly,
   or the drag feels like it is lagging behind the hand. */
.v1-wheel-list.is-dragging{transition:none;}

.v1-wheel-item{
  display:flex;align-items:center;justify-content:center;
  height:42px;width:100%;
  font-weight:600;color:var(--ink);user-select:none;
  transition:opacity .28s var(--ease),transform .28s var(--ease),
             font-size .28s var(--ease),color .28s var(--ease);
}
.v1-wheel-item[data-dist="0"]{font-size:19px;font-weight:800;opacity:1;}
.v1-wheel-item[data-dist="1"]{font-size:16px;opacity:.6;}
.v1-wheel-item[data-dist="2"]{font-size:14px;opacity:.32;}
.v1-wheel-item[data-dist="3"]{font-size:13px;opacity:.16;}
.v1-wheel-item[data-dist="4"]{font-size:12px;opacity:.08;}

@media (prefers-reduced-motion:reduce){
  .v1-wheel-list,.v1-wheel-item{transition:none;}
}

/* ── motion on the brief ────────────────────────────────────────────────
   All transform and opacity, so none of it triggers layout, and the whole
   layer collapses under prefers-reduced-motion at the end. The point is to
   make a change of day legible: the list is what changed, so the list is
   what moves. */

/* Cards arrive in sequence when the day changes, which reads as "this is the
   new day" rather than as the page having silently swapped underneath you. */
@keyframes ca-in{
  from{opacity:0;transform:translateY(10px);}
  to{opacity:1;transform:none;}
}
.v1-ca-card{animation:ca-in .34s var(--ease) both;}
.v1-ca-card:nth-child(1){animation-delay:0ms;}
.v1-ca-card:nth-child(2){animation-delay:55ms;}
.v1-ca-card:nth-child(3){animation-delay:110ms;}
.v1-ca-card:nth-child(4){animation-delay:165ms;}
.v1-ca-card:nth-child(n+5){animation-delay:210ms;}

.v1-ca-empty{animation:ca-in .34s var(--ease) both;}

/* The chips settle in left to right, so the week reads as a week being dealt
   rather than appearing all at once. */
@keyframes chip-in{
  from{opacity:0;transform:translateY(6px) scale(.96);}
  to{opacity:1;transform:none;}
}
.v1-strip-day{animation:chip-in .26s var(--ease) both;}
.v1-strip-day:nth-child(1){animation-delay:0ms;}
.v1-strip-day:nth-child(2){animation-delay:26ms;}
.v1-strip-day:nth-child(3){animation-delay:52ms;}
.v1-strip-day:nth-child(4){animation-delay:78ms;}
.v1-strip-day:nth-child(5){animation-delay:104ms;}
.v1-strip-day:nth-child(6){animation-delay:130ms;}
.v1-strip-day:nth-child(7){animation-delay:156ms;}

/* The selected chip lifts rather than merely darkening, so the eye can find
   it after the row has redrawn. */
.v1-strip-day{transition:transform .18s var(--ease),border-color .18s var(--ease),
              background .18s var(--ease),box-shadow .18s var(--ease);}
.v1-strip-day.on{transform:translateY(-2px);box-shadow:var(--shadow-2);}

/* The calendar's days fade up as a block when a month is drawn, which makes
   paging months feel like turning a page rather than a flicker. */
@keyframes cal-in{ from{opacity:0;transform:translateY(4px);} to{opacity:1;transform:none;} }
.v1-cal-grid{animation:cal-in .2s var(--ease) both;}

/* The drums arrive from the side they replace the grid from. */
@keyframes wheel-in{ from{opacity:0;transform:scale(.97);} to{opacity:1;transform:none;} }
.v1-wheels:not([hidden]){animation:wheel-in .22s var(--spring) both;}

@media (prefers-reduced-motion:reduce){
  .v1-ca-card,.v1-ca-empty,.v1-strip-day,.v1-cal-grid,.v1-wheels:not([hidden]){animation:none;}
  .v1-week-arrow,.v1-week-arrow svg,.v1-strip-day{transition:none;}
}

/* ── the celebration ────────────────────────────────────────────────────
   The burst sits behind the fourth numeral and is removed the moment it has
   played. It is decoration: aria-hidden in the markup, pointer-events off
   here, and nothing under it moves or reflows while it runs. */
.v1-loop-last{position:relative;}
/* Above the band's background, below the numeral and its words. z-index:-1
   put it behind the band itself and it simply disappeared. */
.v1-loop-burst{
  position:absolute;
  width:260px;height:260px;
  transform:translate(-50%,-50%);
  pointer-events:none;z-index:0;
  opacity:.9;
}
.v1-loop-last > *:not(.v1-loop-burst){position:relative;z-index:1;}

@media (max-width:900px){
  .v1-loop-burst{width:190px;height:190px;}
}

/* ── take a test on this ────────────────────────────────────────────────
   The brief ends with the offer a test makes, because reading is not
   remembering and that is the argument the product rests on.

   Shown whether or not a quiz exists. An action that appears only sometimes
   teaches nobody that it exists, so the disabled state carries the
   explanation instead of the button disappearing. */
.v1-quiz-cta{
  display:flex;flex-direction:column;gap:8px;align-items:flex-start;
  padding-top:18px;border-top:1px solid var(--line-2);
}
.v1-quiz-note{font-size:13.5px;line-height:1.5;color:var(--ink-4);max-width:46ch;}
.v1-quiz-cta .v1-btn:disabled{
  opacity:.45;cursor:not-allowed;transform:none;box-shadow:none;
}
@media (max-width:560px){
  .v1-quiz-cta .v1-btn{width:100%;}
}

/* ── the questions on a brief ────────────────────────────────
   Answered in the brief, so they are styled as part of the read rather than
   as a form: no boxes, no Submit, no progress bar. An option is a wide target
   because this is read on a phone on a bus, and the letter down the left is
   what makes four of them scannable without reading each one first.

   Right and wrong are never colour alone. The correct option gains a tick and
   the chosen wrong one a cross, and the line underneath says which in words,
   because roughly one man in twelve cannot tell this green from this red. */
.v1-quiz{
  margin-top:26px;padding-top:22px;border-top:1px solid var(--line-2);
}
.v1-quiz-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;}
.v1-quiz-head h4{font-size:17px;font-weight:800;letter-spacing:-.01em;color:var(--ink);}
.v1-quiz-tally{
  font-size:13px;font-weight:700;color:var(--ink-4);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.v1-quiz-tally.is-done{color:var(--green-deep);}

.v1-q{margin-top:20px;}
.v1-q-ask{font-size:15.5px;font-weight:600;line-height:1.5;color:var(--ink);text-wrap:pretty;}
.v1-q-opts{display:flex;flex-direction:column;gap:8px;margin-top:12px;}
.v1-q-opt{
  display:flex;align-items:flex-start;gap:12px;width:100%;text-align:left;
  padding:12px 14px;min-height:48px;
  border:1.5px solid var(--line);border-radius:var(--r-md);background:var(--page);
  font-size:15px;line-height:1.45;color:var(--ink);
  transition:border-color .16s var(--ease),background .16s var(--ease);
}
.v1-q-opt:hover:not(.is-locked){border-color:var(--blue);background:var(--blue-tint);}
.v1-q-key{
  flex:none;width:24px;height:24px;border-radius:50%;background:var(--chip);
  display:flex;align-items:center;justify-content:center;
  font-size:12px;font-weight:800;color:var(--ink-3);
}
.v1-q-text{flex:1;min-width:0;}

/* Locked, but not dimmed to unreadable: the options are the answer sheet
   afterwards, and greying them out would hide what was just learnt. */
.v1-q-opt.is-locked{cursor:default;}
.v1-q-opt.is-locked:not(.is-right):not(.is-wrong){opacity:.6;}

.v1-q-opt.is-right{border-color:var(--green);background:var(--green-bg);}
.v1-q-opt.is-right .v1-q-key{background:var(--green);color:#FFFFFF;}
.v1-q-opt.is-right .v1-q-key::after{content:'\2713';}
.v1-q-opt.is-right .v1-q-key{font-size:0;}
.v1-q-opt.is-right .v1-q-key::after{font-size:13px;}

.v1-q-opt.is-wrong{border-color:var(--red);background:var(--red-bg);}
.v1-q-opt.is-wrong .v1-q-key{background:var(--red);color:#FFFFFF;font-size:0;}
.v1-q-opt.is-wrong .v1-q-key::after{content:'\00d7';font-size:15px;}

.v1-q-why{
  margin-top:10px;font-size:14px;line-height:1.55;color:var(--ink-2);
  padding-left:12px;border-left:3px solid var(--line-2);
}
.v1-q-why.is-right{border-left-color:var(--green);}
.v1-q-why.is-wrong{border-left-color:var(--red);}

@media (prefers-reduced-motion:reduce){
  .v1-q-opt{transition:none;}
}

/* ── a brief with a picture ─────────────────────────────────────────────
   A picture earns its place when the fact itself is a shape: a figure that
   moved, a boundary that changed, a chart a question could be set on. It is
   never decoration here, which is why the admin refuses to publish a brief
   that carries one without a description.

   There is no caption. The description is the alt text, and printing it
   underneath as well makes a screen reader announce it twice while telling a
   sighted reader about a picture they are looking at. */
.v1-brief-img{
  margin:0 0 20px;border-radius:var(--r-md);overflow:hidden;
  background:var(--sunk);border:1px solid var(--line-2);
}
.v1-brief-img img{display:block;width:100%;height:auto;}

/* On the card it is a thumbnail, not the picture. A list row with a
   full-width image in it makes the day scroll rather than scan.

   Bigger than it was, because it is now the first thing in the row rather
   than the third: the date circle that used to lead is gone, and a 76px
   thumbnail that was a supporting detail reads as an afterthought once it is
   the only thing to the left of the text. */
.v1-ca-thumb{
  flex:none;order:-1;
  width:104px;height:76px;border-radius:12px;overflow:hidden;
  background:var(--sunk);border:1px solid var(--line-2);
}
.v1-ca-thumb img{transition:transform .35s var(--ease);}
/* The picture leans in with the card rather than sitting still inside a
   moving box. Transform only, so nothing here costs a layout. */
.v1-ca-card:hover .v1-ca-thumb img{transform:scale(1.05);}
.v1-ca-thumb img{display:block;width:100%;height:100%;object-fit:cover;}

@media (max-width:640px){
  .v1-ca-thumb{width:72px;height:56px;}
}


/* ══ the graduate half ═══════════════════════════════════════════════════
   careers.html and employers.html. They reuse the exam side's shell, hero
   grid and navy card on purpose: this is one product with two doors, and a
   second visual language would make it read as a different company.
   ════════════════════════════════════════════════════════════════════════ */

/* The line under a hero's buttons that says when something opens. Not small
   print: a page describing a product nobody can buy yet has to say so where
   the buttons are, not at the bottom. */
.v1-note-line{margin-top:18px;font-size:14px;line-height:1.5;color:var(--ink-3);}
.v1-note-line b{font-weight:700;color:var(--ink);}

/* The match list in the navy card, standing where the score jump stands on
   the exam hero. Rows rather than a chart, because the answer to "what would
   I get" is a list of roles. */
.v1-match{display:flex;flex-direction:column;gap:2px;margin-top:6px;}
.v1-match-row{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding:11px 0;border-bottom:1px solid var(--night-3);
}
.v1-match-row:last-child{border-bottom:0;}
.v1-match-role{font-size:14.5px;font-weight:600;color:#E6E9ED;min-width:0;}
.v1-match-fit{
  font-size:12.5px;font-weight:700;color:var(--green);white-space:nowrap;
  font-variant-numeric:tabular-nums;
}

/* The price cards. */
.v1-price-card{display:flex;flex-direction:column;gap:14px;}
.v1-price-h{font-size:19px;font-weight:700;letter-spacing:-.01em;color:var(--ink);}
.v1-price{display:flex;align-items:baseline;gap:8px;}
.v1-price-per{font-size:14px;font-weight:600;color:var(--ink-3);}
.v1-price-card ul{list-style:none;display:flex;flex-direction:column;gap:9px;margin:0;padding:0;}
.v1-price-card li{
  position:relative;padding-left:24px;font-size:14.5px;line-height:1.5;color:var(--ink-2);
}
.v1-price-card li::before{
  content:'\2713';position:absolute;left:0;top:0;
  color:var(--green-deep);font-weight:800;font-size:13px;
}
.v1-price-card .v1-btn{margin-top:auto;}

/* A button-shaped thing that is deliberately not a button, because there is
   nothing behind it yet. It reads as disabled AND says what it is waiting
   for, so nobody presses it twice wondering if the page is broken. */
.v1-btn-off{
  opacity:.55;cursor:not-allowed;pointer-events:none;
  justify-content:center;text-align:center;
}

/* The small line at the end of a section that points somewhere else. */
.v1-fine{margin-top:22px;font-size:14px;line-height:1.6;color:var(--ink-3);}

/* Forms in the navy band. The label sits above the box: a placeholder that
   doubles as the label disappears the moment somebody types, and then the
   form is four unlabelled boxes. */
.v1-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px;}
.v1-field-l{display:flex;flex-direction:column;gap:7px;min-width:0;}
.v1-field-l > span{font-size:12.5px;font-weight:700;color:#94A0AE;letter-spacing:.02em;}
.v1-field-l input{
  min-height:48px;border:0;border-radius:var(--r-md);padding:0 14px;
  background:var(--page);color:var(--ink);font-size:15px;font-weight:500;
  outline:none;width:100%;
}
.v1-field-l input:focus-visible{outline:2.5px solid var(--blue);outline-offset:2px;}
.v1-form-said{margin-top:12px;font-size:13.5px;line-height:1.5;color:#94A0AE;min-height:1.2em;}

@media (max-width:640px){
  .v1-form-grid{grid-template-columns:1fr;}
}

/* The FAQ list. <details> so it works with no JavaScript at all, and so a
   phone's find-in-page can still reach a closed answer. */
.v1-faq{display:flex;flex-direction:column;gap:12px;margin-top:26px;}
.v1-faq .v1-card{padding:20px 22px;}
.v1-faq summary{
  cursor:pointer;font-size:16px;font-weight:700;color:var(--ink);
  list-style:none;display:flex;align-items:center;justify-content:space-between;gap:16px;
}
.v1-faq summary::-webkit-details-marker{display:none;}
.v1-faq summary::after{
  content:'+';font-size:20px;font-weight:600;color:var(--ink-4);flex:none;
  transition:transform .18s var(--ease);
}
.v1-faq details[open] summary::after{content:'\2212';}
.v1-faq p{margin-top:12px;font-size:15px;line-height:1.65;color:var(--ink-2);text-wrap:pretty;}

@media (prefers-reduced-motion:reduce){
  .v1-faq summary::after{transition:none;}
}

/* The Play Store badge, wherever it appears. Hidden until claims.android.url
   is a real listing -- a badge linking to nothing is worse than no badge. */
.v1-play{display:inline-flex;align-items:center;gap:10px;}
.v1-play img{height:40px;width:auto;}
.v1-foot .v1-play{margin-top:16px;}

/* ── long-form pages ────────────────────────────────────────────────────
   The syllabus, cut-off and exam-pattern pages. These exist to be found in
   search and then read end to end on a phone, which makes them the only
   pages on the site that are mostly text — so they get a measure, not the
   full 1200px grid. Anything wider than about 68 characters loses the line
   on the way back. */
.v1-prose{max-width:760px;}
.v1-prose h2{
  font-size:clamp(20px,2.4vw,25px);font-weight:800;letter-spacing:-.02em;
  color:var(--ink);margin-top:38px;
}
.v1-prose p{margin-top:14px;font-size:16.5px;line-height:1.7;color:var(--ink-2);text-wrap:pretty;}
.v1-prose p b{color:var(--ink);font-weight:700;}
.v1-prose .v1-sub{margin-top:10px;}

.v1-list{list-style:none;margin-top:16px;padding:0;display:flex;flex-direction:column;gap:12px;}
.v1-list li{
  position:relative;padding-left:22px;font-size:16px;line-height:1.65;color:var(--ink-2);
}
.v1-list li::before{
  content:'';position:absolute;left:2px;top:11px;
  width:7px;height:7px;border-radius:50%;background:var(--blue-fill);
}
.v1-list b{color:var(--ink);font-weight:700;}

/* The line that says the official notification is the authority, not us.
   Amber rather than red: it is a caution about where to check, not an error,
   and the job board already uses this colour for the same idea. */
.v1-warn-note{
  margin-top:24px;padding:16px 18px;border-radius:var(--r-md);
  background:var(--amber-bg);border:1px solid var(--amber-line);
  font-size:14.5px;line-height:1.6;color:var(--amber-ink);
}
.v1-warn-note b{font-weight:700;}
.v1-warn-note a{font-weight:700;color:var(--amber-deep);text-decoration:underline;}

/* A table of marks, built from rows rather than <table>, so it can become a
   stack on a phone without a horizontal scroll. The numbers line up because
   the font supports tabular figures. */
.v1-table{
  margin-top:22px;border:1px solid var(--line-2);border-radius:var(--r-md);overflow:hidden;
}
.v1-tr{
  display:grid;grid-template-columns:1fr 100px 100px;gap:12px;
  padding:13px 16px;border-bottom:1px solid var(--line-2);
  font-size:15px;color:var(--ink-2);
}
.v1-tr:last-child{border-bottom:0;}
.v1-tr span:not(:first-child){text-align:right;font-variant-numeric:tabular-nums;}
.v1-tr-head{
  background:var(--sunk);font-size:12px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-3);
}
.v1-tr-foot{background:var(--sunk);font-weight:800;color:var(--ink);}

@media (max-width:520px){
  .v1-tr{grid-template-columns:1fr 62px 68px;gap:8px;padding:12px 13px;font-size:14px;}
}


/* One line of Tamil under the English hero sub. Meera Inimai has a single
   weight and app.css pins [lang="ta"] to 400, so this sets size and colour
   only -- asking for a bold Tamil face makes the browser smear the glyphs. */
.v1-sub-ta{
  margin-top:12px;font-size:16px;line-height:1.85;color:var(--ink-3);max-width:34em;
}

/* The link to the graduate half, under the hero buttons. Deliberately not a
   third button: two calls to action are a choice, three are a menu. */
.v1-second-door{
  margin-top:20px;font-size:14.5px;line-height:1.6;color:var(--ink-3);
}
.v1-second-door a{font-weight:700;white-space:nowrap;}



/* ══ the free resume check ══════════════════════════════════════════════
   The one part of the careers page that works today. It sits on the light
   page rather than inside a navy band, so the fields are drawn here instead
   of borrowing the band's pill styling, which assumes a dark background. */
.v1-check{
  margin-top:28px;background:var(--sunk);border:1px solid var(--line);
  border-radius:var(--r-xl);padding:26px;
}
.v1-check .v1-field{
  display:block;background:none;padding:0;min-height:0;
}
.v1-check .v1-field-l{
  display:block;font-size:12px;font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-3);margin-bottom:7px;
}
.v1-check .v1-field input,
.v1-check .v1-field textarea{
  width:100%;background:var(--page);border:1px solid var(--line-2);
  border-radius:var(--r-md);padding:12px 14px;font-size:15px;color:var(--ink);
  min-height:48px;outline:none;
}
.v1-check .v1-field textarea{line-height:1.6;resize:vertical;}
.v1-check .v1-field input:focus,
.v1-check .v1-field textarea:focus{border-color:var(--blue);}
.v1-check .v1-field-wide{margin-bottom:18px;}
.v1-check .v1-fine{display:block;margin-top:8px;max-width:64ch;}
.v1-check .v1-form-grid{margin-bottom:18px;}

/* The rubric, folded away but present before anything is submitted. */
.v1-rubric{margin-top:20px;border-top:1px solid var(--line-2);padding-top:14px;}
.v1-rubric summary{
  cursor:pointer;font-size:14px;font-weight:700;color:var(--ink-2);list-style:none;
}
.v1-rubric summary::after{content:' ▾';color:var(--ink-4);}
.v1-rubric[open] summary::after{content:' ▴';}
.v1-rules{list-style:none;margin:12px 0 0;padding:0;}
.v1-rules li{
  display:grid;grid-template-columns:1fr auto;gap:2px 12px;
  padding:10px 0;border-bottom:1px solid var(--line-2);
}
.v1-rules li b{font-size:14px;font-weight:700;color:var(--ink);}
.v1-rules .pts{font-size:12.5px;font-weight:800;color:var(--ink-4);}
.v1-rules li p{grid-column:1 / -1;font-size:13px;line-height:1.55;color:var(--ink-3);}

/* ── the result ─────────────────────────────────────────────────────────── */
.v1-result{margin-top:24px;padding:26px;}
.v1-result-head{display:flex;align-items:center;gap:20px;flex-wrap:wrap;}
.v1-result-head h3{font-size:20px;font-weight:800;color:var(--ink);line-height:1.3;}
.v1-result-head p{font-size:14.5px;color:var(--ink-2);margin-top:4px;line-height:1.55;max-width:52ch;}

/* The score, as a figure rather than a sentence. Colour is never the only
   signal -- the verdict beside it says the same thing in words. */
.v1-ring{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:96px;height:96px;border-radius:50%;flex:none;
}
.v1-ring b{font-size:30px;font-weight:800;line-height:1;}
.v1-ring span{font-size:11.5px;font-weight:700;margin-top:3px;opacity:.75;}
.v1-ring.is-ok{background:var(--green-bg);color:var(--green-deep);}
.v1-ring.is-no{background:var(--amber-bg);color:var(--amber-deep);}

.v1-checks{list-style:none;margin:22px 0 0;padding:0;}
.v1-checks li{
  display:grid;grid-template-columns:1fr auto;gap:3px 12px;align-items:baseline;
  padding:11px 0;border-top:1px solid var(--line-2);
}
.v1-checks li b{font-size:14.5px;font-weight:700;color:var(--ink);}
.v1-checks li.is-no b{color:var(--amber-deep);}
.v1-checks li.is-ok b::before{content:'✓ ';color:var(--green-ink);}
.v1-checks li.is-no b::before{content:'✕ ';color:var(--amber-ink);}
.v1-checks .pts{font-size:12.5px;font-weight:800;color:var(--ink-4);}
.v1-checks li p{grid-column:1 / -1;font-size:13.5px;line-height:1.6;color:var(--ink-3);max-width:64ch;}

.v1-offer{
  margin-top:22px;padding:18px 20px;border-radius:var(--r-lg);background:var(--blue-bg);
}
.v1-offer b{display:block;font-size:15px;font-weight:800;color:var(--blue-deep);}
.v1-offer p{font-size:14px;line-height:1.6;color:var(--ink-2);margin-top:5px;max-width:58ch;}

@media (max-width:560px){
  .v1-check{padding:20px 16px;}
  .v1-result{padding:20px 16px;}
  .v1-ring{width:76px;height:76px;}
  .v1-ring b{font-size:24px;}
}


/* The paid card of a two-door choice. A quiet ring rather than a bigger card
   or a "most popular" flag: this page shows exactly two options and one of
   them is free, so the paid one needs marking, not shouting. The ring is the
   brand blue at low weight, which reads as "chosen" on a white page without
   competing with the button inside it. */
.v1-plan-best{
  border-color:var(--blue-line);
  box-shadow:0 0 0 3px var(--blue-bg), var(--shadow-1);
}

/* A price card is a flex column, so a pill inside it stretches to the full
   width of the card and stops reading as a badge. */
.v1-price-card > .v1-pill,
.v1-price-card > .v1-badge-free{align-self:flex-start;}

/* This card is a flex column with its own gap, so the offer chip and the
   deadline note would otherwise pay for their spacing twice. */
.v1-price-card > .v1-save,
.v1-price-card > .v1-offer-note{margin-top:0;}

/* The paid state. Green already means "free" on these cards, so the open
   season needs its own colour or the two doors look identical at a glance. */
.v1-pill-blue{background:var(--blue-bg);color:var(--blue-deep);}

/* ── paid job alerts ─────────────────────────────────────────────────────
   The whole ₹9 flow happens in the dialog above rather than on a page of its
   own. The owner asked for it that way and the reason holds up: the person
   pressing "Get job alerts on WhatsApp" is in the middle of reading the job
   board, and a navigation costs them their place in a list they were halfway
   down. A dialog hands it back when they close it.

   These classes are here rather than in a <style> block on job-alerts.html
   because the dialog is opened from the board, from the alerts page, and from
   anywhere else a WhatsApp button is put later. A component with its styles on
   one page is a component that works on one page. */

/* The message that actually arrives, shown rather than described. Nine rupees
   is an easy yes once you can see what it buys; it is an abstraction until
   then. Type and borders rather than an image, so it stays sharp and a screen
   reader can read it. */
.v1-ja-phone{
  background:var(--page);border:1px solid var(--line);border-radius:26px;
  padding:14px;box-shadow:var(--shadow-2);max-width:340px;width:100%;
}
.v1-ja-screen{background:var(--line-2);border-radius:16px;padding:14px 12px 18px;}
.v1-ja-bubble{
  background:linear-gradient(180deg,var(--wa) 0%,var(--wa-deep) 100%);
  color:var(--ink);border-radius:14px 14px 14px 4px;
  padding:12px 14px;font-size:14px;line-height:1.5;max-width:94%;
  box-shadow:0 1px 2px rgba(13,20,32,.12);
}
.v1-ja-bubble + .v1-ja-bubble{margin-top:10px;}
/* Full --ink and no heavy fade. The bubble is a gradient, and the timestamp
   sits at its dark end where a --ink-2 at .72 came out around 2.6:1 -- a
   smudge rather than a time. 11px already makes it recede. */
.v1-ja-meta{font-size:11px;color:var(--ink);opacity:.8;margin-top:7px;text-align:right;}

/* Where you are in the flow. Three steps, because the owner asked for very
   simple steps and four was one more than the flow actually has. */
.v1-ja-steps{display:flex;gap:6px;margin:2px 0 20px;flex-wrap:wrap;}
.v1-ja-pip{
  display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ink-4);
  padding:6px 12px;border-radius:999px;background:var(--chip);font-weight:500;
}
.v1-ja-pip.on{background:var(--blue-bg);color:var(--blue-deep);font-weight:600;}
.v1-ja-pip.done{background:var(--green-bg);color:var(--green-deep);}
.v1-ja-pip i{font-style:normal;font-weight:700;}

/* Checkboxes painted as chips. The input stays in the DOM and keeps its
   keyboard behaviour and its announcement; only its painting is replaced,
   which is the difference between a styled control and a div pretending to be
   one. */
.v1-ja-cats{display:flex;flex-wrap:wrap;gap:9px;}
.v1-ja-cat{position:relative;}
.v1-ja-cat input{position:absolute;inset:0;opacity:0;cursor:pointer;margin:0;}
.v1-ja-cat span{
  display:inline-flex;align-items:center;gap:8px;min-height:44px;
  padding:9px 16px;border-radius:999px;border:1.5px solid var(--line-3);
  background:var(--page);color:var(--ink-2);font-size:.9rem;font-weight:500;
  transition:border-color .16s,background .16s,color .16s;
}
.v1-ja-cat input:checked + span{
  border-color:var(--blue);background:var(--blue-bg);color:var(--blue-deep);font-weight:600;
}
.v1-ja-cat input:focus-visible + span{outline:2px solid var(--blue);outline-offset:2px;}
.v1-ja-cat input:hover:not(:checked) + span{border-color:var(--ink-4);}
.v1-ja-cat span::before{
  content:"";width:8px;height:8px;border-radius:50%;background:var(--line-3);
  transition:background .16s,box-shadow .16s;
}
.v1-ja-cat input:checked + span::before{background:var(--blue);box-shadow:0 0 0 3px var(--blue-line);}

/* The file input, which cannot be styled, inside something that can. */
.v1-ja-drop{
  border:1.5px dashed var(--line-3);border-radius:var(--r-md);padding:18px 16px;
  text-align:center;background:var(--sunk);position:relative;
  transition:border-color .16s,background .16s;
}
.v1-ja-drop:hover,.v1-ja-drop.has{border-color:var(--blue);background:var(--blue-tint);}
.v1-ja-drop input{position:absolute;inset:0;opacity:0;cursor:pointer;}
.v1-ja-drop .n{font-weight:600;color:var(--ink);font-size:.95rem;}
.v1-ja-drop .s{font-size:.83rem;color:var(--ink-3);margin-top:3px;}

/* Two boxes, never one "I agree to everything". Being messaged and having your
   resume handed to a company are different asks, and bundling them means
   neither is freely given. */
.v1-ja-consent{
  border:1.5px solid var(--line);border-radius:var(--r-md);padding:14px 16px;
  background:var(--page);transition:border-color .16s,background .16s;
}
.v1-ja-consent + .v1-ja-consent{margin-top:10px;}
.v1-ja-consent.on{border-color:var(--blue);background:var(--blue-tint);}
.v1-ja-consent label{display:flex;gap:11px;align-items:flex-start;cursor:pointer;}
.v1-ja-consent input{width:20px;height:20px;margin:1px 0 0;flex:none;accent-color:var(--blue);cursor:pointer;}
.v1-ja-consent .t{font-size:.86rem;line-height:1.6;color:var(--ink-2);}
.v1-ja-consent .h{display:block;font-weight:600;color:var(--ink);margin-bottom:3px;font-size:.93rem;}
.v1-ja-opt{
  display:inline-block;margin-left:7px;font-size:.68rem;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;padding:2px 7px;border-radius:999px;
  background:var(--chip);color:var(--ink-3);vertical-align:middle;
}

.v1-ja-label{display:block;font-size:.8rem;font-weight:600;margin-bottom:7px;color:var(--ink);}
.v1-ja-note{font-size:.85rem;color:var(--ink-3);line-height:1.6;}
.v1-ja-err{color:var(--red);font-size:.88rem;line-height:1.6;min-height:1.2em;}

/* The price, on the first screen. Big because it is the objection being
   answered: nine rupees is the whole pitch. */
.v1-ja-price{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;margin:4px 0 2px;}
.v1-ja-price .n{font-size:2.4rem;font-weight:700;letter-spacing:-.025em;font-variant-numeric:tabular-nums;}

.v1-ja-ticks{list-style:none;margin:16px 0 0;padding:0;}
.v1-ja-ticks li{
  display:flex;gap:10px;align-items:flex-start;
  font-size:.92rem;color:var(--ink-2);line-height:1.6;margin-bottom:9px;
}
.v1-ja-ticks li::before{
  content:"✓";color:var(--wa-deep);font-weight:700;flex:none;line-height:1.5;
}

@media (max-width:560px){
  /* A dialog that fills the phone rather than floating in the middle of it.
     At this width the padding either side is space the form needs more. */
  .v1-modal{padding:24px 18px 20px;border-radius:var(--r-lg);}
  .v1-ja-price .n{font-size:2rem;}
}

/* The action stays on screen while the form scrolls under it.
   On a phone the details screen is a long scroll — four fields, the category
   chips, the upload target and two consent blocks whose wording cannot be
   shortened without changing what is being agreed to. Leaving the button at
   the bottom of that means scrolling past everything to act on it, and then
   scrolling back up to fix whatever the error names. Sticky keeps the one
   thing they came to press within reach the whole way down.

   The shadow only appears where there is content behind it, which is what
   stops it looking like a drawn line at the end of a short screen. */
.v1-ja-foot{
  position:sticky;bottom:0;z-index:1;
  background:var(--page);padding-top:14px;
  box-shadow:0 -14px 18px -14px rgba(13,20,32,.22);
}
