/* ==========================================================================
   FHL Kids Barsi — design tokens
   The single source of truth for color, type, spacing, radii and shadows.
   Nothing else in the app should contain a raw hex, px size, or font stack.

   Units follow one rule:
     - grows when the user enlarges text  -> rem   (font sizes, spacing)
     - a physical constant                -> px    (borders, radii, shadows)
     - proportional to its own font size  -> unitless (line-height) / em
   The brand sheet is authored in px; every rem below carries its px value in
   a comment so this file reads 1:1 against the design doc. 1rem = 16px.

   SOURCE: every colour below comes from the Faith Hope Love Kids Ministry
   design system on claude.ai/design -- tokens/colors.css, tokens/fonts.css,
   tokens/typography.css and tokens/spacing.css. The ramp steps used to be
   OKLCH-derived approximations of the 500 cores; they are now the design
   system's own hexes. Re-sync from that project rather than hand-editing a
   hex here.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   Self-hosted so the check-in station never waits on a third-party CDN mid
   service, and so CSP can stay `font-src 'self'`. Both are VARIABLE fonts:
   one file covers every weight. Licences sit beside them (OFL requires it).
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Nunito';
  src: url('fonts/nunito-var.woff2') format('woff2');
  font-weight: 200 1000;          /* variable axis -- any weight in range */
  font-style: normal;
  font-display: swap;             /* show fallback text immediately */
}
@font-face {
  font-family: 'Nunito';
  src: url('fonts/nunito-italic-var.woff2') format('woff2');
  font-weight: 200 1000;
  font-style: italic;
  font-display: swap;             /* only fetched when italic actually renders */
}
@font-face {
  font-family: 'Fredoka';
  src: url('fonts/fredoka-var.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ------------------------------------------------------------------------
     2. Brand ramps -- raw palette. Do not reference these directly in
     components; use the semantic roles in section 3 instead.
     ------------------------------------------------------------------------ */

  /* The 300 steps are DECORATIVE ONLY: each one lands between 1.3:1 and
     3.9:1 on cream, so none of them may carry text or act as a chart mark.
     The 700 steps are the text-safe darks -- all three clear 6.9:1 on white. */

  /* Faith Red */
  --red-100: #FFE3E8;
  --red-300: #FF7186;
  --red-500: #DE0124;   /* core */
  --red-700: #A8001B;   /* 7.83:1 on white */
  --red-900: #6E0012;

  /* Hope Gold */
  --gold-100: #FFF1CC;
  --gold-300: #FFD566;
  --gold-500: #F4B81A;  /* core */
  --gold-700: #C68E00;
  --gold-900: #8A6300;

  /* Love Pink */
  --pink-100: #FFD9EE;
  --pink-300: #FF6BB8;
  --pink-500: #EC0F86;  /* core */
  --pink-700: #B0005E;  /* 6.98:1 on white */
  --pink-900: #73003D;

  /* Faith Navy.
     NB: 700 is DARKER than 500 here, not lighter. The design system ramps
     navy straight down into the near-black used for pressed states, so
     --color-primary-hover below now darkens on hover instead of lifting. */
  --navy-100: #E2E0EA;
  --navy-300: #7E7898;
  --navy-500: #2A2447;  /* core */
  --navy-700: #1B1730;  /* 17.33:1 on white */
  --navy-900: #0E0B1C;

  /* Support */
  --orange-500: #F39200;  /* Sunshine Orange */
  --softpink-500: #F59FBC;
  --cream-500: #FBF6EE;   /* Cream Paper -- the base canvas */

  /* Neutrals. The design system splits these in two: surfaces and rules stay
     WARM, because they are tints of the cream paper, while text on cream goes
     PURPLE-TINTED, because it is tinted toward Faith Navy. Do not swap one
     family for the other -- a warm grey used as body text reads as faded
     print. 100 and 200 are the design system's --cream-tint and
     --cream-border. 300 and 500 have no step in the design system and stay
     derived; they only ever draw rules and chart furniture, never text. */
  --neutral-100: #F0EADF;  /* cream-tint */
  --neutral-200: #E6DFD2;  /* cream-border */
  --neutral-300: #CFC9BE;  /* derived -- borders only */
  --neutral-500: #8C867C;  /* derived -- chart axis and grid only */

  /* Text on cream, from the design system's on-cream set */
  --ink-on-cream:        #3E3A52;  /* 10.10:1 on cream, 10.87:1 on white */
  --ink-on-cream-muted:  #6E6884;  /*  4.91:1 on cream,  5.28:1 on white */
  --ink-on-cream-subtle: #8A8597;  /*  3.32:1 -- meta and placeholders only */

  /* ------------------------------------------------------------------------
     3. Semantic roles -- what components actually use.
     Every fill ships with its own foreground, because white text FAILS on
     half this palette (Hope Gold + white = 1.79:1). Never guess the text
     color; use the paired --color-on-* token.
     ------------------------------------------------------------------------ */

  /* Surfaces (the 60% base of the 60-30-10 rhythm) */
  --color-canvas:        var(--cream-500);   /* page background */
  --color-surface:       #FFFFFF;            /* cards, modals, table rows */
  --color-surface-sunken: var(--neutral-100);
  --color-surface-inverse: var(--navy-500);  /* sidebar, footer -- the 30% anchor */
  --color-on-inverse:    #FFFFFF;            /* 14.57:1 */

  /* Ink.
     The design system separates body ink from display ink: headings and
     structure carry Faith Navy -- the 30% anchor of the 60-30-10 rhythm --
     while running text sits one step softer at --ink-on-cream. Both clear
     AAA, so this is a brand decision rather than a contrast one.
     --color-ink-display is applied to h1-h4 in the base rules at the foot of
     this file. */
  --color-ink:         var(--ink-on-cream);        /* body -- 10.10:1 on cream */
  --color-ink-display: var(--navy-500);            /* headings -- 13.54:1 */
  --color-ink-muted:   var(--ink-on-cream-muted);  /* 4.91:1 -- clears AA */
  --color-ink-subtle:  var(--ink-on-cream-subtle); /* timestamps, placeholders */
  --color-ink-onbrand: #FFFFFF;

  /* Primary action.
     Navy, not a bright: it is the only brand color that clears AA at any text
     size (14.57:1), and the 60-30-10 rhythm spends the brights as a 10% pop
     rather than on every button. Use --color-accent for the one hero CTA on
     a page where it is the only bright thing on screen. */
  --color-primary:        var(--navy-500);
  --color-primary-hover:  var(--navy-700);
  --color-primary-active: var(--navy-900);
  --color-on-primary:     #FFFFFF;           /* 14.57:1 */
  --color-primary-soft:   var(--navy-100);   /* selected rows, chips */

  /* Accent -- the 10% pop. Love Pink is 4.22:1 on white text, which clears AA
     only at large/bold sizes, so accent buttons must be >=18px bold. */
  --color-accent:        var(--pink-500);
  --color-accent-hover:  var(--pink-700);
  --color-on-accent:     #FFFFFF;
  --color-accent-soft:   var(--pink-100);

  /* Status. Reserved meanings -- never reuse these for decoration or charts.
     Always ship with an icon + text label, never color alone.

     NB: the design system does now carry a green -- --do-heading #1F8A4C --
     but it is specified as TEXT on a light panel, not as a fill. It manages
     only 3.33:1 against navy and 4.38:1 against white, so nothing readable
     sits on top of it. Success therefore keeps the app's own green as the
     fill and borrows the design system's --do-bg for the soft tint; #1F8A4C
     is available as --color-success-text for green type on that tint.

     The fill pairs with NAVY, not white: white is 2.86:1 here and fails AA,
     navy clears it at 5.08:1 -- the same fix Hope Gold needed. Do not
     restore white text here.
     The old hover/active shades (#099a56 / #088449) are deliberately gone --
     #099a56 failed against both white and navy, so there was no text color
     that worked on it. Success is a status fill, not a button, so it needs
     no hover state. */
  --color-success:      #0BAF64;
  --color-on-success:   var(--navy-500);    /* 5.08:1 -- WHITE FAILS at 2.86:1 */
  --color-success-soft: #E9F7EE;            /* design system --do-bg */
  --color-success-text: #1F8A4C;            /* design system --do-heading */

  --color-warning:     var(--gold-500);
  --color-on-warning:  var(--navy-500);     /* 8.13:1 -- WHITE FAILS at 1.79:1 */
  --color-warning-soft:var(--gold-100);

  /* Informational. Faith Navy lifted to an indigo -- the same step the chart
     palette uses for KC 2, because navy at full depth reads as ink rather than
     as a colour. 8.12:1 on white, and a different hue family from danger red so
     the two stay distinguishable under protanopia/deuteranopia. */
  --color-info:        #534392;
  --color-on-info:     #FFFFFF;             /* 8.12:1 */
  --color-info-soft:   var(--navy-100);

  --color-danger:      var(--red-500);
  --color-on-danger:   #FFFFFF;             /* 5.08:1 */
  --color-danger-soft: var(--red-100);

  /* Lines */
  --color-border:        var(--neutral-200);
  --color-border-strong: var(--neutral-300);
  --color-focus-ring:    var(--pink-500);

  /* ------------------------------------------------------------------------
     4. Chart palette (Analisis)
     Validated, not eyeballed: worst adjacent pair separates by ΔE 18.5 under
     protanopia/deuteranopia and 26.8 under normal vision.

     Two rules keep it valid:
       a) The SLOT ORDER IS THE SAFETY MECHANISM. KC 1..5 must keep these
          exact assignments -- reordering them drops CVD separation from
          18.5 to 8.8, right at the floor.
       b) Gold and orange sit at 2.2:1 against the canvas, below the 3:1
          mark threshold. That is permitted only because the Analisis page
          also renders the attendance ledger table beside the chart. If the
          table view is ever removed, these two need darker steps.

     Colors drift from the hero cores only where they had to: gold darkens
     (the core is too light to be a mark), and navy becomes an indigo (its
     chroma of 0.063 reads as gray, so it cannot carry identity). Red, pink
     and orange are within ΔE 0.006 of the brand.
     ------------------------------------------------------------------------ */
  --chart-kc-1: #DB0022;   /* Faith Red */
  --chart-kc-2: #534392;   /* Faith Navy, lifted to indigo */
  --chart-kc-3: #D8A100;   /* Hope Gold, deepened */
  --chart-kc-4: #ED1387;   /* Love Pink */
  --chart-kc-5: #F19100;   /* Sunshine Orange */

  --chart-events: #6A52BF;  /* the aggregated events series */
  --chart-grid:   var(--neutral-200);
  --chart-axis:   var(--neutral-500);

  /* The manual headcount -- children counted in the room that the scanner
     never saw. Chart furniture rather than a sixth mark: it is a CORRECTION to
     the five KC series, drawn dashed, and a bright would make it compete with
     the very lines it annotates. Unlike --chart-registered this one HAS a hex
     twin in constants.py (HEADCOUNT_COLOR), because the Analisis datasets are
     built server-side and handed to Chart.js as hex. Change one, change both. */
  --chart-headcount: var(--neutral-500);

  /* Analisis Acara compares two counts of the SAME children -- how many
     registered, and how many of those turned up. They are deliberately not two
     competing hues: the muted bar is the roll, the solid one is who came, so
     the gap between them reads as the shortfall. --chart-attended reuses the
     event series colour from the main Analisis chart, which keeps Acara
     looking like Acara across both screens.
     Unlike the KC ramp these are read from CSS at runtime via
     getComputedStyle, so they have no hex twin in constants.py to keep in
     step -- the chart is client-only and never ships colours from the server. */
  --chart-registered: var(--neutral-500);
  --chart-attended:   var(--chart-events);

  /* ------------------------------------------------------------------------
     5. Type
     Two scales. The APP scale is for dense staff screens -- tables, the
     ledger, the Kanban. The DISPLAY scale carries the brand's poster-sized
     type, and belongs only on surfaces with no density pressure: login, the
     parent portal, and the QR window.

     Do not put display sizes in a data table. Body at the brand's 22-32px
     costs about a third of the visible rows, which is felt during a Sunday
     check-in burst.
     ------------------------------------------------------------------------ */
  --font-display: 'Fredoka', 'Trebuchet MS', sans-serif;
  --font-body:    'Nunito', 'Segoe UI', Arial, sans-serif;

  /* App scale -- replaces the 30 ad-hoc sizes currently in the CSS */
  --text-xs:   0.75rem;    /* 12px -- table meta, timestamps */
  --text-sm:   0.875rem;   /* 14px -- labels, captions, helper text */
  --text-base: 1rem;       /* 16px -- body, table cells, inputs */
  --text-lg:   1.125rem;   /* 18px -- emphasised body, card headers */
  --text-xl:   1.5rem;     /* 24px -- section titles */
  --text-2xl:  2rem;       /* 32px -- page headings */
  --text-3xl:  3rem;       /* 48px -- hero numbers (dashboard counts) */

  /* Display scale -- login / portal / QR only */
  --display-sm: 2.25rem;   /* 36px -- section heading */
  --display-md: 3rem;      /* 48px */
  --display-lg: 4rem;      /* 64px -- display title floor */
  --display-xl: 8.25rem;   /* 132px -- display title ceiling */

  /* Weights -- Nunito 600-900 body, Fredoka 400-700 display */
  --weight-body:      600;
  --weight-body-bold: 700;
  --weight-display:   600;
  --weight-display-bold: 700;

  --leading-tight: 1.2;    /* display type */
  --leading-normal: 1.5;   /* body */
  --leading-loose: 1.7;    /* long-form: portal copy */

  --tracking-eyebrow: 0.125em;  /* +2px at 16px -- uppercase labels */

  /* ------------------------------------------------------------------------
     6. Spacing -- the 4px base scale, expressed in rem so it scales with text
     ------------------------------------------------------------------------ */
  --space-1:  0.25rem;   /*  4px */
  --space-2:  0.5rem;    /*  8px */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-6:  1.5rem;    /* 24px */
  --space-8:  2rem;      /* 32px */
  --space-12: 3rem;      /* 48px */
  --space-16: 4rem;      /* 64px */
  --space-safe: 5.625rem;/* 90px -- social safe margin */

  /* ------------------------------------------------------------------------
     7. Radii -- px on purpose. A chunky corner is a shape; scaling it with
     the root font size distorts the brand look. Never sharp.
     ------------------------------------------------------------------------ */
  --radius-xs:   6px;    /* checkboxes and other small controls */
  --radius-sm:   12px;
  --radius-base: 18px;
  --radius-lg:   24px;
  --radius-xl:   30px;
  --radius-pill: 999px;
  --radius-blob: 62% 38% 55% 45% / 50% 58% 42% 50%;

  /* ------------------------------------------------------------------------
     8. Shadows -- navy-tinted, never neutral gray
     ------------------------------------------------------------------------ */
  --shadow-card:  0 10px 30px rgba(42, 36, 71, 0.08);
  --shadow-hero:  0 14px 40px rgba(42, 36, 71, 0.10);
  --shadow-float: 0 24px 60px rgba(42, 36, 71, 0.28);

  /* ------------------------------------------------------------------------
     9. Borders, motion, layering
     ------------------------------------------------------------------------ */
  --border-hairline: 1px;
  --border-thick:    2px;

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --duration-fast: 120ms;
  --duration-base: 200ms;

  --z-dropdown: 100;
  --z-sticky:   200;
  /* The mobile tab bar and its drawer: above a sticky table header, below
     a modal -- a dialog must never be tabbed out from underneath. */
  --z-nav:      300;
  --z-modal:    900;
  --z-toast:    1000;

  /* ------------------------------------------------------------------------
     10. Safe areas -- the parts of the screen the PHONE owns

     A phone spends the top of its screen on the clock and battery, and the
     bottom on the home/back/recent bar or the gesture pill. Both templates ask
     for `viewport-fit=cover`, which is what makes `env(safe-area-inset-bottom)`
     report a real number instead of 0 -- but cover means the page is laid out
     UNDER that furniture, so anything pinned to an edge has to inset itself or
     it sits beneath the clock, or behind the home button.

     env() is legal inside a custom property, so the four insets get names here
     rather than being re-typed at every edge. The `0px` fallback is what a
     desktop browser and an older phone both resolve to -- and it must be `0px`,
     not `0`, because these are fed to calc().

     Left and right are only ever non-zero on a notched phone held sideways,
     which is exactly the case nobody tests and everybody hits eventually.
     ------------------------------------------------------------------------ */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);

  /* --safe-bottom is NOT a constant on Android, and a fixed bottom bar cannot
     use it directly.

     Chrome for Android draws the page under the gesture navigation bar and
     lets that "chin" retract as you scroll, so `env(safe-area-inset-bottom)`
     is ANIMATED: it counts down to 0 while the page moves and back up when it
     stops. Padding a fixed bar with it makes the bar breathe -- it changes
     height on every scroll, relaying itself each frame, and the tab labels
     shift under the thumb that is reaching for them.

     `safe-area-max-inset-bottom` is the same inset at its largest, and it does
     not animate. Chrome's own edge-to-edge guidance is to reserve the MAX and
     then slide the element down by however much the live inset currently
     falls short, so the bar keeps one height and simply rides the chin:

         padding-bottom: var(--safe-bottom-max);
         bottom:         calc(var(--safe-bottom) - var(--safe-bottom-max));

     The 0px fallback is what every other browser resolves to, which collapses
     both lines back to the plain `bottom: 0` this replaced. */
  --safe-bottom-max: env(safe-area-max-inset-bottom, 0px);

  /* How much room the bottom tab bar takes, gesture bar included. Anything
     that has to end above the bar reads this instead of re-deriving it, so the
     bar's height is stated once. Kept at the top level rather than inside the
     mobile media query: the value is harmless above the breakpoint, where
     nothing pinned to the bottom refers to it. */
  --tabbar-h: 3.25rem;
  /* The MAX inset, not the live one: this is how much room the page must keep
     clear below its content, and a reservation that shrinks while you scroll
     would let the last row slide under a bar that is about to come back. */
  --tabbar-space: calc(var(--tabbar-h) + var(--safe-bottom-max));
}

