/* Lessetlo stylesheet. Design reference: docs/design.md (D030, "control room" with tabs).
   Seasonal themes override the tokens only (public/css/themes/). */

@font-face {
  font-family: "IBM Plex Sans Condensed";
  src: url("/fonts/plex-condensed-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans Condensed";
  src: url("/fonts/plex-condensed-600.woff2") format("woff2");
  font-weight: 500 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans Condensed";
  src: url("/fonts/plex-condensed-700.woff2") format("woff2");
  font-weight: 700 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("/fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("/fonts/plex-mono-700.woff2") format("woff2");
  font-weight: 600 900;
  font-style: normal;
  font-display: swap;
}

/* The redrawn map's labels were measured in Overpass (D028): loaded only by the map. */
@font-face {
  font-family: Overpass;
  src: url("/fonts/overpass.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* D030 "control room": dark slate, light ink; green = live and open, amber = old picture and
     new info, red = closed and the rain/snow limit. One scheme, dark. */
  color-scheme: dark;
  --bg: #0f141a;
  --panel: #161d25;
  --rule: #26313c;
  --rule-strong: #5d6d7d;
  --ink: #e9eef3;
  --muted: #93a1b0;
  --accent: #46c37b;
  --danger: #ef6a5e;
  --ok: #46c37b;
  --warn: #f0a83a;
  --ground: #1d2731;
  /* The current tab in the phones' tab bar (owner 2026-10-24: the page's ground, darker). */
  --tab-current: var(--bg);
  --on-ink: #0f141a;
  --selection: #46c37b55;
  --sans: "IBM Plex Sans Condensed", "Arial Narrow", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --gutter: clamp(1rem, 2.5vw, 2rem);
  --bar-height: 4rem;
  --tabbar-height: 4rem;
}

/* D031: the light scheme, when the system asks for it or the visitor chose it (theme button,
   cookie `theme`, rendered as data-theme on <html>). Seasonal themes override both. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --bg: #f4f6f8;
    --panel: #ffffff;
    --rule: #d5dce3;
    --rule-strong: #7d8b99;
    --ink: #0f141a;
    --muted: #556270;
    --accent: #17733d;
    --danger: #b42318;
    --ok: #17733d;
    --warn: #a65200;
    --ground: #e2e8ee;
    --tab-current: #dde3ea;
    --on-ink: #ffffff;
    --selection: #17733d33;
  }
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f6f8;
  --panel: #ffffff;
  --rule: #d5dce3;
  --rule-strong: #7d8b99;
  --ink: #0f141a;
  --muted: #556270;
  --accent: #17733d;
  --danger: #b42318;
  --ok: #17733d;
  --warn: #a65200;
  --ground: #e2e8ee;
  --tab-current: #dde3ea;
  --on-ink: #ffffff;
  --selection: #17733d33;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

@view-transition {
  navigation: auto;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--bar-height) + 0.75rem);
}

body {
  margin: 0;
  min-height: 100dvh;
  min-width: 250px;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font: 400 1rem / 1.45 var(--sans);
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--selection);
}

main {
  flex: 1;
  width: 100%;
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 var(--gutter) 3rem;
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 700;
  line-height: 1.15;
  text-wrap: balance;
  overflow-wrap: anywhere;
}

h1 {
  font-size: clamp(1.8rem, 4vw, 2.5rem);
}

h2 {
  font-size: 1.3rem;
}

h3 {
  font-size: 1.1rem;
}

h1 a,
h2 a,
h3 a {
  color: inherit;
  text-decoration: none;
}

p {
  margin: 0 0 0.8rem;
}

a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

/* Every :hover sits under (hover: hover): a phone keeps the hover look after a tap, and a
   tapped button left green reads as enabled (owner 2026-10-27, the notification buttons). */
@media (hover: hover) {
  a:hover {
    color: var(--accent);
    text-decoration-color: currentColor;
  }
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  fill: currentColor;
  flex-shrink: 0;
}

time,
.num {
  font-variant-numeric: tabular-nums;
}

/* Utilities ------------------------------------------------------------------------------------ */

.muted {
  color: var(--muted);
}

.danger {
  color: var(--danger);
}

.success {
  color: var(--ok);
}

.highlight {
  color: var(--accent);
}

.note {
  font-size: 0.875rem;
  color: var(--muted);
}

.crossed {
  text-decoration-line: line-through;
  color: var(--muted);
}

[hidden],
.hidden {
  display: none !important;
}

/* Keyboard users jump over the site bar (WCAG 2.4.1); shown only while focused. */
.skip-link {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: var(--on-ink);
  font-weight: 700;
  text-decoration: none;
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: none;
}

main:focus {
  outline: none;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Site bar and tabs (D030) --------------------------------------------------------------------- */

.site-bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: stretch;
  gap: 0 1.75rem;
  width: 100%;
  min-height: var(--bar-height);
  padding: 0 var(--gutter);
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

.wordmark {
  align-self: center;
  font-family: var(--sans);
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  text-decoration: none;
}

@media (hover: hover) {
  .wordmark:hover {
    color: var(--ink);
  }
}

.wordmark .egg {
  height: 0.8em;
  width: auto;
  vertical-align: -0.05em;
}

.tabs {
  display: flex;
  gap: 0.25rem;
}

.tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.9rem;
  color: var(--muted);
  font-size: 1.05rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  /* No blue flash on a tap (owner 2026-10-22): the current tab's mark already answers. */
  -webkit-tap-highlight-color: transparent;
}

.tab .icon {
  display: none;
}

.tab .short {
  display: none;
}

@media (hover: hover) {
  .tab:hover {
    color: var(--ink);
  }
}

/* The tab fills the bar's height: an outer ring would be cut off by the viewport. */
.tab:focus-visible {
  outline-offset: -3px;
}

/* The current tab (rendered by the server for each path, D037; updated by main.js): a green
   line under its word only (owner 2026-10-24), which slides to the next tab (tabs.js). */
.tab[aria-current="page"] {
  color: var(--ink);
}

.tab-label {
  position: relative;
}

.tab-line {
  display: none;
}

.tab[aria-current="page"] .tab-line {
  position: absolute;
  right: 0;
  bottom: -0.55rem;
  left: 0;
  display: block;
  height: 2px;
  background: var(--accent);
  transform-origin: left;
}

/* New since the last visit (FE-85, FE-88, FE-91): the amber square, on the tab and on what
   changed inside it. */
.tab-new,
.new-mark {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  background: var(--warn);
}

.new-mark {
  margin-left: 0.15rem;
  vertical-align: 0.1em;
}

/* FE-91: under the comments heading, how many comments came since the last visit. */
.new-count {
  margin: 0.75rem 0 0;
  color: var(--warn);
  font-family: var(--sans);
  font-size: 0.85rem;
}

.new-count .new-mark {
  margin: 0 0.35rem 0 0;
}

.bar-status {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  margin: 0 0 0 auto;
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: 0.875rem;
  white-space: nowrap;
}

.bar-status a {
  flex-shrink: 0;
  text-decoration: none;
}

.bar-status .lifts strong {
  font-weight: 700;
}

.bar-status .lifts.open strong {
  color: var(--ok);
}

.bar-status .lifts.closed strong {
  color: var(--danger);
}

.bar-status .label,
.bar-status .wind {
  color: var(--muted);
}

.bar-status .short {
  display: none;
}

/* Narrow phones: "RM" (remontées mécaniques) on screen, the full word for screen readers. */
@media (max-width: 420px) {
  .bar-status .long {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .bar-status .short {
    display: inline;
  }
}

/* The smallest phones (FE-72): the number alone. */
@media (max-width: 299px) {
  .bar-status .short {
    display: none;
  }
}

.bar-status .lifts.unknown {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--warn);
}

/* INFO-12: the official list does not answer. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  max-width: 46rem;
  margin-top: 0.5rem;
  padding: 1rem 1.25rem;
  background: var(--panel);
  border: 1px solid var(--warn);
}

.notice > .icon {
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.1rem;
  color: var(--warn);
}

.notice p {
  margin: 0;
}

.notice .notice-title {
  margin-bottom: 0.3rem;
  font-weight: 700;
}

.share {
  display: inline-flex;
  align-items: center;
  align-self: center;
  min-height: 0;
  padding: 0.25rem;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}

@media (hover: hover) {
  .share:hover {
    color: var(--accent);
  }
}

.share.copied {
  color: var(--ok);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  align-self: center;
  min-height: 0;
  padding: 0.25rem;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

@media (hover: hover) {
  .theme-toggle:hover {
    color: var(--accent);
  }
}

.theme-toggle .icon {
  display: none;
}

.theme-toggle[data-choice="auto"] .icon:nth-child(1),
.theme-toggle[data-choice="light"] .icon:nth-child(2),
.theme-toggle[data-choice="dark"] .icon:nth-child(3) {
  display: block;
}

.site-bar:not(:has(.bar-status)) .theme-toggle {
  margin-left: auto;
}

/* BMC-11: the donation button of the site bar, the green of the footer button. */
.bar-donate {
  display: inline-flex;
  align-items: center;
  align-self: center;
  gap: 0.5rem;
  min-height: 2.25rem;
  padding: 0 0.9rem;
  background: var(--accent);
  color: var(--on-ink);
  font-weight: 700;
  white-space: nowrap;
  text-decoration: none;
}

@media (hover: hover) {
  .bar-donate:hover {
    background: var(--ink);
    color: var(--on-ink);
  }
}

.bar-donate .icon {
  width: 1.15rem;
  height: 1.15rem;
}

/* Tab panels (D030, D037): one shown at a time, the one named by data-tab on <html> (rendered by
   the server from the path, then updated by main.js). */
.tab-panel {
  display: none;
  padding-top: 1.25rem;
}

html[data-tab="webcams"] .panels > #webcams,
html[data-tab="infos"] .panels > #infos,
html[data-tab="meteo"] .panels > #meteo,
html[data-tab="commentaires"] .panels > #commentaires {
  display: block;
}

/* Page head ------------------------------------------------------------------------------------ */

.page-head {
  max-width: 46rem;
  padding: 1.5rem 0 1.25rem;
}

.home .page-head {
  max-width: none;
  padding: 0;
}

.home .page-head > * {
  margin-top: 1rem;
}

.page-head:empty {
  display: none;
}

.lede {
  margin: 0.75rem 0 0;
  font-size: 1.1rem;
  color: var(--muted);
}

.lede a {
  color: var(--ink);
}

/* FE-92, FE-58: what the site is and the install card, on the Webcams tab only. */
html[data-tab]:not([data-tab="webcams"]) :is(.home-intro, .install-card) {
  display: none;
}

.beta {
  display: inline-block;
  margin: 0.5rem 0 0;
  color: var(--warn);
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.tip {
  margin: 0;
  color: var(--muted);
}

.tip em {
  font-style: normal;
}

.tip a,
.tip .link-button {
  color: var(--ink);
}

/* FE-58: the install offer, a card at the top of the home page on touch devices. The button
   goes under the text so the text keeps the width of a phone (owner 2026-10-22). */
.install-card {
  display: grid;
  grid-template-areas:
    "icon text close"
    "icon action .";
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.6rem 0.75rem;
  align-items: start;
  padding: 0.75rem 0.25rem 0.75rem 0.75rem;
  border: 1px solid var(--rule);
  background: var(--panel);
}

.install-card img {
  grid-area: icon;
  width: 3rem;
  height: 3rem;
  border-radius: 22%;
}

.install-card p {
  display: flex;
  grid-area: text;
  flex-direction: column;
  margin: 0;
  line-height: 1.25;
}

.install-card span {
  color: var(--muted);
  font-size: 0.9rem;
}

.install-card .install {
  grid-area: action;
  justify-self: start;
  min-height: 2.4rem;
  padding: 0.35em 1.1em;
}

.install-card .install-close {
  grid-area: close;
  justify-content: center;
  width: 2.4rem;
  min-height: 2.4rem;
  margin-top: -0.4rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
}

.offline {
  padding: 1rem 0;
}

.offline-title {
  margin: 0 0 0.5rem;
  color: var(--danger);
  font-size: 1.6rem;
  font-weight: 700;
}

/* Buttons and forms ---------------------------------------------------------------------------- */

button {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  min-height: 2.6rem;
  padding: 0.45em 1.1em;
  border: 1px solid var(--ink);
  border-radius: 0;
  background: var(--ink);
  color: var(--on-ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

/* Not the share and link buttons: their accent text would vanish on an accent fill (review). */
@media (hover: hover) {
  button:not(
    .share,
    .link-button,
    .theme-toggle,
    .notify-bell,
    .install-close,
    .viewer button
  ):hover:not(:disabled) {
    background: var(--accent);
    border-color: var(--accent);
  }
}

button:disabled {
  border-color: var(--rule-strong);
  background: transparent;
  color: var(--muted);
  cursor: not-allowed;
}

.link-button {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-weight: 400;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  text-underline-offset: 0.18em;
  cursor: pointer;
}

@media (hover: hover) {
  .link-button:hover {
    background: none;
    color: var(--accent);
  }
}

.form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 40rem;
}

.form label {
  margin-top: 0.75rem;
  font-weight: 700;
}

textarea,
input[type="email"],
input[type="text"] {
  width: 100%;
  padding: 0.7em 0.8em;
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
}

/* Taller only: a wider field would stick out of its column (owner, 2026-10-22). */
textarea {
  resize: vertical;
}

/* FE-93 (owner, 2026-10-23): the characters used out of the limit, in the box's bottom corner, left
   of the resize grip (main.js wraps the box). The last line of text stays clear of it. */
.char-box {
  display: block;
  position: relative;
}

.char-box textarea[maxlength] {
  display: block;
  padding-bottom: 1.5rem;
}

.char-count {
  position: absolute;
  right: 1.1rem;
  bottom: 0.3rem;
  padding: 0 0.2rem;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  pointer-events: none;
}

textarea:focus,
input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: transparent;
}

.form-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  margin-top: 1rem;
}

.form-actions a {
  color: var(--muted);
  font-size: 0.9rem;
}

.cf-turnstile {
  margin-top: 1rem;
}

/* In the info form it comes after the buttons, as in the comments' (owner 2026-10-23: the same
   space above "Publier"); it takes room only when Cloudflare shows a challenge. */
.composer-form .cf-turnstile {
  margin-top: 0;
}

.composer-form .cf-turnstile:empty {
  display: none;
}

/* Cards: one look for every block of the board ------------------------------------------------- */

.readings,
.bulletin,
#commentaires:not(.tab-panel),
.aside-card {
  background: var(--panel);
  border: 1px solid var(--rule);
}

.bulletin {
  padding: 1.1rem 1.25rem;
}

.bulletin h3,
.card-title {
  margin: 0 0 0.2rem;
  font-size: 1.15rem;
}

/* Headings are titles followed by a hairline (owner, 2026-10-21, "Titre et filet": the small
   spaced capitals read as a template). */
.card-title {
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 700;
}

:target > h3 a,
:target > .bulletin-head h3 a,
:target > .list-head h3 a,
:target:not(.tab-panel) > h2 a {
  color: var(--accent);
}

/* Webcams panel -------------------------------------------------------------------------------- */

.cam-group + .cam-group {
  margin-top: 1.75rem;
}

.group-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 0.75rem;
  color: var(--ink);
  font-size: 1.15rem;
  font-weight: 700;
}

.group-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

/* Justified rows: each figure grows in proportion to its aspect ratio (--ratio, set inline), so
   images in a row share one height and fill the width. */
.webcam-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.875rem;
}

