/* ============================================================================
   V2PRO — TIER A: showcase surfaces
   Depends on tokens.css + base.css. Loaded after components.css.

   Everything here is for the screens a person *looks at* rather than works in:
   login, the two dashboards, the subscription drawer, store, wallet. Glass,
   22px radii, gradient numerals, ambient motion.

   Tier B (tables, forms, the command palette) lives in components.css and
   deliberately gets none of this. The dividing rule is legibility, not taste:
   backdrop blur under 11–13px Persian glyphs destroys them.

   Every animation here is switched off by the reduced-motion block in
   base.css, which sets durations to 1ms rather than 0 so transitionend still
   fires. Anything that must stop entirely is listed at the bottom of this file.
   ========================================================================== */

/* ============================================================================
   Ambient scene — the drifting backdrop
   One fixed layer behind the content, pointer-transparent. Two blurred orbs
   and a perspective grid floor. No WebGL, no canvas, no library: three
   elements and two keyframes, so it costs nothing on a phone.
   ========================================================================== */

.scene {
  position: fixed;
  inset: 0;
  z-index: var(--z-scene);
  overflow: hidden;
  pointer-events: none;
  perspective: 900px;
}

.scene__orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: var(--scene-orb-opacity);
  will-change: transform;
}

.scene__orb:nth-child(1) {
  width: 420px;
  height: 420px;
  background: var(--scene-orb-1);
  top: -140px;
  inset-inline-end: -120px;
  animation: orb-a 16s var(--ease-out) infinite alternate;
}

.scene__orb:nth-child(2) {
  width: 360px;
  height: 360px;
  background: var(--scene-orb-2);
  bottom: -160px;
  inset-inline-start: -140px;
  animation: orb-b 20s var(--ease-out) infinite alternate;
}

@keyframes orb-a { to { transform: translate(-60px, 80px) scale(1.15); } }
@keyframes orb-b { to { transform: translate(70px, -60px) scale(.9); } }

/* Perspective grid. Masked to fade upward so it reads as a floor receding
   rather than a rectangle of lines. */
.scene__floor {
  position: absolute;
  left: 50%;
  bottom: -30px;
  width: 220vw;
  height: 42vh;
  transform: translateX(-50%) perspective(560px) rotateX(74deg);
  transform-origin: bottom center;
  opacity: var(--scene-grid-opacity);
  background:
    linear-gradient(transparent 0, transparent calc(100% - 1px), var(--brand-line) 0) 0 0 / 64px 64px,
    linear-gradient(90deg, transparent 0, transparent calc(100% - 1px), var(--violet-soft) 0) 0 0 / 64px 64px;
  -webkit-mask: linear-gradient(to top, rgba(0, 0, 0, .55) 0, transparent 85%);
  mask: linear-gradient(to top, rgba(0, 0, 0, .55) 0, transparent 85%);
  animation: floor-drift 3.2s linear infinite;
}

@keyframes floor-drift { to { background-position: 0 64px, 0 0; } }

/* ============================================================================
   Panel — the glass showcase card
   ========================================================================== */

.panel {
  position: relative;
  background: var(--panel-bg);
  border: 1px solid var(--panel-line);
  border-radius: var(--panel-radius);
  padding: var(--panel-pad);
  box-shadow: var(--panel-shadow);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
}

.panel__head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  font-size: var(--fs-13);
  font-weight: 600;
  color: var(--ink-2);
}

.panel__head svg {
  flex: none;
  color: var(--brand);
}

/* Pushes trailing content (a badge, a link) to the far edge of the header. */
.panel__head-end { margin-inline-start: auto; }

/* Hero variant: a slow conic sweep around the border. Drawn with a masked
   pseudo-element so it never inherits into content, and animated through a
   registered custom property so the browser interpolates the angle instead of
   restyling on every frame. */
