/* Snowpack — instrument surface.
   Type: one grotesque for labels, one monospace for every figure. Tabular
   numerals are mandatory: values update live, and proportional digits make a
   column jitter every time a 1 becomes an 8. */
:root{
  --bg:#04070d; --pnl:#080d16; --pnl2:#0b1220; --rule:#1a2635; --rule2:#243244;
  --ink:#e8eef6; --mut:#8ea0b6; --dim:#5d6e84; --faint:#3d4c5e;
  --ok:#3ddc97; --warn:#f0b429; --bad:#ff5a5f; --info:#4fb8ff;
  --mono:ui-monospace,"SF Mono","JetBrains Mono","DejaVu Sans Mono",Menlo,Consolas,monospace;
  --sans:system-ui,-apple-system,"Segoe UI","Liberation Sans",sans-serif;
  --gut:14px;
  /* ── SHAPE, MOTION AND STATE ───────────────────────────────────────────
   * The three things that separate an interface that feels considered from
   * one that feels assembled — and all three were missing, not wrong.
   *
   * RADIUS. Everything was 1-4px. That is a deliberate look — technical,
   * sharp, instrument-like — and it is the wrong one here. A 2px corner reads
   * as a boundary being enforced; an 8-10px corner reads as a surface. This
   * app is read one-handed on a chairlift, and softer geometry is calmer to
   * scan. Three steps only, because a fourth is a decision nobody can make
   * consistently at two in the morning.
   *
   * MOTION. There were eight transitions in the entire application. Every
   * other state change — opening a panel, switching a view, pressing a
   * control — happened in a single frame. Instant is not fast; instant is
   * jarring, because the eye has to re-find what moved. A hundred and forty
   * milliseconds on a decelerating curve is below the threshold where anyone
   * would call it slow and above the one where the change is legible.
   *
   * STATE. Hover and press were signalled by changing BORDER COLOUR, which is
   * a one-pixel event on a dark ground and is invisible on a phone, where
   * there is no hover at all. State now lifts the SURFACE — a faint wash that
   * reads instantly at any size and degrades gracefully to nothing on touch.
   */
  --r-sm:6px; --r:10px; --r-lg:14px;
  --dur:.14s; --dur-slow:.28s;
  --ease:cubic-bezier(.22,.61,.36,1);          /* decelerating; nothing overshoots */
  --lift:rgba(146,180,222,.07);                /* hover wash */
  --lift-2:rgba(146,180,222,.13);              /* pressed / active wash */
  --ring:0 0 0 2px rgba(79,184,255,.38);       /* one focus treatment, everywhere */
  --shadow:0 8px 28px rgba(2,5,11,.5);
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}

/* The card sizes its own columns.
   These breakpoints used to read the viewport, which is the wrong ruler: on a
   1440px desktop the card is 490px wide in its column, so a "viewport is wide"
   rule gave it six columns of eighty pixels and figures broke mid-number.
   A container query asks the only question that matters — how wide is the
   card — and gives the same answer on a phone and in a narrow panel. */
.pnl{background:var(--pnl);border:1px solid var(--rule);border-radius:var(--r);overflow:hidden;
  container-type:inline-size;container-name:card}

/* Identification strip — who and where, in the notation the audience writes. */
.pnl-id{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;
  padding:11px var(--gut);border-bottom:1px solid var(--rule2);background:var(--pnl2)}
.pnl-id h2{font-size:16px;font-weight:650;letter-spacing:-.01em;line-height:1.2}
.idm{display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:4px;
  font-family:var(--mono);font-size:10.5px;color:var(--dim);letter-spacing:.02em}
.idm i{width:1px;height:9px;background:var(--rule2);display:inline-block}
.idm .stn{color:var(--info)}
.idr{display:flex;align-items:center;gap:6px;flex-shrink:0}

.tag{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.09em;
  padding:3px 6px;border-radius:var(--r-sm);border:1px solid transparent}