.webcam {
  position: relative;
  flex: var(--ratio) 1 calc(var(--ratio) * 12rem);
  min-width: 0;
  max-width: calc(var(--ratio) * 440px);
  margin: 0;
  background: var(--panel);
  border: 1px solid var(--rule);
}

.webcam:target {
  border-color: var(--ink);
}

.webcam .frame {
  position: relative;
  display: block;
  background: var(--bg);
}

.webcam img {
  display: block;
  width: 100%;
  height: auto;
}

/* The webcam's own shape, whatever picture is in it (owner, 2026-10-23: offline, the 600x350
   placeholder turned a panorama's frame into a huge box and broke the row). */
.webcam .frame > img,
.webcam .frame > picture > img {
  aspect-ratio: var(--ratio);
  object-fit: contain;
}

.webcam figcaption {
  padding: 0.55rem 0.85rem 0.6rem;
}

.cam-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 0.65rem;
}

.cam-name {
  flex: 0 1 auto;
  min-width: 0;
  font-weight: 700;
  text-decoration: none;
}

.webcam:target .cam-name {
  color: var(--accent);
}

.altitude {
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: 0.9rem;
  font-weight: 700;
  white-space: nowrap;
}

.seen {
  color: var(--muted);
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
  white-space: nowrap;
}