@property --sweep {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.panel--hero::before {
  content: '';
  position: absolute;
  inset: -1.5px;
  border-radius: calc(var(--panel-radius) + 1.5px);
  padding: 1.5px;
  pointer-events: none;
  background: conic-gradient(
    from var(--sweep),
    transparent 0 10%,
    var(--violet) 22%,
    var(--brand) 32%,
    transparent 44% 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: sweep 6s linear infinite;
}

@keyframes sweep { to { --sweep: 360deg; } }

/* ============================================================================
   Numerals — the display figures
   Gradient-clipped, so the one number that matters on a screen is unmistakably
   the one number that matters.
   ========================================================================== */

.numeral {
  display: block;
  font-size: var(--fs-56);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -2px;
  background: var(--grad-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 2px 16px rgba(34, 211, 238, .35));
}

.numeral--sm { font-size: var(--fs-44); letter-spacing: -1px; }

/* A threshold numeral drops the gradient entirely rather than tinting it.
   A gradient that runs from violet into amber reads as decoration; a flat
   amber number reads as a warning. */
.numeral[data-tone='warn'],
.numeral[data-tone='crit'] {
  background: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  filter: none;
}

.numeral[data-tone='warn'] { color: var(--warn); }
.numeral[data-tone='crit'] { color: var(--crit); }

.numeral-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
}

.numeral-row__unit {
  font-size: var(--fs-13);
  color: var(--ink-3);
}

/* ============================================================================
   Gauge — the circular usage dial
   The SVG geometry is built by ui/chart.js; this styles it.
   ========================================================================== */

.gauge {
  position: relative;
  width: var(--gauge-size);
  height: var(--gauge-size);
  margin-inline: auto;
  flex: none;
}

.gauge > svg {
  width: 100%;
  height: 100%;
  /* The arc starts at 12 o'clock and fills clockwise regardless of document
     direction. Usage is not a reading order. */
  transform: rotate(-90deg);
}

.gauge__track {
  fill: none;
  stroke: var(--gauge-track);
  stroke-width: var(--gauge-stroke);
}

/* The gradient reference is deliberately NOT set here. Two gauges on one page
   need two gradient ids, so chart.js generates them and sets `stroke` as a
   presentation attribute. A `stroke: url(#gaugeGrad)` declaration in this file
   would win over that attribute and point every dial at one hardcoded id —
   which renders the arc invisible rather than mis-coloured, because the
   reference simply fails to resolve. */
.gauge__fill {
  fill: none;
  stroke-width: var(--gauge-stroke);
  stroke-linecap: round;
  transition: stroke-dashoffset 1.1s var(--ease-out);
}

/* The arc is a meaningful graphic, so it owes 3:1 against its surroundings.
   Driving the SVG stops from the same tokens as every other gradient means it
   darkens in light mode instead of fading to a 1.9:1 cyan on white. */
.gauge__from { stop-color: var(--grad-from); }
.gauge__to { stop-color: var(--grad-to); }

/* Threshold override: at 75% and 90% the dial stops being decorative. */
.gauge[data-tone='warn'] .gauge__fill { stroke: var(--warn); }
.gauge[data-tone='crit'] .gauge__fill { stroke: var(--crit); }

.gauge__glow {
  position: absolute;
  inset: 9px;
  border-radius: 50%;
  pointer-events: none;
  box-shadow:
    0 0 36px rgba(34, 211, 238, .3),
    inset 0 0 26px rgba(34, 211, 238, .16);
  animation: gauge-breathe 3.5s var(--ease-out) infinite;
}

.gauge[data-tone='warn'] .gauge__glow,
.gauge[data-tone='crit'] .gauge__glow { box-shadow: none; }

@keyframes gauge-breathe {
  0%, 100% { opacity: .4; }
  50% { opacity: 1; }
}

.gauge__center {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  gap: 2px;
}

.gauge__value {
  font-size: var(--fs-34);
  font-weight: 700;
  letter-spacing: -1px;
  color: var(--ink-1);
}

.gauge__label {
  font-size: var(--fs-12);
  color: var(--ink-3);
}

/* ============================================================================
   Tile — the icon-over-value stat block
   Denser and flatter than the operator .stat card: four of these sit in a row
   under a hero, where they are a readout rather than the subject.
   ========================================================================== */

.tilegrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-2);
}

.tile {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: var(--sp-3) var(--sp-2);
  text-align: center;
  background: var(--chip-bg);
  border: 1px solid var(--panel-line);
  border-radius: var(--r-3);
}

.tile svg {
  color: var(--brand);
  margin-bottom: 2px;
}