.tag-station{color:#04120b;background:var(--ok)}
/* An absence of data is not an ALARM.
 *
 * This was a red chip on a red-tinted field. The reasoning behind it was
 * sound as far as it went — a green badge over a panel of dashes would read
 * as "measured, and the mountain is quiet", which is the opposite of the
 * truth — but the cure was worse. Red is the colour this app reserves for
 * danger: avalanche conditions, a warning in force, a reading that should
 * change what somebody does today. A station simply not reporting is none of
 * those. Painting it red tells a reader something is WRONG on the mountain
 * when the only thing wrong is our telemetry, and it spends the alarm colour
 * on the most common state a remote resort is ever in.
 *
 * So: no fill, no frame, no alarm. Absence reads as absence — quiet, dimmed,
 * and stated in words next to the dashes it explains. */
.tag-none{background:transparent;color:var(--mut);border-color:transparent;
  letter-spacing:.06em}
.tag-model{color:#031320;background:var(--info)}
.tag-forecast{color:var(--mut);border-color:var(--rule2)}
.tag-resort{color:#1a1204;background:var(--warn)}
.age-fresh{color:var(--ok);border-color:rgba(61,220,151,.35)}
.age-aging{color:var(--warn);border-color:rgba(240,180,41,.35)}
/* Same rule as .tag-none above: an old reading is not a hazard, it is an old
   reading. It is dimmed and dated, not alarmed. */
.age-stale{color:var(--mut);border-color:var(--rule2)}

/* Icon buttons carry no frame.
 *
 * This was a 27px bordered square with a glyph inside it — the exact shape
 * that has been called out repeatedly, and the reason is worth writing down
 * rather than re-deriving: a frame is how an interface admits its icon is not
 * legible on its own. If a reader needs a box to know the mountain symbol is
 * pressable, the symbol is wrong. Fix the symbol.
 *
 * So the frame goes and the affordance moves to where it belongs — the glyph
 * brightens on hover, and the target grows to 44px so a gloved thumb still
 * finds it while the mark on screen stays small. */
.ib{width:44px;height:44px;border:0;border-radius:0;background:transparent;
  color:var(--mut);display:grid;place-items:center;cursor:pointer;
  transition:color .12s ease, transform .12s ease}
.ib:hover{color:var(--info)}
.ib:active{transform:scale(.92)}
.ib:focus-visible{outline:1.5px solid var(--info);outline-offset:2px}

.grp{border-bottom:1px solid var(--rule)}
.grp:last-of-type{border-bottom:0}
.grp h3{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);padding:8px var(--gut) 0}

/* Data cells: fixed columns so figures line up across rows and panels. */
.cells{display:grid;gap:1px;background:var(--rule);margin-top:8px}
.c6{grid-template-columns:repeat(2,1fr)}
@container card (min-width:400px){.c6{grid-template-columns:repeat(3,1fr)}}
@container card (min-width:700px){.c6{grid-template-columns:repeat(6,1fr)}
  .c6 .wide{grid-column:span 1}}
.c{background:var(--pnl);padding:8px var(--gut) 10px;min-width:0}
.c-l{display:block;font-family:var(--mono);font-size:9px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--faint);font-weight:700}
.c-v{display:block;font-family:var(--mono);font-size:17px;font-weight:600;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;margin-top:3px;
  /* Break between a figure and its unit, never inside the figure.
     `overflow-wrap:anywhere` split a ceiling of 3,109 m across two lines as
     "3,10" and "9 m" — and "3,10" reads as a whole, wrong number. A reading
     that is silently incorrect is far worse than one that wraps. */
  overflow-wrap:normal;word-break:normal}
.c.bad .c-v,.c.warn .c-v,.c.ok .c-v{font-size:14px;line-height:1.25}
.c-s{display:block;font-size:10.5px;color:var(--dim);margin-top:2px;line-height:1.3}
.c.ok .c-v{color:var(--ok)} .c.warn .c-v{color:var(--warn)} .c.bad .c-v{color:var(--bad)}
.c-s .ok{color:var(--ok)} .c-s .na,.na{color:var(--faint)}
.tr{font-size:9px;margin-left:5px;vertical-align:middle}
/* A falling number is not a warning. Red on a downward trend is a stock
   ticker idiom, and it does not transfer: snow depth settling is what snow
   depth does, every day, and flagging it in the hazard colour tells a reader
   to worry about the ordinary. Direction is carried by the arrow. */
.tr.up{color:var(--ok)} .tr.dn{color:var(--dim)} .tr.fl{color:var(--dim)}

/* Vertical profile as a real table — this is a reading task, not a graphic. */
.prof{width:100%;border-collapse:collapse;margin-top:8px;font-family:var(--mono);font-size:11.5px}
.prof th{font-size:9px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);
  font-weight:700;text-align:left;padding:0 var(--gut) 6px}