.seen.live {
  color: var(--ok);
}

.seen.live::before {
  /* Not read: the caption says "en direct" to screen readers (A11Y-03). */
  content: "● " / "";
}

.seen.stale {
  color: var(--warn);
}

/* PLAY-02: the time of the image of the day on screen, in the caption's own place. A live time's
   dot keeps its room, unseen: the time does not move when playback starts (owner 2026-10-22). */
.seen.replay {
  color: var(--ink);
}

.seen.replay::before {
  visibility: hidden;
}

.place {
  display: block;
  color: var(--muted);
  font-size: 0.9rem;
}

.webcam .share {
  margin-left: auto;
  font-size: 0.85rem;
}

@media (hover: hover) {
  .webcam .share {
    opacity: 0;
  }

  .webcam:hover .share,
  .webcam:focus-within .share,
  .webcam:target .share,
  .webcam .share.copied {
    opacity: 1;
  }
}

/* FE-103: a webcam page's own block, on top of the Webcams panel (without JS, or under the
   viewer). */
.webcam-page {
  margin: 0 0 2rem;
}

.webcam-page h2 {
  margin: 0 0 0.25rem;
  font-size: 1.3rem;
}

.webcam-page-facts {
  margin: 0 0 0.75rem;
  color: var(--muted);
}

.webcam-page-picture img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* Old pictures: small and dimmed, after the live ones (FRESH-20). */
.cam-group.old .webcam {
  flex: var(--ratio) 1 calc(var(--ratio) * 7rem);
  max-width: calc(var(--ratio) * 220px);
}

.cam-group.old .webcam img {
  filter: grayscale(0.6) brightness(0.8);
}

/* A panorama among the old pictures: the middle, cropped to the others' 4:3 (owner, 2026-10-21;
   it was a thin strip). The viewer shows it whole. */
.cam-group.old .webcam.wide img {
  aspect-ratio: 4 / 3;
  height: auto;
  object-fit: cover;
}

.cam-group.old figcaption {
  font-size: 0.9rem;
}

/* Images of the day and viewer (docs/spec/viewer.md) ----------------------------------------- */

/* PLAY-01, PLAY-02: the frames over the live picture, the time and the sliding bar. */
/* PLAY-03: a finger held on a webcam (on the page or in the viewer) plays its images of the day,
   without the phone's image or link menu, nor the tap highlight (owner 2026-10-22: a blue
   flash over the picture). */
.webcam .frame,
.webcam .frame img,
.viewer-stage,
.viewer-image {
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

.day-layer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.35s ease-in-out;
  pointer-events: none;
}

.day-layer.playing {
  opacity: 1;
}

.day-layer img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* An image without a source would show the browser's broken-image frame (a light 1px line). */
.day-layer img:not([src]) {
  visibility: hidden;
}

/* PLAY-02 (owner 2026-10-22): the progress bar is outside the picture, which may scroll or zoom:
   along the top of the webcam's frame on the page, in place of the header's bottom line in the
   viewer. */
.day-track {
  position: absolute;
  z-index: 1;
  right: 0;
  left: 0;
  /* Owner, 2026-10-23: hard to follow on a pale sky or a green forest. A thicker bar on an almost
     opaque dark rail reads on any picture (no shadow under it, owner 2026-10-24). */
  height: 4px;
  background: rgb(11 15 20 / 0.8);
  opacity: 0;
  transition: opacity 0.35s ease-in-out;
  pointer-events: none;
}

.day-track.playing {
  opacity: 1;
}

.webcam > .day-track {
  top: 0;
}

.viewer-bar > .day-track {
  bottom: -1px;
}

.viewer-bar:has(> .day-track.playing) {
  border-bottom-color: transparent;
}

.day-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: #5be08f;
  transition-property: width;
  transition-timing-function: linear;
}

/* VIEW-07 (owner 2026-10-22): under a webcam's address (a reload, a shared link) the page under
   the viewer is not painted before the viewer opens (webcams.js adds viewer-ready in the same
   step): the viewer's background until then, the page after 4 s if the script never runs. */
html[data-viewer]:not(.viewer-ready) {
  background: #0b0f14;
}

html[data-viewer]:not(.viewer-ready) body {
  visibility: hidden;
  animation: viewer-fallback 0s linear 4s forwards;
}

@keyframes viewer-fallback {
  to {
    visibility: visible;
  }
}

/* VIEW-01: the viewer covers the screen. */
.viewer {
  /* Always dark: the copied caption uses the dark tokens whatever the scheme (review R2). */
  --ink: #e9eef3;
  --muted: #93a1b0;
  --ok: #46c37b;
  --accent: #46c37b;
  --warn: #f0a83a;
  --danger: #ef6a5e;
  width: 100vw;
  max-width: none;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: #0b0f14;
  color: #e9eef3;
}

/* The page under the viewer keeps still and shows no scrollbar of its own (owner, 2026-10-21:
   a scrollbar along the full-screen view with classic, non-overlay scrollbars). */
html:has(dialog.viewer[open]),
html:has(dialog.welcome[open]) {
  overflow: hidden;
}

/* The column never wider than the screen: an auto one took the bar's widest content, and a long
   line name in the map's header pushed the buttons out of view (owner 2026-10-24). */
.viewer[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
}

.viewer::backdrop {
  background: #0b0f14;
}

.viewer-bar {
  /* VIEW-09: a finger pulling the header down is ours (pull to refresh, webcams.js). */
  touch-action: none;
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem var(--gutter);
  border-bottom: 1px solid #26313c;
}

/* VIEW-09: pulled, the header and the picture move down (--pull, set by webcams.js) and an arrow
   turns as the pull grows, green once releasing reloads. */
.viewer-bar,
.viewer-stage {
  translate: 0 var(--pull, 0px);
}

.viewer.pull-release .viewer-bar,
.viewer.pull-release .viewer-stage {
  transition: translate 0.2s ease-out;
}

