/* ========================================================================
   GagaMuller Learning — 01-tokens.css
   Design tokens: the brand palette in both themes, the webfonts, and the one rule that governs motion.

   Loaded by index.html as sheet 1 of 7. The order is the cascade: a
   later sheet may override an earlier one, so it must not be changed.
   ======================================================================== */
  /* =====================================================================
     GagaMuller Learning — shares the GagaMuller design system used by
     My GagaMuller, the Calendar, World Clock and Organisation Chart: the
     gm emblem, violet primary controls, monospace for dates and codes,
     green "Live data" status pill, rounded bordered cards.

     Palette matched to the My GagaMuller portal: a deep indigo canvas in
     dark, a lilac-white one in light, the same violet accent in both, and
     the portal's pastel status tints. Two rules for anything added later:
     no raw hex outside this block, and every colour has a light value as
     well as a dark one - a hex written inline is a colour that only works
     in one theme, which is how the old sign-out link ended up needing an
     override of its own.
     ===================================================================== */
  /* The GagaMuller faces: Averta PE Bold for headings, Avenir for body copy.
     Both licensed, so the files are not in the repo - put them in src/fonts/
     under these names and they take over on their own. font-display:swap, so
     a file that is not there yet costs nothing. */
  @font-face{font-family:'Averta PE'; font-weight:700; font-style:normal; font-display:swap;
    src:url('/fonts/AvertaPE-Bold.woff2') format('woff2');}
  @font-face{font-family:'Avenir'; font-weight:400; font-style:normal; font-display:swap;
    src:url('/fonts/Avenir-Roman.woff2') format('woff2');}
  @font-face{font-family:'Avenir'; font-weight:500; font-style:normal; font-display:swap;
    src:url('/fonts/Avenir-Medium.woff2') format('woff2');}
  @font-face{font-family:'Avenir'; font-weight:700; font-style:normal; font-display:swap;
    src:url('/fonts/Avenir-Heavy.woff2') format('woff2');}
  :root{
    /* ---------- dark (default) ----------
       Canvas is Dark Blue, one of the three brand background colours. Surface
       and raised are that same hue lifted, so every panel is the brand colour
       at a different depth rather than a new colour invented for the purpose. */
    /* Matched to the Organisation Chart: a purple-black canvas rather than a
       blue one, with the cards a shade above it and the purple swash carrying
       the colour rather than the page behind it. */
    --bg:#120C1E; --surface:#1C1428; --raised:#261B36;
    --ink:#FFFFFF; --muted:#C6BDE6; --line:rgba(255,255,255,.09);
    /* Course Curator's badge. Pink rather than another purple, because it has
       to be told apart from the brand at a glance in a column of role badges -
       and every other tone in the palette already means something. */
    --pink:#FF7ABF; --pink-soft:rgba(255,58,158,.28);
    --brand:#B681FF;                    /* Light Purple - text, icons, borders */
    --brand-solid:#7215F4;              /* GagaMuller Purple - solid fills */
    --brand-soft:rgba(114,21,244,.30);
    --brand-grad:linear-gradient(135deg,#7215F4,#500FAB);
    --brand-shadow:0 3px 16px rgba(114,21,244,.5);
    --brand-glow:rgba(114,21,244,.45);
    /* Text printed ON a filled chip. Dark theme fills are the bright brand
       status colours, so it is Black; the light theme inverts both. */
    --ink-on-strong:#101010;
    --live:#0CF571;
    --ok:#0CF571; --ok-soft:rgba(12,245,113,.16);
    --ok-grad:linear-gradient(135deg,#0CF571,#0AA84F);
    --warn:#E7891C; --warn-soft:rgba(231,137,28,.18);
    --warn-grad:linear-gradient(135deg,#E7891C,#A35F0A);
    --bad:#F25024; --bad-soft:rgba(242,80,36,.18);
    --info:#5892DE; --info-soft:rgba(88,146,222,.18);
    --c-sec:#5892DE; --c-ai:#B681FF; --c-phish:#E7891C; --c-ind:#0CF571;
    --fill:rgba(182,129,250,.08); --fill-2:rgba(182,129,250,.18); --hair:rgba(182,129,250,.12);
    --scrim:rgba(16,16,16,.72);
    --radius:16px; --radius-s:11px;
    /* Brand faces first, what was here before as the fallback. Averta PE and
       Avenir are licensed, so the woff2 files cannot travel in this repo: drop
       them into src/fonts/ and the @font-face block above picks them up with
       no other change. Until they are there the page renders in Outfit and
       Inter, exactly as it does today. */
    --display:'Avenir','Avenir Next','Inter','Segoe UI',system-ui,sans-serif;
    --brand-face:'Averta PE','Avenir Next','Outfit','Inter','Segoe UI',system-ui,sans-serif;
    --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
    --shadow:0 18px 46px rgba(0,0,0,.6);
    /* Two lights, not one: a soft violet high on the left, and the broad
       sweep low across the page that the Organisation Chart leads with. */
    --glow-1:rgba(114,21,244,.20); --glow-2:rgba(80,15,171,.14);
    /* The four stops of the field the arc sits in, top to bottom, and the
       colour of the hairlines. Changing these four is the whole of retheming
       the background - the canvas reads them rather than holding its own. */
    --art-1:#1B1428; --art-2:#241641; --art-3:#351A6B; --art-4:#5C27BE;
    --art-accent:#A78BFA; --art-op:1;
  }
  /* ---------- light mode ----------
     White surfaces, Black ink, Grey rules - the three brand neutrals - with
     GagaMuller Purple as the accent.

     The status colours need a word. #0CF571, #E7891C and #F25024 are the brand
     values and they are used as-is on the dark theme, where they carry text at
     10.7:1, 6.0:1 and 4.9:1. On white they carry text at 1.5:1, 2.6:1 and
     3.5:1 - unreadable, near-unreadable and below AA. So the light theme uses
     a darkened tint of the same hue for anything a word sits on or in, which
     is the smallest departure that keeps the page legible. The brand values
     themselves stay exact wherever they are a fill, a dot or a chart series. */
   body.light{
      --bg:#ECECEF; --surface:#FFFFFF; --raised:#F5F5F7;
    --ink:#101010; --muted:#56545E; --line:#E5E5E5;
    --pink:#C2186F; --pink-soft:rgba(255,58,158,.13);
    --brand:#7215F4; --brand-solid:#7215F4; --brand-soft:rgba(114,21,244,.10);
    --brand-grad:linear-gradient(135deg,#7215F4,#500FAB);
    --brand-shadow:0 3px 14px rgba(114,21,244,.30);
    --brand-glow:rgba(114,21,244,.26);
    --ink-on-strong:#FFFFFF;
    --live:#07753C;
    --ok:#07753C; --ok-soft:rgba(12,245,113,.16);
    --ok-grad:linear-gradient(135deg,#07753C,#055C2E);
    --warn:#96570A; --warn-soft:rgba(231,137,28,.18);
    --warn-grad:linear-gradient(135deg,#96570A,#7A4708);
    --bad:#C0350F; --bad-soft:rgba(242,80,36,.14);
    --info:#3074A7; --info-soft:rgba(48,116,167,.12);
    --c-sec:#3074A7; --c-ai:#7215F4; --c-phish:#96570A; --c-ind:#07753C;
    --fill:rgba(16,16,16,.045); --fill-2:rgba(16,16,16,.10); --hair:#E5E5E5;
    --scrim:rgba(26,20,65,.45);
    --shadow:0 18px 46px rgba(16,16,16,.14);
    --glow-1:rgba(114,21,244,.10); --glow-2:rgba(182,129,250,.10);
    /* Same four stops, same arc, inverted: near-white at the top falling to a
       pale lilac. The accent is brand purple rather than lavender, because a
       lavender hairline on white is invisible. */
    --art-1:#FAFAFC; --art-2:#F3F0FB; --art-3:#E9E1FA; --art-4:#D6C4F8;
    --art-accent:#7215F4; --art-op:.42;
  }

  /* smooth theme switch — one GPU-composited crossfade of the whole page
     (no per-element transitions, so it never janks on the course grid) */
  ::view-transition-old(root), ::view-transition-new(root){
    animation-duration:.42s; animation-timing-function:cubic-bezier(.4,0,.2,1);
  }

  *{box-sizing:border-box; margin:0; padding:0;}
  /* scrollbar-gutter:stable reserves the scrollbar's width whether or not it
     is there. Opening a drop-down makes the page taller, the scrollbar
     appears, the viewport narrows and every column shifts sideways a few
     pixels - which reads as the page flinching each time something opens. */
  html{scroll-behavior:smooth; scrollbar-gutter:stable;
    scrollbar-width:thin; scrollbar-color:rgba(114,21,244,.5) #120C1E;}
  body{
    background-color:var(--bg); color:var(--ink);
    font-family:var(--display); font-size:15px; line-height:1.55;
    /* The wave-arc field. Two declarations, not one: the second interpolates in
       oklab, which is what stops a purple-to-violet ramp going muddy grey
       through the middle. A browser that does not know oklab throws that line
       away and keeps the first, so both get a gradient - one is just better. */
    background-image:linear-gradient(180deg,
      var(--art-1) 0%, var(--art-2) 46%, var(--art-3) 76%, var(--art-4) 100%);
    background-image:linear-gradient(180deg in oklab,
      var(--art-1) 0%, var(--art-2) 46%, var(--art-3) 76%, var(--art-4) 100%);
    /* Fixed to the viewport, so the field stays put and the page scrolls over
       it rather than dragging the gradient along. */
    background-attachment:fixed;
    background-repeat:no-repeat;
    background-size:100% 100%;
    min-height:100vh;
  }
  /* The wave arc itself, drawn on a canvas by BGART further down. z-index:-1
     puts it above the page background and below every bit of content - a
     positioned element at z-index:0 would paint OVER the text. */
  .bgart{position:fixed; inset:0; width:100%; height:100%; display:block;
    z-index:-1; pointer-events:none; opacity:var(--art-op);}
  /* The sign-in gate is a short paragraph in the middle of an otherwise empty
     screen - exactly where the ribbons cross. Dim them while it is showing.
     :has() is a progressive enhancement here: a browser without it just gets
     the normal artwork. */
  body:has(#gate:not([hidden])) .bgart{opacity:calc(var(--art-op) * .5);}
  ::-webkit-scrollbar{width:10px; height:10px;}
  ::-webkit-scrollbar-track{background:#120C1E;}
  ::-webkit-scrollbar-thumb{background:rgba(114,21,244,.55); border-radius:999px; border:2px solid #120C1E;}
  ::-webkit-scrollbar-thumb:hover{background:rgba(114,21,244,.8);}
  html.light{scrollbar-color:rgba(114,21,244,.5) #E5E5E5;}
  html.light::-webkit-scrollbar-track, html.light ::-webkit-scrollbar-track{background:#E5E5E5;}
  html.light::-webkit-scrollbar-thumb, html.light ::-webkit-scrollbar-thumb{background:rgba(114,21,244,.5); border:2px solid #E5E5E5;}

  button,input,select,textarea{font:inherit; color:inherit;}
  button{cursor:pointer; border:0; background:none;}
  a{color:var(--brand); text-decoration:none;}
  h1,h2,h3,h4{font-family:var(--display); font-weight:800; letter-spacing:-.3px; line-height:1.15;}
  h1{font-size:clamp(26px,3.4vw,36px); letter-spacing:-.6px;}
  h2{font-size:19px;} h3{font-size:15.5px; font-weight:700;} h4{font-size:14px; font-weight:700;}
  p{margin:0 0 .7em;}
  ::selection{background:var(--brand-soft);}
  .mono{font-family:var(--mono); font-size:.85em; letter-spacing:.1px;}
  .hidden{display:none !important;}
  /* The UA stylesheet's [hidden]{display:none} loses to ANY author display rule,
     so .gate{display:grid} kept the sign-in gate painted over a working app -
     a permanent "Signing you in" spinner with no error anywhere. Author-level
     rule so setting .hidden = true actually hides things. */
  [hidden]{display:none !important;}
  .sr{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);}

  @keyframes pageIn{from{opacity:0; transform:translateY(8px);} to{opacity:1; transform:none;}}
  @keyframes popIn{from{opacity:0; transform:translateY(-6px) scale(.98);} to{opacity:1; transform:none;}}
  @keyframes slideIn{from{opacity:0; transform:translateX(22px);} to{opacity:1; transform:none;}}
  @keyframes rise{from{opacity:0; transform:translateY(14px);} to{opacity:1; transform:none;}}
  @keyframes pop{0%{transform:scale(.72); opacity:0;} 60%{transform:scale(1.07);} 100%{transform:scale(1); opacity:1;}}
  .pop{animation:pop .45s cubic-bezier(.22,1,.36,1);}

  /* ---------- moving between tabs ----------
     Only the page body moves. The topbar, header and nav are rebuilt on every
     render but carry no animation, so they read as furniture that stays put
     while the content behind them changes - which is what makes a tab switch
     feel like one app rather than a series of page loads.

     Direction comes from the nav order, so Settings -> Dashboard arrives from
     the left and Dashboard -> Settings from the right. The distance is
     deliberately small: 12px is enough for the eye to read a direction and
     small enough that it never looks like a slide deck. Both the opacity and
     the transform are GPU-composited, so nothing here forces a layout pass.

     The whole block is switched off by the prefers-reduced-motion rule below,
     which leaves the content in its final position with no animation at all. */
  @keyframes viewInFwd {from{opacity:0; transform:translate3d(12px,4px,0);} to{opacity:1; transform:none;}}
  @keyframes viewInBack{from{opacity:0; transform:translate3d(-12px,4px,0);} to{opacity:1; transform:none;}}
  @keyframes viewInFade{from{opacity:0; transform:translate3d(0,6px,0);}    to{opacity:1; transform:none;}}
  .view.in-fwd {animation:viewInFwd  .26s cubic-bezier(.22,.61,.36,1) both;}
  .view.in-back{animation:viewInBack .26s cubic-bezier(.22,.61,.36,1) both;}
  .view.in-fade{animation:viewInFade .30s cubic-bezier(.22,.61,.36,1) both;}

  /* ---- while the walkthrough is running ---------------------------------
     THE TOUR MEASURES ONCE, AND THESE MOVE THE TARGET AFTER IT. A step is drawn
     in the same task that rendered the page or inserted the dialog, and every
     one of these carries `both`, so the from-keyframe is already applied when
     getBoundingClientRect() is called: the tab slide is measured 12px across
     and 4px down from where it lands, and a dialog 22px above it. #tourRoot is
     fixed to the body, outside #view and #modal-root, so the ring does not
     travel with the target the way it would if it were inside them - it simply
     ends up beside the thing it is pointing at, and nothing puts it right,
     because the correction only ever came from a scroll.

     Standing them down for the length of a tour is smaller than measuring
     twice and timing the second one against three different durations, and it
     cannot drift when one of those durations changes. */
  body.touring .view.in-fwd, body.touring .view.in-back, body.touring .view.in-fade,
  body.touring .ov, body.touring .mdl{animation:none;}
  /* AND THE COURSE EDITOR'S OWN ENTRANCES. Opening the slide deck repaints
     #edPane with the .pane-in lift and fades the stage with .dk-turn - both keyframes
     the ring would measure mid-flight, so it landed beside a half-transparent,
     still-sliding .dk-strip and the walkthrough looked broken the instant the deck
     opened. The same decision as .view and .mdl above: stand the editor's entrances
     down for the length of a tour so every deck target is final and solid the moment
     the ring reads it. */
  body.touring #edPane.pane-in, body.touring .dk-stage.dk-turn{animation:none;}
  /* AND THE FOLDS, for the same reason from the other direction. A drop-down
     takes .26s to grow, and the tour measures the moment the press lands - so
     opening a Settings section scrolled the page to where the next heading was
     a quarter of a second ago, and the ring came to rest beside it rather than
     on it. Opening is instant for the length of a tour; the layout is final by
     the time anything reads it. */
  body.touring .dept-fold, body.touring .lib-fold,
  body.touring .dept-clip, body.touring .lib-clip,
  body.touring .comp-fold, body.touring .comp-clip,
  body.touring .cpd-fold, body.touring .cpd-clip{transition:none;}
  body.touring .comp-group[open] > .col{animation:none;}

  @media (prefers-reduced-motion: reduce){
    html{scroll-behavior:auto;}
    *{animation:none!important; transition:none!important;}
  }

