/* Theme variables for BlazorBlueprint (default neutral theme from its installation guide) plus the brand:
   a fitness green as the primary colour and Inter as the typeface. Loaded before blazorblueprint.css.
   To change the brand colour, change --primary here (light and dark) and nothing else. */

@font-face {
  font-family: "Inter Variable";
  src: url("../fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;

  /* Light theme in three planes, so every surface can be told apart: a light grey page, lighter grey cards and bars on top of it,
     and borders dark enough to see. Not white and not warm: a white page hid the contrast and the fields, a cream one was too yellow, and a very light grey
     left the brand green (a bright one) with almost no contrast as text. The page is the hex of BrandColor.LightSurface: change both together. */
  --background: #D3D8D8;
  --foreground: oklch(0.17 0.01 195);
  --card: oklch(0.925 0.003 195);
  --card-foreground: oklch(0.17 0.01 195);
  --popover: oklch(0.925 0.003 195);
  --popover-foreground: oklch(0.17 0.01 195);
  --primary: rgb(0 255 0);
  --primary-foreground: oklch(0.16 0 0);
  --secondary: oklch(0.84 0.005 195);
  --secondary-foreground: oklch(0.22 0.01 195);
  --muted: oklch(0.84 0.005 195);
  --muted-foreground: oklch(0.40 0.012 195);
  --accent: oklch(0.84 0.007 190);
  --accent-foreground: oklch(0.22 0.01 195);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(1 0 0);
  --border: oklch(0.77 0.007 195);
  --input: oklch(0.68 0.01 195);
  --ring: rgb(0 255 0);
  --radius: 0.75rem;

  --sidebar: oklch(0.925 0.003 195);
  --sidebar-foreground: oklch(0.17 0.01 195);
  --sidebar-accent: oklch(0.87 0.008 190);
  --sidebar-accent-foreground: oklch(0.22 0.01 195);
  --sidebar-border: oklch(0.77 0.007 195);
  --sidebar-ring: rgb(0 255 0);

  /* The active item of the menus: the neutral accent of the theme (the primary colour is only on its icon, see below). */
  --sidebar-menu-button-active-bg: var(--sidebar-accent);
  --sidebar-menu-button-active-color: var(--sidebar-accent-foreground);
  --sidebar-menu-button-active-font-weight: 500;
  --sidebar-menu-button-radius: 0.5rem;
  --sidebar-menu-button-py: 0.5rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.205 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: rgb(0 255 0);
  --primary-foreground: oklch(0.16 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(1 0 0);
  --border: oklch(0.34 0 0);
  --input: oklch(0.4 0 0);
  --ring: rgb(0 255 0);

  --sidebar: oklch(0.175 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(0.3 0 0);
  --sidebar-ring: rgb(0 255 0);

  --sidebar-menu-button-active-bg: var(--sidebar-accent);
  --sidebar-menu-button-active-color: var(--sidebar-accent-foreground);
}

* {
  border-color: var(--border);
}

/* The brand green is a bright one: fine as a background and in the dark theme, but as text or icon on a light page it glows and does not
   read. In the light theme, text and icons that take the brand colour get a darker green of the same hue. */
:root:not(.dark) .text-primary {
  color: oklch(0.42 0.15 145);
}

/* A field the form flags as wrong. Components ship a red border for aria-invalid, but the unlayered rule above overrides it. */
[aria-invalid="true"] {
  border-color: var(--destructive);
  box-shadow: 0 0 0 1px var(--destructive);
}

/* The coloured edge of a list item, by status: the colour is the one of the status badge next to it. */
.status-accent {
  border-left: 4px solid var(--status-color, var(--primary));
}

.status-pending {
  --status-color: oklch(0.769 0.188 70);
}

.status-approved {
  --status-color: oklch(0.696 0.17 162);
}

.status-rejected {
  --status-color: var(--destructive);
}

.status-suspended {
  --status-color: oklch(0.65 0.02 260);
}

/* The top border of form cards and dialogs: the brand colour. */
.form-card {
  border-top: 2px solid var(--primary);
}

body {
  background-color: var(--background);
  color: var(--foreground);
}

/* The status filter of the Administrator's list: each button wears the colour of its status, filled when it is the one chosen.
   Plain classes for the same reason as the borders: they must beat the component's own colours. */
.status-filter {
  border-color: var(--status-color);
  color: var(--status-color);
}

.status-filter-on {
  background-color: var(--status-color);
  color: oklch(0.2 0 0);
}


/* Motion: dialogs appear with a short fade and rise instead of popping in. Slow enough to notice (about 300 ms) yet never in the way, and
   off for people who ask their system for less motion. */
@keyframes appear {
  from {
    opacity: 0;
    transform: translateY(0.5rem) scale(0.98);
  }
}

/* A drawer (the library's sheet, also a dialog) keeps its own slide: it is the one that carries "slide-in-from-*" in its classes. */
[role="dialog"]:not([class*="slide-in-from"]),
[role="alertdialog"] {
  animation: appear 0.32s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  [role="dialog"]:not([class*="slide-in-from"]),
  [role="alertdialog"] {
    animation: none;
  }
}

/* The two side bars of the application (the main menu and the page's panel on the right, both drawn by the library) open and close at the
   same pace on wide screens: change --frame-duration to tune both at once. The library's own bar says 200 ms in its classes, so this
   rule, outside the layers, wins over it. On a phone both are the library's drawer. */
:root {
  --frame-duration: 250ms;

  /* The library's own 16rem, set here so it is one value to tune: the pages inside a folder of the menu are one level deeper and «Formas de pago» must fit on one line. */
  --sidebar-width: 16rem;

  /* Each level of the menu nests inside the one above, so the library's indent (14px + 10px) would leave the deepest pages almost no room: a lighter one. */
  --sidebar-sub-mx: 0.625rem;
  --sidebar-sub-px: 0.375rem;
}

@media (min-width: 768px) {
  aside[data-side][data-variant] {
    transition-duration: var(--frame-duration);
  }
}

@media (prefers-reduced-motion: reduce) {
  aside[data-side][data-variant] {
    transition: none;
  }
}

/* Dialogs on a phone. The library's dialog is as wide as the screen and as tall as its content, so a long title or a tall form went past
   the edges. Now it always leaves a margin on each side, never passes the visible height (what does not fit scrolls inside it), and
   breaks long words instead of widening. Wider limits per dialog (max-w-lg and the like) still apply on larger screens. Drawers are not
   touched: they carry "slide-in-from-*". */
[role="alertdialog"],
[role="dialog"]:not([class*="slide-in-from"]) {
  width: calc(100vw - 2rem);
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
  overflow-wrap: anywhere;
}

/* Their buttons are stacked on a phone, and the library only separates them from the small breakpoint up. */
@media (max-width: 639px) {
  [role="alertdialog"] [class*="flex-col-reverse"],
  [role="dialog"]:not([class*="slide-in-from"]) [class*="flex-col-reverse"] {
    gap: 0.75rem;
  }
}

/* A neutral change (something was edited): blue, between the green of what was added and the amber of what was put away. */
.status-info {
  --status-color: oklch(0.62 0.15 250);
}

/* The check of a success that must be seen from afar (an entry marked): it pops in, stays, and fades out. It lasts as long as
   SuccessFeedback.DefaultVisibleFor. With reduced motion it only fades. */
@keyframes success-pop {
  0% { opacity: 0; transform: scale(0.8); }
  12% { opacity: 1; transform: scale(1.06); }
  20% { transform: scale(1); }
  88% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}

.success-pop {
  animation: success-pop 5s ease both;
}

/* The photo in the celebration of an entry: big, with a halo that pulses out of it a few times; the check that pops in sits next to the text. */
.avatar-framed.avatar-framed-xl {
  /* Written with both classes so it beats the base rule of .avatar-framed, which comes later in this file. */
  /* 192 px: big enough to recognise the face from the front desk, and smaller than the 256 px the photo is saved at, so it stays sharp. A narrower screen makes it shrink. */
  width: 12rem;
  max-width: 100%;
  height: auto;
  aspect-ratio: 1;
  border-width: 4px;
}

@keyframes success-halo {
  0% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--primary) 60%, transparent); }
  100% { box-shadow: 0 0 0 36px transparent; }
}