.viewer-pull {
  position: absolute;
  top: calc(var(--pull, 0px) / 2 - 0.75rem);
  left: 50%;
  display: flex;
  width: 1.5rem;
  height: 1.5rem;
  color: #93a1b0;
  opacity: var(--pull-progress, 0);
  translate: -50% 0;
  rotate: calc(var(--pull-progress, 0) * 270deg);
  pointer-events: none;
}

/* The icon fills its box: the circle's center (the middle of its 20 x 20 drawing) is then the
   center it turns around (owner 2026-10-22: it wobbled, 2 px off). */
.viewer-pull svg {
  width: 100%;
  height: 100%;
}

.viewer.pull-ready .viewer-pull,
.viewer.refreshing .viewer-pull {
  color: #46c37b;
}

/* Released: it turns until the new picture is there. */
.viewer.refreshing .viewer-pull {
  opacity: 1;
  animation: pull-spin 0.8s linear infinite;
}

/* Both ends given: from the arrow's own angle (270deg once pulled) each turn was a quarter, then
   a jump back (owner 2026-10-22: glitchy). */
@keyframes pull-spin {
  from {
    rotate: 0deg;
  }

  to {
    rotate: 360deg;
  }
}

.viewer-caption {
  flex: 1;
  min-width: 0;
}

.viewer-caption .cam-name {
  color: #e9eef3;
}

.viewer-caption .place {
  color: #93a1b0;
}

.viewer-actions {
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.5rem;
}

/* Narrow phones: the actions go under the caption, the close button stays in the corner. */
@media (max-width: 480px) {
  .viewer-bar {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  .viewer-actions {
    order: 3;
    flex-basis: 100%;
  }
}

/* A short label: the full one is the link's title and accessible name. */
.viewer-external,
.viewer-day,
.viewer-closed {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 2.75rem;
  padding: 0 0.85rem;
  border: 1px solid #3a4855;
  background: transparent;
  color: #e9eef3;
  font-size: 0.95rem;
  white-space: nowrap;
  text-decoration: none;
}

.viewer-external svg {
  width: 0.9rem;
  height: 0.9rem;
}

/* Pressed is the only "on" look: a hover color would stick after a tap on touch screens. */
.viewer-day[aria-pressed="true"],
.viewer-closed[aria-pressed="true"] {
  border-color: #46c37b;
  color: #46c37b;
}

/* Owner 2026-10-24: the button turned green at once while the closed lines faded in, which read
   as a lag of the map. It fades with them, same timing as .closed-set. */
.viewer-closed {
  transition:
    border-color 0.2s ease-in-out,
    color 0.2s ease-in-out;
}

@media (hover: hover) {
  .viewer-external:hover,
  .viewer-day:hover,
  .viewer-closed:hover {
    border-color: #93a1b0;
    color: #e9eef3;
  }

  .viewer-day[aria-pressed="true"]:hover,
  .viewer-closed[aria-pressed="true"]:hover {
    border-color: #46c37b;
    color: #46c37b;
  }
}

/* VIEW-02: the webcam's share button and the download link (VIEW-10), the same size as the
   other actions. */
.viewer-share-slot {
  display: contents;
}

.viewer .viewer-download,
.viewer .viewer-share {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  min-height: 2.75rem;
  border: 1px solid #3a4855;
  color: #e9eef3;
}

.viewer .viewer-share .icon {
  width: 1.15rem;
  height: 1.15rem;
}

.viewer .viewer-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  min-height: 2.75rem;
  padding: 0;
  border: 1px solid #3a4855;
  background: rgb(15 20 26 / 0.75);
  color: #e9eef3;
}

@media (hover: hover) {
  .viewer .viewer-download:hover,
  .viewer .viewer-share:hover,
  .viewer .viewer-close:hover {
    border-color: #46c37b;
    background: rgb(15 20 26 / 0.9);
    color: #46c37b;
  }
}

.viewer-stage {
  /* VIEW-05, VIEW-08: a finger scrolls the zoomed picture natively; the pinch and the double tap
     are ours (webcams.js), never the browser's page zoom. */
  touch-action: pan-x pan-y;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  overscroll-behavior: contain;
  /* The image is sized from the stage, not the screen: the bar's height varies (caption on two
     lines, phone) and a fixed guess made the stage scroll by a few pixels. */
  container-type: size;
}

.viewer-frame {
  position: relative;
  /* VIEW-05: the zoom is a transform from the picture's top left corner. */
  transform-origin: 0 0;
  max-width: 100%;
  max-height: 100%;
}

/* The previous webcam's picture is not shown while the next one downloads. */
.viewer-frame.loading .viewer-image {
  visibility: hidden;
}

.viewer-image {
  display: block;
  max-width: 100cqw;
  max-height: 100cqh;
  width: auto;
  height: auto;
}

/* VIEW-05: zoomed, a mouse drags the picture (owner: never a magnifying glass). */
/* VIEW-05: zoomed, the picture is laid out at its size (webcams.js) and the stage scrolls,
   without scrollbars; centered where smaller than the stage (auto margins). A mouse drags it
   (owner: never a magnifying glass). */
.viewer-stage.zoomed {
  align-items: flex-start;
  justify-content: flex-start;
  overflow: auto;
  scrollbar-width: none;
}

.viewer-stage.zoomed::-webkit-scrollbar {
  display: none;
}

.viewer-stage.zoomed .viewer-frame {
  /* Its own size, not shrunk to the stage (a flex item). */
  flex: none;
  max-width: none;
  max-height: none;
  margin: auto;
  cursor: grab;
}

.viewer-stage.zoomed .viewer-image {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
}

.viewer-stage.grabbing .viewer-frame {
  cursor: grabbing;
  user-select: none;
}

/* Composited while two fingers pinch it; sharp again once laid out. */
.viewer-stage.dragging .viewer-frame {
  will-change: transform;
}

@media (max-width: 720px) {
  .viewer-external,
  .viewer-day {
    padding: 0 0.6rem;
  }
}

/* Infos and Météo panels (D042) ---------------------------------------------------------------- */

/* D043 (tickets.md TICKET-30): the infos under the map, one list, newest first. One column, as
   wide as it reads. */
.tickets {
  max-width: 48rem;
  margin-top: 1.75rem;
}

.list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
}

.list-head :is(h2, h3) {
  margin: 0;
  font-size: 1.15rem;
}

.list-head :is(h2, h3) a {
  color: var(--ink);
  text-decoration: none;
}

/* Composers (owner, 2026-10-22): a thinner, empty item (dashed) on top of a list, where the new
   one will appear; it opens its form in place (main.js). Lighter than a page form: it sits in
   the list. */
.composer {
  margin-top: 1rem;
}

.composer-open {
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.55rem 1.1rem;
  border: 1px dashed var(--rule-strong);
  background: var(--panel);
  color: var(--muted);
  font-weight: 600;
  text-decoration: none;
}

/* With a mouse only: a phone keeps the hover after a tap. */
@media (hover: hover) {
  .composer-open:hover {
    border-color: var(--accent);
    color: var(--ink);
  }
}

.composer.open .composer-open,
.composer:not(.open) .composer-form {
  display: none;
}

.composer-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem 1.1rem;
  border: 1px solid var(--rule-strong);
  background: var(--panel);
}

.composer-form :is(textarea, input[type="text"]) {
  padding: 0.45rem 0.6rem;
  font-size: 0.95rem;
}

.composer-form button[type="submit"],
.composer-form .photo-button {
  min-height: 2.1rem;
  padding: 0.25em 0.9em;
  font-size: 0.9rem;
}

.composer-form button[type="submit"]:not(:disabled) {
  border-color: var(--accent);
  background: transparent;
  color: var(--accent);
}

@media (hover: hover) {
  .composer-form button[type="submit"]:hover:not(:disabled) {
    background: var(--accent);
    color: var(--on-ink);
  }
}