/* Volunteers scanning at a desk shouldn't get motion; respect the OS setting. */
@media (prefers-reduced-motion: reduce) {
  :root { --duration-fast: 0ms; --duration-base: 0ms; }
}

/* --------------------------------------------------------------------------
   10. Base element defaults
   The only place tokens are applied to bare elements. Everything else styles
   classes.
   -------------------------------------------------------------------------- */

/* border-box lives here, not in style.css, because the standalone pages
   (login, portal, the QR window) never load style.css. Without it a
   `width:100%` input adds its padding and border on top of the 100% and
   overflows its container -- which is what made the login inputs sit wider
   than the Masuk button. */
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  background: var(--color-canvas);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--weight-body);
  line-height: var(--leading-normal);
}

h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  /* The navy anchor. Body text around these sits a step softer -- see the Ink
     block above. A heading placed on navy overrides this locally with
     --color-on-inverse. */
  color: var(--color-ink-display);
}

.eyebrow {
  font-family: var(--font-display);
  font-weight: var(--weight-display);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--text-sm);
  color: var(--color-accent);
}

:focus-visible {
  outline: var(--border-thick) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Icons. Sized in em so they scale with whatever text they sit beside, and
   stroked with currentColor so they take that text's colour token -- an icon
   in the sidebar goes white, the same icon on a card goes navy, with no
   per-context rule. Replaces the emoji the UI used to use, which rendered
   differently on every OS and could not be recoloured at all. */
.icon {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.2em;
  flex: none;
  color: currentColor;
}