.tile__value {
  font-size: var(--fs-16);
  font-weight: 700;
  color: var(--ink-1);
}

.tile__label {
  font-size: var(--fs-11);
  color: var(--ink-3);
}

.tile[data-tone='ok'] svg { color: var(--ok); }
.tile[data-tone='warn'] svg { color: var(--warn); }
.tile[data-tone='crit'] svg { color: var(--crit); }
.tile[data-tone='violet'] svg { color: var(--violet); }

@media (max-width: 640px) {
  .tilegrid { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================================
   Glow chip — status as a pill with a lit dot
   ========================================================================== */

.statuschip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 7px 14px;
  border-radius: var(--r-pill);
  background: var(--chip-bg);
  border: 1px solid var(--chip-line);
  font-size: var(--fs-13);
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
}

.statuschip__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  background: currentcolor;
  box-shadow: 0 0 10px currentcolor;
}

.statuschip[data-tone='ok'] { color: var(--ok); }
.statuschip[data-tone='warn'] { color: var(--warn); }
.statuschip[data-tone='neg'] { color: var(--neg); }
.statuschip[data-tone='crit'] { color: var(--crit); }
.statuschip[data-tone='brand'] { color: var(--brand); }

/* A live dot pulses; a settled one does not. The distinction carries meaning —
   it is the difference between "online now" and "status: active". */
.statuschip[data-live] .statuschip__dot { animation: chip-pulse 2s var(--ease-out) infinite; }

@keyframes chip-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .55; transform: scale(.86); }
}

/* ============================================================================
   Link list — the copyable URLs a reseller hands to a paying customer

   Each row was its own lit box: three stacked haloes down the drawer, all
   shouting equally, none of them the thing you came for. And each was a single
   line — label above, then URL and copy button fighting for the same 500px, so
   a 60-character subscription URL was cut off at the ellipsis while the button
   sat in a pool of empty space. (Its side of the padding was the wide one:
   `padding: 5px 5px 5px var(--sp-3)` reads as physical left, which in an RTL
   document is the button's side, not the text's.)

   One lit container, hairline-separated rows, and the URL gets the full width
   on its own line beneath a label/copy header. The glow is spent once, on the
   list, so it still says "this is the thing you came for" without three of
   them saying it at the same volume.
   ========================================================================== */

.linklist {
  border-radius: var(--r-3);
  background: var(--bg-1);
  border: 1px solid var(--brand-line);
  box-shadow: 0 0 26px -14px rgba(34, 211, 238, .55), inset 0 0 20px rgba(34, 211, 238, .04);
  overflow: hidden;
  transition: border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}

.linklist:focus-within {
  border-color: var(--brand);
  box-shadow: 0 0 32px -12px rgba(34, 211, 238, .7), inset 0 0 22px rgba(34, 211, 238, .07);
}

.linkfield {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 6px var(--sp-2);
  padding: var(--sp-3);
  transition: background-color var(--dur-1) var(--ease-out);
}

.linkfield + .linkfield { border-top: 1px solid var(--line-1); }

/* The row's hover affordance is the URL sharpening, not a tint behind it. A
   tint would sit under 11px brand-coloured text, and in the light theme every
   available tint drops that pairing below AA — --brand measures 4.86:1 on the
   list surface and 4.34:1 through a 6% chip fill. */
.linkfield:hover .linkfield__value { color: var(--ink-1); }

.linkfield__label {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: var(--fs-11);
  font-weight: 600;
  color: var(--brand);
}

.linkfield__label svg { flex: none; width: 13px; height: 13px; }

/* The name may be a customer's display name, which can be anything. */
.linkfield__label span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A real input, not a div: it is selectable, focusable, and readable by a
   screen reader as the value it is. Readonly rather than disabled so it can
   still be focused and copied.

   The selector is compound to outrank base.css, which gives every input a 38px
   box with an inset shadow and every `input[readonly]` a pale surface of its
   own. Both are right inside a form and wrong here: they drew a second, sunken
   field around a URL that is already sitting in a lit one. */