.composer-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.composer-cancel {
  color: var(--muted);
  font-size: 0.9rem;
}

.tickets .owner-note {
  margin: 1rem 0 0;
}

/* The tickets touch, one border between two (owner, 2026-10-22). */
.ticket-list {
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}

.ticket-list > li + li,
.composer + .ticket-list,
.ticket-list > li > .composer-form {
  margin-top: -1px;
}

.ticket {
  padding: 0.9rem 1.1rem;
  background: var(--panel);
  border: 1px solid var(--rule);
}

.ticket-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.5rem;
  margin: 0 0 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.ticket-source {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  color: var(--ink);
  font-weight: 700;
}

.ticket-source.official {
  color: var(--accent);
}

.ticket-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.2rem;
  margin-top: 0.75rem;
  font-size: 0.9rem;
}

.ticket-actions a,
.ticket-actions .link-button {
  color: var(--muted);
}

/* Both sets of actions are in the page; the ticket's state picks one (main.js moves it). */
.ticket:not(.is-archived) :is(.when-archived, .ticket-archived),
.ticket.is-archived .when-active {
  display: none;
}

.ticket-undo {
  padding: 0.6rem 0;
  color: var(--muted);
}

.ticket-list.archived .ticket {
  opacity: 0.75;
}

.tickets-archived-toggle {
  margin-top: 1rem;
  color: var(--muted);
}

/* Owner, 2026-10-23: what the two labels mean, drawn as on the tickets. */
.tickets-legend {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 0.75rem;
  align-items: start;
  margin: 1.25rem 0 0;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}

/* The label's baseline would be its icon's bottom: centered on the first line of text instead. */
.tickets-legend .ticket-source {
  display: flex;
  height: 1.5em;
}

.tickets-legend div {
  display: contents;
}

.tickets-legend dd {
  margin: 0;
}

.tickets-legend a {
  color: inherit;
}

/* Under the drawing, a table: no wider than it reads. */
#meteo .readings {
  max-width: 32rem;
  margin-top: 1rem;
}

.readings dl {
  margin: 0;
}

.readings dl > div {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 1.25rem;
  border-bottom: 1px solid var(--rule);
}

.readings dt {
  font-size: 0.95rem;
}

.readings dd {
  margin: 0;
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}

.readings .lpn dd {
  color: var(--danger);
}

/* Three dashes, the profile's rain/snow line in small (FE-19). */
.lpn-key {
  display: inline-block;
  width: 26px;
  height: 2px;
  margin-left: 0.5rem;
  vertical-align: 0.3em;
  background: repeating-linear-gradient(90deg, var(--danger) 0 6px, transparent 6px 10px);
}

/* Under 300 px the label fills the row: the dashes would go to a line of their own (review
   2026-10-24). */
@media (max-width: 299px) {
  .lpn-key {
    display: none;
  }
}

.readings .placeholder {
  padding: 0.75rem 1.25rem;
}

.board-note {
  margin: 0;
  padding: 0.6rem 1.25rem 0.75rem;
  color: var(--muted);
  font-size: 0.8rem;
}