.prof th:not(:first-child),.prof td:not(:first-child){text-align:right}
.prof th.pb,.prof td.pb{width:34%}
.prof td{padding:5px var(--gut);border-top:1px solid var(--rule)}
.prof .bn{font-family:var(--sans);font-weight:600;font-size:12px;text-align:left}
.prof .num{font-variant-numeric:tabular-nums}
.prof .str{font-weight:700;color:var(--ink)}
.prof .warn{color:var(--warn)}
/* A zero value must draw nothing; a 2px stub reads as 'a little snow'. */
.prof td.pb i{display:block;height:5px;border-radius:var(--r-sm);
  background:linear-gradient(90deg,var(--info),var(--ok))}
.prof td.pb{padding-left:4px;padding-right:4px}

.note{font-size:11px;color:var(--dim);padding:7px var(--gut) 10px}
.note b{color:var(--ink);font-family:var(--mono)}
.note.prob{color:var(--warn)}

/* Danger rose */
.avy-wrap{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start;padding:10px var(--gut) 12px}
.rose{flex-shrink:0}
.avy-meta{flex:1;min-width:170px}
.peak{display:flex;align-items:baseline;gap:9px;padding-bottom:4px}
.peak .pl{font-family:var(--mono);font-size:30px;font-weight:700;line-height:1;color:var(--dc)}
.peak .pn{font-size:13px;font-weight:600;color:var(--ink)}
.dl{list-style:none;margin-top:9px;display:grid;gap:3px}
.dl li{display:flex;align-items:center;gap:7px;font-size:10.5px;color:var(--mut)}
.dl i{width:10px;height:10px;border-radius:var(--r-sm);flex-shrink:0}
.dl span{font-family:var(--mono);color:var(--dim);width:8px}
.dl em{font-style:normal}

.pnl-ft{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:7px var(--gut);
  border-top:1px solid var(--rule2);background:var(--pnl2);
  font-family:var(--mono);font-size:9.5px;letter-spacing:.05em;color:var(--dim)}
.pnl-ft i{width:1px;height:9px;background:var(--rule2)}
/* The third copy of the stale-equals-alarm mistake (see .tag-none and
   .age-stale). An old reading is old, not dangerous. */
.pnl-ft .fresh{color:var(--ok)} .pnl-ft .aging{color:var(--warn)} .pnl-ft .stale{color:var(--mut)}


/* At-a-glance. Consumer-readable, sitting above the instrument detail rather
   than replacing it — a skier gets the answer, a forecaster scrolls on. */
.glance{display:flex;flex-wrap:wrap;gap:14px;align-items:center;justify-content:space-between;
  padding:13px var(--gut);border-bottom:1px solid var(--rule)}