.success-halo {
  animation: success-halo 1.4s ease-out 0.3s 3 both;
}

@keyframes success-check {
  0% { opacity: 0; transform: scale(0) rotate(-30deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

.success-check {
  animation: success-check 600ms cubic-bezier(0.34, 1.56, 0.64, 1) 250ms both;
}

/* The notice of a new version must be seen: its edge is a gradient of colours that turns round it, and the whole card hops now and then. The angle is a registered
   property so the gradient can turn; where that is not supported the edge stays still, and with reduced motion nothing moves. */
@property --update-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@keyframes update-spin {
  to { --update-angle: 360deg; }
}

@keyframes update-hop {
  0%, 55%, 100% { transform: translateY(0); }
  65% { transform: translateY(-12px); }
  75% { transform: translateY(0); }
  83% { transform: translateY(-5px); }
  91% { transform: translateY(0); }
}

.update-glow {
  padding: 2px;
  border-radius: 0.875rem;
  background: conic-gradient(from var(--update-angle), var(--primary), #38bdf8, #a78bfa, #f472b6, #fbbf24, var(--primary));
  box-shadow: 0 0 22px color-mix(in oklab, var(--primary) 45%, transparent);
  animation: update-spin 3s linear infinite, update-hop 3.2s ease-in-out infinite;
}

/* The welcome that appears behind the sliding login card. */
@keyframes welcome-in {
  from { opacity: 0; transform: translateY(-12%); }
  to { opacity: 1; transform: translateY(0); }
}

.welcome-in {
  animation: welcome-in 1400ms ease both;
}

@media (prefers-reduced-motion: reduce) {
  .success-pop,
  .success-halo,
  .success-check,
  .update-glow,
  .welcome-in {
    animation-name: none;
  }
}

/* The badge that says how many days a membership is valid: in the light theme the soft green tint under green text could not be read,
   so the text takes the colour of the rest of the text there (the dark theme keeps its own). */
:root:not(.dark) .validity-badge {
  color: var(--foreground);
}


/* The frame behind the sheet of the sign-in forms: the colour of the theme, which shows along the top edge of the sheet and fills the
   card once the sheet slides away. Until then only the top strip is coloured: a full background bled, anti-aliased, through the
   rounded bottom corners of the sheet as tiny green dots. */
.auth-frame {
  background: linear-gradient(var(--primary), var(--primary)) top / 100% 1rem no-repeat;
}

.auth-frame.welcomed {
  background: var(--primary);
}

/* The photo of a person (the list of members and their card): a circle with a neutral frame. */
.avatar-framed {
  width: 4rem;
  height: 4rem;
  border: 2px solid var(--border);
  border-radius: 9999px;
  overflow: hidden;
}

/* The photo on the card of the member's own page keeps the colour of how the membership stands. */
.avatar-framed-status {
  border-color: var(--status-color, var(--primary));
}

.avatar-framed-sm {
  width: 2.75rem;
  height: 2.75rem;
}

.avatar-framed-md {
  width: 4.5rem;
  height: 4.5rem;
}

.avatar-framed-lg {
  width: 5rem;
  height: 5rem;
}

.avatar-framed,
.avatar-framed > * {
  border-radius: 9999px;
}

/* The photo on the member's page: a square as wide as its column, with softly rounded corners instead of a circle. */
.avatar-framed.avatar-framed-fill {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
}

.avatar-framed.avatar-framed-fill,
.avatar-framed.avatar-framed-fill > * {
  border-radius: 0.75rem;
}

.avatar-framed.avatar-framed-fill > img {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The fields of a way of paying (or refunding): a sheet a little apart from the page in both themes, so each way reads as its own block. */
.way-card {
  background-color: color-mix(in oklch, var(--foreground) 6%, var(--card));
}

/* On the light theme the same step is smaller: the sheet stays between the form around it and the fields inside it. */
:root:not(.dark) .way-card {
  background-color: color-mix(in oklch, var(--foreground) 1.5%, var(--card));
}

/* The edge of the cards of the lobby: a 1px line that is bright at the top left and fades towards the opposite corner, drawn over the border with a mask so only the
   line shows. The plain border is made transparent (this rule is not in a layer, so it beats the utilities). */
.card-edge {
  position: relative;
  border-color: transparent;
}

.card-edge::before {
  content: "";
  position: absolute;
  inset: -1px;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(135deg, rgb(255 255 255 / 0.28), rgb(255 255 255 / 0.07) 45%, transparent 85%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/* The card of the search of the lobby, the area the front desk uses most: a light travels around its edge, so it stands out among the rest. The light is a conic gradient
   whose angle moves (the angle is a registered property, so the browser can animate it), drawn over the border with a mask so only the line shows. Without motion
   (the setting of the person) the light stays still at the top left. */
@property --glow-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

/* The light is the primary colour unless the card says otherwise (glow-status: the colour of its status class). */
.border-glow {
  --glow-color: var(--primary);
  position: relative;
  border-color: color-mix(in oklch, var(--glow-color) 30%, transparent);
  box-shadow: 0 0 22px -10px var(--glow-color);
}

.glow-status {
  --glow-color: var(--status-color, var(--primary));
}

/* Only the turning light, without the soft halo around the card. */
.glow-flat {
  box-shadow: none;
}

.border-glow::before {
  content: "";
  position: absolute;
  inset: -1px;
  padding: 2px;
  border-radius: inherit;
  pointer-events: none;
  background: conic-gradient(from var(--glow-angle), transparent 0deg, transparent 250deg, color-mix(in oklch, var(--glow-color) 70%, transparent) 320deg, var(--glow-color) 350deg, white 360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: glow-turn 5s linear infinite;
}

@keyframes glow-turn {
  to {
    --glow-angle: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .border-glow::before {
    animation: none;
    --glow-angle: 300deg;
  }
}

/* The curtain of a change of center, the colour of the center (--center-hue): its panel sweeps in from the left, stays covering the screen while this is redrawn behind it, and sweeps
   out to the right, all in the time of the change (--switch-ms); its name rises while it covers. Without motion (the setting of the person) the panel just stays while the change lasts. */
@keyframes center-curtain-sweep {
  0% {
    transform: translateX(-102%);
  }
  40%,
  60% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(102%);
  }
}

@keyframes center-curtain-name {
  0%,
  25% {
    opacity: 0;
    transform: translateY(14px) scale(0.96);
  }
  42%,
  60% {
    opacity: 1;
    transform: none;
  }
  75%,
  100% {
    opacity: 0;
    transform: translateY(-10px);
  }
}

@keyframes center-curtain-line {
  0%,
  35% {
    transform: scaleX(0);
  }
  55%,
  100% {
    transform: scaleX(1);
  }
}

.center-curtain-panel {
  background-image: linear-gradient(115deg, oklch(0.5 0.18 var(--center-hue)), oklch(0.3 0.12 var(--center-hue)));
  box-shadow: 0 0 60px 10px oklch(0.8 0.15 var(--center-hue) / 60%);
  animation: center-curtain-sweep var(--switch-ms, 1100ms) cubic-bezier(0.65, 0, 0.35, 1) both;
}

.center-curtain-name {
  animation: center-curtain-name var(--switch-ms, 1100ms) ease-out both;
}

.center-curtain-line {
  transform-origin: left;
  animation: center-curtain-line var(--switch-ms, 1100ms) ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .center-curtain-panel,
  .center-curtain-name,
  .center-curtain-line {
    animation: none;
  }
}

/* A soft tint of the status colour (a status class sets it) that fades from the left edge across the card. */
.status-tint {
  background-image: linear-gradient(to right, color-mix(in oklch, var(--status-color, transparent) 16%, transparent), transparent 70%);
}

/* A card of a list a step lighter than the others, so it stands out from the page in both themes. */
.card-raised {
  background-color: color-mix(in oklch, var(--card), white 6%);
}

:root:not(.dark) .card-raised {
  background-color: color-mix(in oklch, var(--card), white 60%);
}

.card-raised:hover {
  background-color: var(--accent);
}

/* The tables of the lists on wide screens (ResponsiveList): the same padding in every header and cell, the text vertically centred. */
.list-table th,
.list-table td {
  padding: 0.5rem 1rem;
  text-align: left;
  vertical-align: middle;
}

.list-table th {
  font-weight: 500;
}

/* A column of figures is flush right, its title too (the rule above would otherwise win over the utility class). */
.list-table th.text-right,
.list-table td.text-right {
  text-align: right;
}

/* In the menus the primary colour is only on the icon of the active item; the item itself is the neutral accent. */
[data-sidebar="menu-button"][data-active="true"] svg,
[data-sidebar="menu-sub-button"][data-active="true"] svg {
  color: var(--primary);
}

/* The thin scrollbar of the library's theme (its rule only reaches the library's own scroll boxes, those with a "bb:overflow-" class): the
   same one for the scroll boxes of the app (the page, the dialogs, the tables) and for the document itself. */
html,
:where([class*="overflow-"]) {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

:where([class*="overflow-"])::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

:where([class*="overflow-"])::-webkit-scrollbar-track,
:where([class*="overflow-"])::-webkit-scrollbar-corner {
  background: transparent;
}

:where([class*="overflow-"])::-webkit-scrollbar-thumb {
  background: var(--border);
  background-clip: padding-box;
  border: 2px solid transparent;
  border-radius: 9999px;
}

/* On paper (a receipt): the frame of the app is left out, what scrolls shows whole, and the colours are the ones of a sheet, because the
   theme's light text on a dark page would print as white on white. */
@media print {
  aside,
  nav,
  .no-print {
    display: none !important;
  }

  :root,
  .dark {
    --background: #fff;
    --foreground: #000;
    --card: #fff;
    --card-foreground: #000;
    --muted-foreground: #444;
    --border: #bbb;
  }

  html,
  body,
  [class*="overflow-"],
  [class*="h-dvh"],
  [class*="h-svh"],
  [class*="h-screen"],
  [class*="max-h-"],
  [class*="h-full"] {
    height: auto !important;
    overflow: visible !important;
  }

  body {
    background: #fff;
    color: #000;
  }

  .invoice-paper {
    border: 0;
  }

  /* A table on paper is drawn the same everywhere: a thin grid on every cell (or none), never borders on some sides only; a row is not split between two sheets
     and the header comes again on the next one. */
  .list-table {
    width: 100%;
    border-collapse: collapse;
  }

  .list-table th,
  .list-table td {
    border: 1px solid #bbb;
    padding: 0.3rem 0.6rem;
  }

  .list-table thead {
    display: table-header-group;
  }

  .list-table thead th {
    background: #f0f0f0;
    color: #000;
  }

  .list-table tr {
    break-inside: avoid;
  }

  /* The frame the table has on screen (rounded, with its own border) is not drawn: the grid is its frame. */
  .list-frame {
    border: 0;
    border-radius: 0;
    background: none;
  }

  /* The name of the client is a link on screen; on paper it is just the name. */
  .invoice-paper a {
    text-decoration: none;
  }
}

/* Sidebar areas and folders: they open by growing their height from 0 to the content (grid rows), with a fade. Closed, they leave the tab order. */
.nav-collapse {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease, visibility 0s linear 0.3s;
}

.nav-collapse[data-open="true"] {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

.nav-collapse > .nav-collapse-inner {
  min-height: 0;
  overflow: hidden;
  padding: 2px;
  margin: -2px;
  transform: translateY(-4px);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.nav-collapse[data-open="true"] > .nav-collapse-inner {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .nav-collapse,
  .nav-collapse > .nav-collapse-inner {
    transition: none;
  }
}