.linkfield .linkfield__value {
  grid-column: 1 / -1;
  width: 100%;
  min-width: 0;
  min-height: 0;
  border: 0;
  background: none;
  box-shadow: none;
  padding: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  line-height: 1.7;
  color: var(--ink-2);
  text-overflow: ellipsis;
  direction: ltr;
  text-align: left;
  cursor: text;
}

.linkfield .linkfield__value:focus {
  outline: none;
  box-shadow: none;
  color: var(--ink-1);
}

/* Copy control. It was an icon alone inside the field, which read as a stray
   glyph rather than a button — and confirmed only through a toast that appears
   at the other end of the screen. Labelled, bounded, and it answers in place.
   The fill stays transparent for the contrast reason above; hover moves the
   border and puts the ring *outside* it, where it is not behind the label. */
.copybtn {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px var(--sp-2);
  border-radius: var(--r-1);
  background: none;
  border: 1px solid var(--chip-line);
  color: var(--brand);
  font-size: var(--fs-11);
  font-weight: 600;
  white-space: nowrap;
  transition:
    border-color var(--dur-1) var(--ease-out),
    box-shadow var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}

.copybtn svg { flex: none; width: 13px; height: 13px; }

.copybtn:hover {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}

.copybtn[data-done='1'],
.copybtn[data-done='1']:hover {
  color: var(--ok);
  border-color: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-soft);
}

/* ============================================================================
   Protocol badge — VLESS / VMESS / TROJAN
   ========================================================================== */

.proto {
  flex: none;
  padding: 4px var(--sp-2);
  border-radius: var(--r-1);
  background: var(--chip-bg);
  border: 1px solid var(--chip-line);
  /* --brand, not --brand-2: at 10px this owes AA, and --brand-2 over the chip
     fill measures 3.32:1 on a light card. --brand clears at 4.65:1. */
  color: var(--brand);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  direction: ltr;
}

/* ============================================================================
   App grid — one-tap import targets
   ========================================================================== */

.applinks {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2);
}

.applink {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: var(--sp-3) var(--sp-2);
  text-align: center;
  border-radius: var(--r-3);
  background: var(--chip-bg);
  border: 1px solid var(--panel-line);
  color: var(--ink-1);
  transition: border-color var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}

.applink:hover {
  border-color: var(--brand);
  color: var(--ink-1);
  transform: translateY(-2px);
}

.applink svg { color: var(--brand); margin-bottom: var(--sp-1); }
.applink__name { font-size: var(--fs-13); font-weight: 600; }
.applink__plat { font-size: var(--fs-11); color: var(--ink-3); }

@media (max-width: 640px) {
  .applinks { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================================
   Gradient call to action
   One per screen. The moment there are two, neither is primary.
   ========================================================================== */

.btn--grad {
  background: var(--grad-brand);
  color: var(--grad-ink);
  border: 0;
  box-shadow: 0 10px 26px -8px rgba(34, 211, 238, .45);
}

.btn--grad:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 14px 30px -8px rgba(34, 211, 238, .6);
}

.btn--grad:active:not(:disabled) { transform: translateY(0); }

/* ============================================================================
   Hero layout — gauge beside its readout
   ========================================================================== */

.hero {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-wrap: wrap;
}

.hero__info {
  flex: 1;
  min-width: 230px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  align-items: flex-start;
}

.hero__meta {
  display: grid;
  gap: var(--sp-1);
  font-size: var(--fs-13);
  color: var(--ink-3);
}

.hero__meta b {
  color: var(--ink-1);
  font-weight: 500;
}

@media (max-width: 560px) {
  .panel { padding: var(--sp-4); border-radius: var(--r-4); }
  .gauge { --gauge-size: 150px; }
  .gauge__value { font-size: var(--fs-26); }
  .numeral { font-size: var(--fs-44); letter-spacing: -1px; }
  .hero__info { min-width: 0; }
}

/* ============================================================================
   Motion opt-out
   base.css collapses durations to 1ms globally, which handles transitions.
   These are infinite animations — they have to stop outright, not run fast.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .scene__orb,
  .scene__floor,
  .panel--hero::before,
  .gauge__glow,
  .statuschip[data-live] .statuschip__dot {
    animation: none;
  }

  .gauge__fill { transition: none; }
  .applink:hover, .btn--grad:hover:not(:disabled) { transform: none; }
}