.g-main{display:flex;align-items:center;gap:13px;min-width:0}
.g-ico{width:46px;height:46px;flex-shrink:0;color:#cfe0f0}
.g-temp{font-family:var(--mono);font-size:30px;font-weight:600;line-height:1;
  font-variant-numeric:tabular-nums;letter-spacing:-.03em}
.g-word{font-size:13px;color:var(--mut);margin-top:3px}
.g-chips{display:grid;grid-template-columns:repeat(2,minmax(78px,1fr));gap:1px 14px;flex:1;min-width:190px}
@media(min-width:560px){.g-chips{grid-template-columns:repeat(4,auto);justify-content:end;gap:0 20px}}
.g-chip{display:flex;flex-direction:column;gap:1px;min-width:0}
.g-chip span{font-family:var(--mono);font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);font-weight:700}
.g-chip b{font-family:var(--mono);font-size:15px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.g-chip.good b{color:var(--ok)} .g-chip.warn b{color:var(--warn)}

/* "As of" sits inside its section heading, in the heading's own voice —
   context for the numbers below it, not an alarm about the whole card. */
.grp h3 .asof{font-family:var(--mono);font-size:9px;font-weight:600;letter-spacing:.06em;
  text-transform:none;color:var(--faint);margin-left:8px}
.grp h3 .asof.old{color:var(--warn)}

/* Provenance as a word in the identity line, not a coloured box.
   It still distinguishes a measured reading from a modelled one — that
   distinction is real — but it stops shouting from the corner. */
.idm .src{color:var(--dim)}
.idm .src-station{color:var(--ok)}
/* Not the alarm colour — the same reasoning as .tag-none above, which was
   fixed while THIS rule quietly kept painting the identity line red. Two
   places encoding one idea is how a fix half-lands: the badge went quiet and
   the header did not. Red is reserved for hazard. A station that is not
   reporting is an absence, and it reads as one. */
.idm .src-none{color:var(--mut)}

/* Conditions lead the card. The picture follows the numbers. */
.pnl > .glance:first-child{border-top:0}
.pnl .hero{margin-top:0}


/* ══════════════════════════════════════════════════════ the resort hero
 *
 * One cinematic frame, at the render's OWN aspect ratio.
 *
 * This replaces two stacked bands — a 104px photo strip and a 132px synthetic
 * sky — that between them gave a production render 104 pixels of height and
 * cropped it top and bottom. Both of those decisions were wrong once real
 * images existed, and the cropping one was actively destructive: every render
 * carries its name-and-logo lockup set into the bottom of the frame, and a
 * centre-cropped 3:2 image cuts it in half.
 *
 * So the frame holds 3:2 exactly and the image is never cropped. That costs
 * real estate and is worth it — this is the one place the product gets to
 * look like the mountain instead of like a dashboard.
 */
.hero{
  position:relative; margin:0; overflow:hidden;
  aspect-ratio:3/2;
  background:linear-gradient(180deg, var(--sky-top,#16274a), var(--sky-hor,#33507a));
  border-bottom:1px solid var(--rule2);
}
/* On a wide desktop card the full 3:2 becomes taller than the readings beside
   it, so the frame is capped and the image is allowed to crop — but only from
   the TOP, which is sky. The lockup at the bottom survives either way. */
@media(min-width:900px){
  .hero{ max-height:340px; }
  .hero-img{ object-position:center bottom; }
}
.hero-img{
  position:relative; z-index:1;
  width:100%; height:100%; object-fit:cover; object-position:center;
  display:block;
}
.hero[data-noimg="1"] .hero-img{ display:none }

/* The grade and the dim sit ABOVE the image and below the text. Blend modes
   are what make this read as light rather than as a coloured sheet laid on
   top: overlay warms without washing out, multiply deepens without muddying. */
.hero-grade,.hero-dim{ position:absolute; inset:0; z-index:2; pointer-events:none }
.hero-dim{ background:#050a14 }

/* Falling weather, drawn only when something is falling. In front of the
   scene, which is where it actually is. */
.hero-fall{ position:absolute; inset:0; z-index:3; pointer-events:none; opacity:.6 }
/* Snow in THREE layers, not one.
 *
 * A single repeating dot grid is unmistakably a grid: the eye finds the
 * lattice within a second and the illusion is gone, which over a photographic
 * render looks worse than no snow at all. Real falling snow has depth — near
 * flakes are larger, brighter and fall faster; distant ones are small, faint
 * and almost hang. Three layers at different scales, speeds and opacities
 * beat against each other so no repeat is visible, and the parallax between
 * them is what reads as depth rather than as texture. */
.hero-fall.snow{
  background-image:
    radial-gradient(rgba(255,255,255,.95) .9px, transparent 1.3px),
    radial-gradient(rgba(255,255,255,.6)  .7px, transparent 1.1px),
    radial-gradient(rgba(255,255,255,.38) .6px, transparent .9px);
  background-size:97px 121px, 61px 83px, 43px 57px;
  background-position:0 0, 23px 41px, 47px 13px;
  animation:hero-snow-a 7.5s linear infinite,
            hero-snow-b 11s linear infinite,
            hero-snow-c 16s linear infinite;
  opacity:.72;
}
.hero-fall.rain{
  background-image:linear-gradient(150deg, transparent 46%, rgba(200,224,245,.75) 50%, transparent 54%);
  background-size:14px 22px; animation:hero-rain .55s linear infinite;
}
/* Each layer drifts on its own vector, so they never re-align into a pattern.
   The slight horizontal component is the wind; straight-down snow reads as
   rain. */
@keyframes hero-snow-a{ to{ background-position:34px 484px, 23px 41px, 47px 13px } }
@keyframes hero-snow-b{ to{ background-position:0 0, 46px 456px, 47px 13px } }
@keyframes hero-snow-c{ to{ background-position:0 0, 23px 41px, 65px 399px } }
@keyframes hero-rain{ to{ background-position:-40px 220px } }
@media (prefers-reduced-motion:reduce){ .hero-fall{ animation:none } }

/* THE READINGS GO TOP-LEFT, AND THE BOTTOM IS A PROTECTED ZONE.
 *
 * Every render places its lockup bottom-centre. Nothing this app draws may
 * land there — not a scrim, not a label, not a badge. Top-left is safe on all
 * of them, because a photograph of a mountain has sky in the top corners. */
.hero-read{
  position:absolute; z-index:4; left:0; right:0; top:0;
  display:flex; flex-wrap:wrap; gap:3px 12px; align-items:baseline;
  padding:9px 12px 22px;
  font-family:var(--mono); font-size:10.5px; letter-spacing:.05em;
  color:#eaf2fb; text-shadow:0 1px 3px rgba(3,7,14,.85);
  /* A short wash under the text only — reaching a third of the way down at
     most, so it never approaches the lockup. */
  background:linear-gradient(180deg, rgba(4,9,18,.62), rgba(4,9,18,.22) 58%, transparent);
  pointer-events:none;
}
.hero-when{ font-weight:700 }
.hero-warn{ color:var(--warn) }

/* The drawn fallback for a resort with no render yet: the true skyline of
   that range from its own elevation model. */
.hero-skyline{ position:absolute; inset:0; z-index:0 }
.hero-skyline svg{ width:100%; height:100%; display:block }

/* ══════════════════════════════════════════════════ MOTION AND STATE LAYER
 *
 * One place that decides how every control in this app responds to being
 * touched. Written as a layer rather than sprinkled through each component,
 * because the failure mode being fixed is INCONSISTENCY — a segment control
 * that brightened its border, an icon button that changed colour, a legend
 * that appeared in a single frame, all invented separately.
 */

/* Everything that can be pressed shares one transition and one focus ring. */
button, .seg button, .views button, .ib, .brand, .l2-full, .l2-min, .l2-close,
.legend2-mark, .hits li, .m3d-retry, .grip{
  transition: background-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease),
              opacity var(--dur) var(--ease);
}
button:focus-visible, .ib:focus-visible, .brand:focus-visible,
.seg button:focus-visible, .views button:focus-visible, .legend2-mark:focus-visible{
  outline:none; box-shadow:var(--ring);
}

/* State is a SURFACE WASH, not a border colour. Works on a dark ground, works
   at a glance, and simply does not appear on a touch device — which is
   correct, because on a touch device there is no hover to report. */
@media (hover:hover){
  .seg button:hover, .views button:hover, .ib:hover,
  .l2-full:hover, .l2-min:hover, .l2-close:hover, .legend2-mark:hover{
    background-color:var(--lift);
  }
  .hits li:hover{ background-color:var(--lift) }
}
.seg button:active, .views button:active, .ib:active,
.legend2-mark:active, .brand:active{ background-color:var(--lift-2) }

/* A press is acknowledged physically. Small — 2% — because anything larger
   reads as a toy, and because a gloved thumb is imprecise enough that a
   control which moves much is a control that gets mis-hit. */
.ib:active, .brand:active, .m3d-retry:active{ transform:scale(.97) }

/* Panels arrive rather than appear. */
.legend2, .rprev, .hits{
  transition: opacity var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease);
}

/* ── ICONS ────────────────────────────────────────────────────────────────
 * One weight, one join, one cap, sized by the text beside them. They were
 * drawn at 15px, 20px, 24px and 27px with stroke widths from 1.4 to 2, which
 * is why a row of them never looked like a set. Inheriting colour is the
 * other half: an icon that specifies its own colour cannot participate in the
 * hover and active states above. */
svg[data-i], .ib svg, .seg svg, .views svg, .find svg{
  width:18px; height:18px; flex:0 0 auto;
  stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
  color:inherit;
}

/* ── REDUCED MOTION ───────────────────────────────────────────────────────
 * Honoured properly: transitions collapse to nothing rather than merely
 * shortening. Someone who asks for no motion has usually asked because
 * motion makes them ill, and a fast animation is still an animation. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    transition-duration:.01ms !important;
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    scroll-behavior:auto !important;
  }
}

/* The air row sits under the snow row, quieter by a step. Snow is why someone
   opened the app; the atmosphere is what they check second. Same chip shape,
   so the two rows read as one block rather than two competing bands. */
.g-air{margin-top:6px;opacity:.92}
.g-air .g-chip b{font-size:12.5px}