.stamp {
  margin: 0 0 0.9rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.messages {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.message {
  overflow-wrap: anywhere;
}

.messages .message + .message {
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.message > :last-child,
.message .text > :last-child {
  margin-bottom: 0;
}

.message h4,
.message h5 {
  margin: 0.2rem 0;
  font-size: 1rem;
}

.message h5 {
  font-size: 0.95rem;
}

.placeholder {
  margin: 0.4rem 0 0.8rem;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.2rem;
  margin: 1rem 0 0;
  font-size: 0.9rem;
}

.actions a:first-child {
  font-weight: 700;
}

.owner-note {
  margin-bottom: 0.8rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--warn);
}

.reveal .when-open,
.reveal[aria-expanded="true"] .when-closed {
  display: none;
}

.reveal[aria-expanded="true"] .when-open {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
}

.outdated {
  margin-top: 0.6rem;
}

/* NOTIF-42 (owner, 2026-10-21): a bell next to what it is about. Off: an outlined bell, muted;
   on: filled, green, "Prévenu". */
.notify {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem 0.75rem;
}

.notify[hidden] {
  display: none;
}

.notify[data-pending] {
  visibility: hidden;
}

.notify-bell {
  min-height: 2.25rem;
  padding: 0.3em 0.75em;
  border: 1px solid var(--rule-strong);
  background: transparent;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
}

@media (hover: hover) {
  button.notify-bell:hover:not(:disabled) {
    border-color: var(--accent);
    background: transparent;
    color: var(--accent);
  }
}

.notify-bell[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

.notify-bell .icon:nth-of-type(2),
.notify-bell .notify-on,
.notify-bell[aria-pressed="true"] .icon:nth-of-type(1),
.notify-bell[aria-pressed="true"] .notify-off {
  display: none;
}

.notify-bell[aria-pressed="true"] .icon:nth-of-type(2),
.notify-bell[aria-pressed="true"] .notify-on {
  display: inline;
}

.notify-bell:disabled,
.notify-bell[aria-disabled="true"] {
  cursor: wait;
  opacity: 0.6;
}

.notify-status {
  color: var(--muted);
  font-size: 0.85rem;
}

/* Empty, the status takes no room: its gap pushed the bell off the right edge it shares with the
   cards below (owner 2026-10-24). With words, the gap is there beside the bell only, none when
   it wraps under it (review 2026-10-24). */
.notify-status:empty {
  margin-left: -0.75rem;
}

/* The bulletin titles share their line with the bell. */
.bulletin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  margin-bottom: 0.2rem;
}

.bulletin-head h3 {
  margin: 0;
}

/* NOTIF-43: the Notifications page, one line per topic. */
.notify-list {
  display: grid;
  gap: 1rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.notify-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  padding: 1rem 1.25rem;
  background: var(--panel);
  border: 1px solid var(--rule);
}

.notify-list h2 {
  margin: 0;
  font-size: 1.1rem;
}

.notify-list p {
  margin: 0.2rem 0 0;
}

/* Live profile (components/profile.ts, D025 content in D030 colors) */
.profile {
  margin: 0;
}

.profile svg {
  display: block;
  width: 100%;
  height: auto;
}

.profile .profile-narrow {
  display: none;
}

.profile .ground {
  fill: var(--ground);
}

.profile .ridge {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linejoin: round;
}

.profile .lpn {
  stroke: var(--danger);
  stroke-width: 1.5;
  stroke-dasharray: 8 6;
}

.profile text {
  font-family: var(--sans);
  fill: var(--ink);
  /* halo in the page color: labels stay readable where they cross the ridge or the dashed line */
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 5px;
  stroke-linejoin: round;
}

.profile .value {
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: 19px;
  font-weight: 700;
}

.profile .note {
  font-size: 14px;
  fill: var(--muted);
}

.profile .pole {
  stroke: var(--muted);
  stroke-width: 2.5;
}

.profile .snow {
  fill: #ffffff;
  stroke: var(--muted);
  stroke-width: 1;
}

.profile .station {
  fill: var(--ink);
}

.profile .base {
  stroke: var(--bg);
  stroke-width: 2;
}

.profile .base.open {
  fill: var(--ok);
}

.profile .base.closed {
  fill: var(--danger);
}

/* Lift list unknown or too old (INFO-12): no claim either way. */
.profile .base.unknown {
  fill: var(--muted);
}

.profile .base-name {
  font-size: 15px;
  font-weight: 600;
}

/* Ouvertures panel ----------------------------------------------------------------------------- */

.openings-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  margin-bottom: 1rem;
}

.domain-status {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.3rem 0.6rem;
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
}

.domain-status .count {
  font-family: var(--sans);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 700;
  line-height: 1;
}

.domain-status.open > .count {
  color: var(--ok);
}

.domain-status.closed > .count {
  color: var(--danger);
}

.domain-status .slopes {
  display: inline-flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-left: 1rem;
}

.domain-status .slopes .count {
  font-size: clamp(1.4rem, 3.5vw, 2rem);
}

.map-figure {
  margin: 0;
}

.map-figure figcaption {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.map-figure figcaption::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

.map-figure figcaption > a {
  color: var(--ink);
  font-size: 1.15rem;
  font-weight: 700;
  text-decoration: none;
}

.map-drawn {
  position: relative;
  /* Big enough to read the names on a desktop, not a wall of map. */
  max-width: 72rem;
  aspect-ratio: 1638 / 791;
  background: var(--panel);
  border: 1px solid var(--rule);
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  /* Safari (iOS included) only honors the prefixed property: a long press must not select. */
  -webkit-user-select: none;
  user-select: none;
}

/* Browsers without aspect-ratio (Safari before 15): the same box from a padding. */
@supports not (aspect-ratio: 1) {
  .map-drawn::before {
    content: "";
    display: block;
    padding-top: 48.29%;
  }
}

.map-background,
.map-drawn svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* MAP-50 (D028): closed items are in the page but hidden; a mouse over the map or a long press
   (main.js adds .reveal) shows the whole map, planned items solid. */
/* Owner, 2026-10-19: they fade in and out (as on the old site), not on/off; 0.2 s ease-in-out,
   the old site's timing (owner, 2026-10-21). Owner, 2026-10-22: a press showed them late, the
   phone drawing them for the first time. So they are always drawn, just short of invisible
   (browsers skip drawing what is fully transparent), and out of the pointer's way. */
.drawn-map .closed-set {
  opacity: 0.01;
  pointer-events: none;
  transition: opacity 0.2s ease-in-out;
}

.drawn-map .pending-set {
  transition: opacity 0.2s ease-in-out;
}

/* Owner 2026-10-24: on a phone the lifts flashed off, on, off as a finger was lifted, a layer
   made for the fade and dropped at its end. Kept for good, nothing is made or dropped. */
.drawn-map .closed-set,
.drawn-map .pending-set {
  will-change: opacity;
}

/* MAP-52: `locked`, the viewer's "Afficher toutes les pistes" button pressed. */
.map-drawn:is(.reveal, .locked) .closed-set {
  opacity: 1;
  pointer-events: auto;
}

.map-drawn:is(.reveal, .locked) .pending-set {
  opacity: 1;
}

.drawn-map .hit {
  stroke: transparent;
  /* A finger-wide band around each line, in map units: main.js sets --map-px (map units per CSS
     pixel) from the drawn size (review: 14 units were about 3 px on a phone). vector-effect does
     not reach through the <use> in Chrome. */
  stroke-width: calc(24 * var(--map-px, 2.5px));
}

/* Amber, light (owner, 2026-10-21): white vanished on the snow. */
.drawn-map g.selected .hit {
  stroke: rgb(240 168 58 / 0.55);
  stroke-width: calc(8 * var(--map-px, 2.5px));
}

/* One line of fixed height: the name appearing must not move what follows (owner, 2026-10-19). */
.map-name {
  height: 1.4em;
  margin: 0.4rem 0 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.4;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.footnote {
  max-width: 72rem;
  margin-top: 0.5rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* Owner, 2026-10-22: the name of the line under the pointer only makes sense with a mouse. */
@media (hover: none) {
  .map-name,
  .map-name-hint {
    display: none;
  }
}

/* MAP-52: on the page the map opens the viewer (the plain link cursor, as the webcams). */
.map-drawn[role="button"] {
  cursor: pointer;
}

/* MAP-52: the map in the viewer, sized from the stage like a webcam's picture (VIEW-05), its
   name line under the title. */
.map-viewer .map-drawn {
  width: min(100cqw, calc(100cqh * 1638 / 791));
  max-width: none;
  border: 0;
}

.viewer-stage.zoomed .map-drawn {
  width: 100%;
  height: 100%;
}

/* Its name only with a mouse, as on the page (owner 2026-10-24: not in the header on a phone). */
.map-viewer .map-name {
  margin: 0;
}

/* Centered on the whole screen, not under the bar (owner 2026-10-24), the map and the webcams:
   raised by up to the bar's height, what room there is (--lift, viewer-kit.js). */
.viewer .viewer-stage:not(.zoomed) .viewer-frame {
  margin-top: calc(-1 * var(--lift, 0px));
}

/* Comments (docs/spec/comments.md) ------------------------------------------------------------ */

#commentaires:not(.tab-panel) {
  max-width: 50rem;
  padding: 1.25rem 1.5rem;
}

.comments-card {
  max-width: 50rem;
}

/* The Commentaires tab: the list, then a side column on wide screens (the donors' words). */
.comments-layout {
  display: grid;
  grid-template-columns: minmax(0, 50rem) minmax(16rem, 22rem);
  align-items: start;
  gap: 1.25rem;
}

.comments-layout:not(:has(> .comments-aside)) {
  grid-template-columns: minmax(0, 50rem);
}

.comments-aside {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.aside-card {
  padding: 1.1rem 1.25rem;
}

.aside-card p:last-child {
  margin-bottom: 0;
}

/* BMC-10: smaller than on the about page, it is a side note. */
.donor-quotes h3 {
  margin: 0;
  font-size: 1rem;
}

.donor-quotes .quotes {
  margin: 0.75rem 0;
}

.donor-quotes .quotes li {
  margin-bottom: 0.75rem;
}

.donor-quotes .quotes blockquote {
  font-size: 1rem;
}

.comment-form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.comment-form-row > input {
  flex: 1 1 12rem;
  width: auto;
}

/* COM-42: the photo button, an outlined twin of Publier; the file input inside is hidden. */
.photo-button {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  min-height: 2.6rem;
  padding: 0.45em 0.9em;
  border: 1px solid var(--rule-strong);
  color: var(--ink);
  font-weight: 700;
  cursor: pointer;
}

@media (hover: hover) {
  .photo-button:hover {
    border-color: var(--accent);
    color: var(--accent);
  }
}

.photo-button:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.photo-preview,
.photo-current {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.photo-preview[hidden],
.photo-current[hidden] {
  display: none;
}

.photo-preview img,
.photo-current img {
  max-width: min(100%, 12rem);
  max-height: 8rem;
  border: 1px solid var(--rule);
}

/* TICKET-19: an info's current photo, a box to take it off (a new photo replaces it anyway). */
.photo-current label {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  color: var(--muted);
  font-size: 0.9rem;
}

.comment-photo,
.ticket-photo {
  display: inline-block;
}

/* TICKET-19: an info's photo under its text, as a comment's. */
.ticket-photo img:not([height]) {
  max-height: 17em;
}

.ticket-photo img {
  display: block;
  max-width: 100%;
  /* The width and height attributes give the ratio and at most 272 px (PERF-10); imported
     images without them keep the old cap. */
  height: auto;
  margin-top: 0.5rem;
}

/* COM-42: a posted photo full screen, over a dark backdrop; a click anywhere closes it. */
.photo-viewer {
  max-width: 100vw;
  max-height: 100dvh;
  padding: 0;
  border: 0;
  background: transparent;
}

.photo-viewer[open] {
  display: grid;
  place-items: center;
}

.photo-viewer::backdrop {
  background: rgb(11 15 20 / 0.92);
}

.photo-viewer img {
  display: block;
  max-width: calc(100vw - 2rem);
  max-height: calc(100dvh - 5rem);
  cursor: pointer;
}

.photo-viewer-close {
  margin-top: 0.75rem;
  border-color: #e9eef3;
  background: transparent;
  color: #e9eef3;
}

html:has(dialog.photo-viewer[open]) {
  overflow: hidden;
}

.replying {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* COM-21 honeypot: clipped where it stands, not pushed off screen (the Konami mirror, FE-59,
   threw a field at -10000px 10000px past the right edge, owner 2026-10-22). */
.hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.comments,
.replies {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The comments touch, one border between two, as the infos (owner, 2026-10-22). */
.comments {
  margin-top: -1px;
}

.comments > li {
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  background: var(--panel);
}

.comments > li + li {
  margin-top: -1px;
}

/* A reply being written, right under its comment. */
.comments .composer-form {
  margin-top: 0.75rem;
  padding: 0.75rem 0 0;
  border: 0;
  border-top: 1px solid var(--rule);
}

.replies {
  margin-top: 0.6rem;
  padding-left: 1rem;
  border-left: 2px solid var(--rule-strong);
}

.replies > li + li {
  margin-top: 0.6rem;
}

.comment-head {
  margin: 0 0 0.2rem;
}

.comment-head .when {
  color: var(--muted);
  font-size: 0.85rem;
  text-decoration: none;
}

.comment:target .comment-head strong {
  color: var(--accent);
}

.comment-body {
  overflow-wrap: anywhere;
}

.comment-image:not([height]) {
  max-height: 17em;
}

.comment-image {
  display: block;
  max-width: 100%;
  /* The width and height attributes give the ratio and at most 272 px (PERF-10); imported
     images without them keep the old cap. */
  height: auto;
  margin-top: 0.5rem;
}

/* A11Y-17 (audit 2026-10-23): on touch screens the small icon buttons and text actions get a
   bigger invisible hit area (44 px for the icons, 40 px tall for the links), the layout
   unchanged; gloves and cold fingers on the slopes. */
@media (pointer: coarse) {
  .share,
  .ticket-actions :is(a, .link-button),
  .comment-actions :is(a, button) {
    position: relative;
  }

  .share::after,
  .ticket-actions :is(a, .link-button)::after,
  .comment-actions :is(a, button)::after {
    position: absolute;
    inset: -10px -4px;
    content: "";
  }

  .share::after {
    inset: -11px;
  }

  /* In the site bar, narrower than the gap to the theme and donation buttons, so it never
     reaches them (review 2026-10-24: their edges opened the share sheet). */
  .site-bar .share::after {
    inset: -11px -6px;
  }
}

.comment-actions {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
}

/* Room for the actions' taller hit area on touch screens (A11Y-17): it no longer covers the
   comment's last line (review 2026-10-24). */
@media (pointer: coarse) {
  .comment-actions {
    margin-top: 0.7rem;
  }
}

.comment-actions a {
  color: var(--muted);
}

/* ADMIN-12, ADMIN-13, COM-32: delete buttons, muted like "Répondre", red when pointed at. */
.comment-actions .admin-delete,
.ticket-actions .admin-delete,
.own-delete .link-button {
  min-height: 0;
  padding: 0;
  color: var(--muted);
  font-size: inherit;
}

@media (hover: hover) {
  .comment-actions .admin-delete:hover:not(:disabled),
  .ticket-actions .admin-delete:hover:not(:disabled),
  .own-delete .link-button:hover:not(:disabled) {
    color: var(--danger);
  }
}

.own-delete {
  margin: 0;
}

/* COM-32: the visitor's own comments are marked "vous". */
.comment .you {
  margin-left: 0.3rem;
  padding: 0 0.35em;
  border: 1px solid var(--accent);
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 700;
  vertical-align: 0.1em;
}

.comment.removed p {
  margin: 0;
  font-style: italic;
}

.pager {
  display: flex;
  gap: 1.5rem;
  margin-top: 1rem;
  font-weight: 700;
}

/* About ---------------------------------------------------------------------------------------- */

.about main > section,
.about main > header {
  max-width: 50rem;
}

.about main > section > h2 {
  margin: 2.5rem 0 1rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--rule);
}

.about main > section section {
  margin-bottom: 1.75rem;
}

.about h3 {
  margin-bottom: 0.5rem;
}

.plain {
  margin: 0;
  padding-left: 1.1rem;
}

.plain li {
  margin-bottom: 0.35rem;
}

table.sources {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}

.sources th {
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 600;
  text-align: left;
}

.sources th,
.sources td {
  /* Not `anywhere`: it lets the table squeeze a column to one letter ("car/te"). */
  overflow-wrap: break-word;
  padding: 0.35rem 0.75rem 0.35rem 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: baseline;
}

.sources td:last-child {
  padding-right: 0;
  white-space: nowrap;
  text-align: right;
}

.sources td:last-child a {
  color: var(--muted);
}

.gif {
  float: right;
  width: min(45%, 300px);
  margin: 0.25rem 0 0.75rem 1.5rem;
}

.gif video {
  display: block;
  width: 100%;
  height: auto;
}

.gif figcaption {
  margin-top: 0.3rem;
  color: var(--muted);
  font-size: 0.8rem;
}

#webcams-disparues::after {
  content: "";
  display: block;
  clear: both;
}

.donation-hook {
  max-width: 34rem;
  margin: 0.5rem 0 1rem;
  font-size: 1.25rem;
  line-height: 1.3;
}

.donation-hook a {
  text-decoration: none;
}

.donation-hook em {
  font-style: italic;
}

.donate {
  font-weight: 700;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
}

.quotes {
  margin: 1rem 0;
  padding: 0;
  list-style: none;
}

.quotes li {
  margin-bottom: 1rem;
}

.quotes blockquote {
  margin: 0;
  font-size: 1.1rem;
}

.quotes .by {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.85rem;
}

.thanks {
  color: var(--muted);
}

q {
  quotes: "« " " »" "“" "”";
}

/* Message pages -------------------------------------------------------------------------------- */

.message-page .page-head {
  padding-top: 3rem;
}

.message-content {
  max-width: 34rem;
  font-size: 1.4rem;
}

.message-content.danger {
  color: var(--ink);
}

.not-found .message-content {
  font-size: 1.15rem;
  color: var(--muted);
}

.destinations {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: 0.75rem;
  max-width: 48rem;
  margin: 1.75rem 0;
  padding: 0;
  list-style: none;
}

.destinations a {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  background: var(--panel);
  color: var(--ink);
  font-size: 1.1rem;
  text-decoration: none;
  transition:
    border-color 0.15s,
    color 0.15s;
}

@media (hover: hover) {
  .destinations a:hover {
    border-color: var(--accent);
    color: var(--accent);
  }
}

.destinations .icon {
  width: 1.4rem;
  height: 1.4rem;
  color: var(--accent);
}

/* Footer (FE-15, BMC-03) ----------------------------------------------------------------------- */

/* Owner, 2026-10-22: no rule on top; the donation band opens the footer. */
footer {
  width: 100%;
  margin-top: 2rem;
  padding: 0 0 2rem;
  color: var(--muted);
  font-size: 0.9rem;
}

footer p {
  margin: 0;
}

/* The site links, under the donation band. */
.footer-grid {
  max-width: 1440px;
  margin: 0 auto;
  padding: 1.5rem var(--gutter) 0;
}

.footer-title {
  margin-bottom: 0.6rem;
  color: var(--ink);
  font-weight: 700;
}

/* Owner, 2026-10-23: two groups of links side by side, the list had grown too long. */
.footer-columns {
  display: flex;
  gap: 0.45rem 3rem;
}

.footer-links {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin: 0;
}

footer .footer-links a {
  color: var(--ink);
  text-decoration: none;
}

@media (hover: hover) {
  footer .footer-links a:hover {
    color: var(--accent);
  }
}

/* FE-105: "(quoi de neuf ?)" reopens the welcome, a quieter link after the old site's. */
footer .footer-links a.whats-new {
  color: var(--muted);
}

@media (hover: hover) {
  footer .footer-links a.whats-new:hover {
    color: var(--accent);
  }
}

/* FE-58: the install link, a button among the links. */
.footer-links .install-link {
  align-self: flex-start;
  color: var(--ink);
  text-decoration: none;
}

/* D026: one donation call. Owner, 2026-10-22: not one more card among the others, a band as
   wide as the window, in a green tint, that opens the footer. */
.support-band {
  background: color-mix(in srgb, var(--accent) 13%, var(--bg));
}

.support-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.9rem 2rem;
  max-width: 1440px;
  margin: 0 auto;
  padding: 1.5rem var(--gutter);
}

.support-pitch {
  max-width: 40rem;
  margin: 0;
  color: var(--ink);
  font-size: 1rem;
}

.support-action {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
}

.support-proof {
  color: var(--muted);
  font-size: 0.9rem;
}

.support-button {
  view-transition-name: bmc;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 2.75rem;
  padding: 0 1.25rem;
  background: var(--accent);
  color: var(--on-ink);
  font-size: 1.05rem;
  font-weight: 700;
  text-decoration: none;
}

@media (hover: hover) {
  .support-button:hover {
    background: var(--ink);
    color: var(--on-ink);
  }
}

@media (max-width: 640px) {
  /* One flow of links on a phone: the groups follow each other. */
  .footer-columns {
    flex-direction: column;
  }

  .footer-links {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.4rem 1.25rem;
  }
}

/* Medium screens: the Météo board stacks its side column under the two others. */
@media (max-width: 1100px) {
  .comments-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Tablets: the bar keeps one line (it overflowed before the donation button, 2026-10-21). */
  .site-bar {
    gap: 0 1rem;
  }

  .tab {
    padding: 0 0.6rem;
  }

  .bar-status .wind {
    display: none;
  }
}

/* The site bar's words go before its numbers: first the place of the temperature, then the
   label of the donation button (BMC-11), so everything holds on one line. The numbers keep their
   words (UX-01, audit 2026-10-23). */
@media (max-width: 1365px) {
  /* Still read by screen readers (review 2026-10-17). */
  .bar-status .gust {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (max-width: 1499px) {
  .bar-status .summit {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@media (max-width: 1239px) {
  .bar-donate {
    padding: 0 0.6rem;
  }

  .bar-donate-label {
    display: none;
  }
}

/* Narrow tablets: the summary goes (the tabs hold the same information). */
@media (min-width: 721px) and (max-width: 880px) {
  .site-bar {
    gap: 0 0.75rem;
  }

  .bar-status {
    display: none;
  }

  .site-bar .theme-toggle {
    margin-left: auto;
  }
}

/* Phones: the tabs become a bottom bar (D030) -------------------------------------------------- */

@media (max-width: 720px) {
  :root {
    --bar-height: 3.25rem;
  }

  /* The fixed tab bar must not hide what scrolling or focus brings into view (review R2). */
  html.with-tabs {
    scroll-padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom) + 0.5rem);
  }

  .with-tabs body {
    padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
  }

  .site-bar {
    align-items: center;
    gap: 0 0.75rem;
  }

  .bar-donate {
    min-height: 2rem;
    padding: 0 0.45rem;
  }

  .wordmark {
    font-size: 1.25rem;
  }

  .tabs {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 20;
    display: grid;
    grid-auto-columns: minmax(0, 1fr);
    grid-auto-flow: column;
    gap: 0;
    height: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--panel);
    border-top: 1px solid var(--rule);
  }

  .tab {
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    padding: 0 0.25rem;
    font-size: 0.75rem;
  }

  .tab .icon {
    display: block;
    width: 1.35rem;
    height: 1.35rem;
  }

  .tab .long {
    display: none;
  }

  .tab .short {
    display: inline;
  }

  /* The page's darker ground behind the current tab (owner 2026-10-24). */
  .tab[aria-current="page"] {
    background: var(--tab-current);
  }

  .tab[aria-current="page"] .tab-line {
    bottom: -0.3rem;
  }

  .tab .tab-new {
    position: absolute;
    top: 0.55rem;
    left: calc(50% + 0.55rem);
  }

  .bar-status {
    gap: 0.8rem;
    font-size: 0.8rem;
  }

  /* The summit temperature stays; the wind is on the Météo tab (owner, 2026-10-21). */
  .bar-status .wind {
    display: none;
  }

  .profile .profile-wide {
    display: none;
  }

  .profile .profile-narrow {
    display: block;
  }

  .tab-panel {
    padding-top: 0.9rem;
  }

  #commentaires:not(.tab-panel) {
    padding: 1rem;
  }

  /* A panorama keeps a readable height and scrolls sideways; main.js adds the miniature under
     it (FE-23a). */
  .webcam.pano .frame {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .webcam.pano .frame::-webkit-scrollbar {
    display: none;
  }

  .webcam.pano .frame img {
    width: auto;
    max-width: none;
    height: 220px;
  }

  /* FE-23a: the whole panorama in a strip, a frame on the part shown; drag it to move. */
  .pano-map {
    position: relative;
    border-top: 1px solid var(--rule);
    background: var(--bg);
    cursor: pointer;
    touch-action: none;
    user-select: none;
  }

  .pano-map img {
    display: block;
    width: 100%;
    height: 26px;
    object-fit: fill;
    opacity: 0.55;
    -webkit-user-drag: none;
  }

  .pano-window {
    position: absolute;
    top: 0;
    bottom: 0;
    border: 1.5px solid var(--ink);
    background: rgb(255 255 255 / 0.18);
  }

  .webcam {
    flex-basis: 100%;
    max-width: none;
  }

  .cam-group.old .webcam {
    flex: 1 1 calc(50% - 0.5rem);
    max-width: none;
  }
}

@media (max-width: 400px) {
  .site-bar {
    gap: 0 0.55rem;
  }
}

/* Smallest phones: the wordmark, the summary, the theme and donation buttons in one line; the
   page share button goes (each webcam keeps its own). */
@media (max-width: 340px) {
  .share-page {
    display: none;
  }
}

@media (max-width: 320px) {
  .site-bar {
    gap: 0 0.5rem;
  }

  .wordmark {
    font-size: 1.05rem;
  }

  .bar-status {
    gap: 0.5rem;
    font-size: 0.75rem;
  }
}

@media (max-width: 340px) {
  .cam-group.old .webcam {
    flex-basis: 100%;
  }

  .gif {
    float: none;
    width: 100%;
    margin: 0 0 1rem;
  }
}

/* FE-105: the welcome of visitors coming back from the legacy site. */
.welcome {
  width: min(34rem, calc(100% - 2rem));
  max-height: calc(100% - 2rem);
  padding: 1.5rem 1.5rem 1.25rem;
  border: 1px solid var(--rule);
  background: var(--panel);
  color: var(--ink);
}

.welcome::backdrop {
  background: rgb(0 0 0 / 0.6);
}

.welcome h2 {
  margin: 0 0 0.75rem;
  font-size: 1.6rem;
  line-height: 1.15;
}

.welcome p {
  margin: 0.75rem 0 0;
}

.welcome ul {
  margin: 0.75rem 0;
  padding-left: 1.2rem;
}

.welcome li + li {
  margin-top: 0.5rem;
}

.welcome form {
  margin-top: 1.25rem;
}
