/* Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
 */
@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(fonts/font-1.woff2?v=790b255c10c3) format('woff2');
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(fonts/font-2.woff2?v=7f655207c2f6) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Noto Sans';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100%;
  font-display: swap;
  src: url(fonts/font-3.woff2?v=51ca196f49a3) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Geologica by Monokrom (Sindre Bremnes and Frode Helland), SIL Open Font License 1.1 (https://github.com/googlefonts/geologica). */
@font-face{font-family:'Geologica';font-style:normal;font-weight:300 800;font-display:swap;src:url(fonts/geologica-greek.woff2) format('woff2');unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}
@font-face{font-family:'Geologica';font-style:normal;font-weight:300 800;font-display:swap;src:url(fonts/geologica-latin.woff2) format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

/* ---------- Tokens: studio greys, ink, one neon accent, semantic states ---------- */
:root{
 color-scheme:light;
 --bg:#ededed;--sheet:#f6f6f6;--surface:#ffffff;--surface-2:#ececec;--surface-3:#e0e0e0;
 --line:#dedede;--line-strong:#c6c6c6;
 --text:#0e0e0e;--text-2:#3b3b3b;--text-3:#636363;
 --accent:#ef1a84;--accent-hover:#d40f72;--on-accent:#0e0e0e;--accent-soft:#ef1a8414;
 --success:#0f7a3d;--success-soft:#0f7a3d14;
 --warning:#985200;--warning-soft:#e8890c1a;
 --danger:#d61f3a;--danger-soft:#d61f3a12;
 --info:var(--text);--info-soft:var(--surface-2);
 --shadow-sm:0 1px 2px #0e0e0e12,0 1px 1px #0e0e0e0a;
 --shadow-md:0 12px 28px -12px #0e0e0e2e,0 2px 6px -2px #0e0e0e14;
 --shadow-lg:0 32px 64px -24px #0e0e0e40,0 6px 16px -6px #0e0e0e17;
 --highlight:inset 0 1px 0 #ffffffcc;
 /* Liquid glass for surfaces floating over the hero, approximated in plain CSS (true refraction needs SVG filters in
    backdrop-filter, which only Chromium supports): light catches the upper-left rim and leaves through the
    lower-right, the edge glows where the glass thickens, its underside darkens like a lens, and a sheen lies along
    the top like on the event blocks. */
 --glass-rim:inset 1px 1px 0 #ffffff,inset -1px -1px 0 #ffffff40,inset 0 -10px 16px -12px #0e0e0e14,inset 0 0 14px -6px #ffffffb3;
 --glass-sheen:linear-gradient(180deg,#ffffff66,#ffffff00 46%);
 /* The header pill's fill; clearer in light so the sheen and the lit top edge read against it. */
 --glass-fill:color-mix(in srgb,var(--surface) 64%,transparent);
 /* Shapes: everything you press is a pill; surfaces are soft rectangles. */
 --pill:999px;--r-sheet:28px;--r-lg:20px;--r-md:14px;--r-sm:10px;--r-block:12px;
 --ease:cubic-bezier(.16,1,.3,1);--spring:cubic-bezier(.16,1,.3,1);
 --studio:#e1e1e1;--studio-glow:#fcfcfc;
 --side:340px;--hour:56px;--bar:76px;
 /* Scroll offset where the planner sheet reaches the header: hero height minus the overlap. */
 --dock:calc(max(100svh,600px) - 13svh);
 font:400 14px/1.5 Geologica,'Noto Sans',system-ui,sans-serif;color:var(--text);background:var(--bg);
 font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;
}
:root[data-theme=dark]{
 color-scheme:dark;
 --bg:#0b0b0b;--sheet:#111111;--surface:#181818;--surface-2:#1e1e1e;--surface-3:#282828;
 --line:#242424;--line-strong:#363636;
 --text:#f2f2f2;--text-2:#bdbdbd;--text-3:#8f8f8f;
 --accent:#ff4fa8;--accent-hover:#ff73ba;--on-accent:#0e0e0e;--accent-soft:#ff4fa81f;
 --success:#4ade80;--success-soft:#4ade801a;
 --warning:#fbbf24;--warning-soft:#fbbf241a;
 --danger:#ff5c6c;--danger-soft:#ff5c6c1c;
 --shadow-sm:0 1px 2px #0008;
 --shadow-md:0 14px 30px -14px #000d,0 2px 6px -2px #0009;
 --shadow-lg:0 36px 72px -28px #000,0 6px 16px -6px #000a;
 --highlight:inset 0 1px 0 #ffffff12;
 --glass-rim:inset 1px 1px 0 #ffffff47,inset -1px -1px 0 #ffffff1a,inset 0 -10px 16px -12px #00000066,inset 0 0 14px -6px #ffffff1f;
 --glass-sheen:linear-gradient(180deg,#ffffff26,#ffffff00 46%);
 --glass-fill:color-mix(in srgb,var(--surface) 72%,transparent);
 --studio:#040404;--studio-glow:#1e1e1e;
}
/* Raised surfaces lean on soft elevation; on ink, where shadows vanish, a hairline marks the edge instead. */
:root[data-theme=dark] :is(dialog,.data-menu-actions,.hero-tip){border-color:var(--line)}
/* Spring with a ~5% overshoot for things that land: digits, flights, blocks. */
@supports (transition-timing-function:linear(0,1)){:root{--spring:linear(0, .02 2%, .09 4.5%, .36 10%, .72 17%, .93 23%, 1.03 29%, 1.05 35%, 1.03 43%, 1.004 55%, .998 70%, 1)}}
*,*::before,*::after{box-sizing:border-box}
body{margin:0;min-height:100dvh;background:var(--bg)}
/* Lenis smooth scroll (vendored lenis.css). */
html.lenis,html.lenis body{height:auto}
.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}
.lenis [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-smooth iframe{pointer-events:none}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
::selection{background:color-mix(in srgb,var(--accent) 32%,transparent)}
input,textarea{caret-color:var(--accent)}
*{scrollbar-width:thin;scrollbar-color:var(--line-strong) transparent}
a{color:var(--text);text-underline-offset:3px;text-decoration-thickness:1px}
a:hover{text-decoration-color:var(--accent);text-decoration-thickness:2px}
p{line-height:1.6}
h1,h2,h3{margin:0;text-wrap:balance}
[hidden]{display:none!important}
.sprite{position:absolute;width:0;height:0;overflow:hidden}
.icon{width:16px;height:16px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
:focus-visible{outline:2px solid var(--text);outline-offset:2px}

/* ---------- Controls: pills that press, never lift ---------- */
button,input,select,textarea{font:inherit;color:inherit}
button,select,summary,input[type=color],input[type=checkbox]{cursor:pointer}
button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:38px;padding:8px 16px;border:1px solid var(--line-strong);border-radius:var(--pill);background:var(--surface);box-shadow:var(--shadow-sm);font-size:13px;font-weight:550;line-height:1.3;white-space:nowrap;transition:background-color .18s,border-color .18s,color .18s,box-shadow .18s,transform .18s var(--ease)}
button:hover{background:var(--surface-2);border-color:var(--line-strong)}
button:active:not(:disabled){transform:scale(.96)}
button:disabled{opacity:.5;cursor:wait}
.primary{background:var(--text);border-color:transparent;color:var(--sheet);box-shadow:var(--shadow-md)}
.primary:hover{background:color-mix(in srgb,var(--text) 80%,var(--accent));border-color:transparent}
.quiet{background:transparent;border-color:transparent;box-shadow:none}
.quiet:hover{background:color-mix(in srgb,var(--text) 7%,transparent);border-color:transparent}
.link-button{display:inline;min-height:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none;color:var(--text);font-weight:600;text-decoration:underline;text-underline-offset:3px;font-size:inherit;white-space:normal}
.link-button:hover{background:none;text-decoration-color:var(--accent)}
input,select{min-width:0;height:40px;padding:0 16px;border:1px solid var(--line);border-radius:var(--pill);background:var(--surface-2);font-size:13px;transition:border-color .18s,box-shadow .18s,background-color .18s}
input:hover,select:hover{border-color:var(--line-strong)}
input:focus-visible,select:focus-visible{outline:0;border-color:var(--text);box-shadow:0 0 0 3px color-mix(in srgb,var(--text) 14%,transparent);background:var(--surface)}
input[type=search]{appearance:none}
input::placeholder{color:var(--text-3);opacity:1}
input[type=checkbox]{width:18px;height:18px;padding:0;accent-color:var(--text);flex-shrink:0}
input[type=file]{height:auto;padding:10px 14px;border-radius:var(--r-md)}
select option{background:var(--surface);color:var(--text)}

/* ---------- Header: a floating pill over the hero that docks onto a solid bar over the planner ---------- */
.masthead{position:fixed;top:0;left:0;right:0;z-index:50;isolation:isolate;display:flex;align-items:center;justify-content:space-between;gap:20px;height:var(--bar);padding:10px calc(max(12px,(100% - 1600px)/2) + 10px)}
.masthead::before{content:'';position:absolute;inset:10px max(12px,calc((100% - 1600px)/2));z-index:-1;border:1px solid color-mix(in srgb,var(--line-strong) 60%,transparent);border-radius:var(--pill);background:var(--glass-sheen),var(--glass-fill);backdrop-filter:blur(20px) saturate(1.8);-webkit-backdrop-filter:blur(20px) saturate(1.8);box-shadow:0 12px 32px -20px #0e0e0e59,var(--glass-rim)}
.masthead::after{content:'';position:absolute;inset:0;z-index:-2;background:var(--sheet);border-bottom:1px solid var(--line);opacity:0}
@supports (animation-timeline:scroll()){
 .masthead::after{animation:dock linear both;animation-timeline:scroll(root);animation-range:calc(var(--dock) - var(--bar) - 160px) calc(var(--dock) - var(--bar) - 20px)}
 @keyframes dock{to{opacity:1}}
}
@supports not (animation-timeline:scroll()){.masthead::after{opacity:1}}
.brand{display:flex;align-items:center;gap:12px;padding-left:4px;color:var(--text);text-decoration:none}
.brand-mark{display:grid;place-content:center;width:38px;height:38px;flex-shrink:0;border-radius:var(--pill);background:var(--text);color:var(--sheet);font-size:11px;font-weight:700;line-height:1;letter-spacing:.02em;text-indent:.04em}
.brand-text{font-size:13px;font-weight:600;line-height:1.25}
.brand-text small{display:block;color:var(--text-3);font-size:12px;font-weight:400}
.header-actions{display:flex;align-items:center;gap:2px}
.icon-btn{width:38px;min-height:38px;padding:0}
.icon-btn .icon{color:var(--text-2)}
.theme-control{position:relative;display:flex;align-items:center}
.theme-control .icon{position:absolute;left:13px;pointer-events:none;color:var(--text-3)}
.theme-control .chev{left:auto;right:12px;width:14px;height:14px}
.theme-control select{height:38px;padding:0 32px 0 36px;appearance:none;background:transparent;border-color:transparent;font-weight:550}
.theme-control select:hover{background:color-mix(in srgb,var(--text) 7%,transparent)}
.data-menu{position:relative}
.data-menu summary{display:flex;align-items:center;gap:6px;min-height:38px;padding:8px 14px;border-radius:var(--pill);font-size:13px;font-weight:550;list-style:none;transition:background-color .18s}
.data-menu summary::-webkit-details-marker{display:none}
.data-menu summary:hover,.data-menu[open] summary{background:color-mix(in srgb,var(--text) 7%,transparent)}
.data-menu-actions{position:absolute;right:0;top:calc(100% + 12px);z-index:60;width:300px;max-width:calc(100vw - 32px);padding:8px;background:var(--surface);border:1px solid transparent;border-radius:var(--r-lg);box-shadow:var(--shadow-lg),var(--highlight);transform-origin:top right}
.data-menu-actions button{display:flex;justify-content:flex-start;width:100%;border:0;box-shadow:none;background:none;border-radius:var(--r-sm);font-weight:500;text-align:left;white-space:normal}
.data-menu-actions button:hover{background:var(--surface-2)}
.data-menu[open] .data-menu-actions{animation:pop-in .2s var(--ease)}

/* ---------- Hero: the 3D week in a studio light, then the planner sheet rises over it ---------- */
/* touch-action: horizontal swipes spin the 3D board (free view); vertical swipes and pinch stay native. */
.hero{position:sticky;top:0;z-index:0;height:100svh;min-height:600px;overflow:hidden;isolation:isolate;touch-action:pan-y pinch-zoom;background:radial-gradient(46% 58% at 64% 46%,var(--studio-glow),transparent 74%),linear-gradient(180deg,var(--studio),var(--bg) 96%)}
/* The 3D stage floats above the planner sheet: week3d.js lands the board on the week grid, then hides the layer. */
.hero-stage{position:fixed;inset:0;z-index:2;pointer-events:none}
.hero-stage.docked{visibility:hidden}
/* Reduced motion: a still picture that scrolls away with the hero. */
.hero-stage.still{position:absolute;bottom:auto;height:max(100svh,600px)}
.hero-stage canvas{display:block;width:100%;height:100%;opacity:0;transition:opacity 1.4s var(--ease)}
.hero-stage.live canvas{opacity:1}
.hero-tip{position:absolute;left:0;top:0;z-index:1;display:grid;gap:2px;max-width:260px;padding:9px 14px 10px;border:1px solid transparent;border-radius:var(--r-md);background:var(--glass-sheen),color-mix(in srgb,var(--surface) 86%,transparent);backdrop-filter:blur(12px) saturate(1.8);-webkit-backdrop-filter:blur(12px) saturate(1.8);box-shadow:var(--shadow-md),var(--glass-rim);color:var(--text-2);font-size:12px;line-height:1.4;pointer-events:none;opacity:0;transition:opacity .18s}
.hero-tip.on{opacity:1}
.hero-tip b{color:var(--text);font-size:13px;font-weight:600}
.hero-copy{position:absolute;left:max(24px,4vw);bottom:calc(13svh + 44px);max-width:min(46rem,50vw);pointer-events:none}
.hero-title{font-size:clamp(52px,7.2vw,96px);font-weight:620;line-height:.98;letter-spacing:-.04em}
/* Each line rises out of its own mask; the padding keeps Greek descenders unclipped. */
.hero-title .line{display:block;overflow:hidden;padding:.04em 0 .14em;margin-bottom:-.14em}
.hero-title .line>span{display:inline-block;animation:line-in 1.2s .1s var(--ease) both}
.hero-title .line+.line>span{animation-delay:.19s}
.hero-title em{font-style:normal;color:var(--accent)}
.hero-sub{max-width:31ch;margin:22px 0 0;color:var(--text-2);font-size:clamp(16px,1.3vw,19px);line-height:1.5;animation:fade-up 1s .42s var(--ease) both}
.hero-cta{display:inline-flex;align-items:center;min-height:52px;margin-top:28px;padding:0 26px;border-radius:var(--pill);background:var(--text);color:var(--sheet);box-shadow:var(--shadow-md);font-size:14px;font-weight:600;text-decoration:none;pointer-events:auto;transition:background-color .18s,transform .18s var(--ease);animation:fade-up 1s .54s var(--ease) both}
.hero-cta:hover{background:color-mix(in srgb,var(--text) 80%,var(--accent));text-decoration:none}
.hero-cta:active{transform:scale(.96)}
@supports (animation-timeline:scroll()){
 .hero-copy{animation:copy-recede linear both;animation-timeline:scroll(root);animation-range:0 60svh}
 @keyframes copy-recede{to{transform:translateY(-10svh);opacity:0}}
}

/* ---------- Planner sheet ---------- */
main{position:relative;z-index:1;margin:-13svh 0 0;padding:0 max(20px,2vw,calc((100% - 2080px)/2 + 2vw)) 24px;background:var(--sheet);border-radius:var(--r-sheet) var(--r-sheet) 0 0;box-shadow:0 -1px 0 color-mix(in srgb,var(--line-strong) 50%,transparent),0 -30px 60px -34px #0e0e0e59;scroll-margin-top:var(--bar);animation:sheet-in 1.2s .36s var(--ease) backwards}
/* Fill backwards only: a transform left filling on main would trap the fixed course drawer inside it. */
.intro{padding:26px 0 22px}
.intro-actions{display:flex;align-items:center;justify-content:space-between;gap:12px}
/* Season: an ink thumb slides under the active label. */
.season-switch{position:relative;isolation:isolate;display:flex;padding:4px;border:1px solid var(--line);border-radius:var(--pill);background:var(--surface-2)}
.season-switch::before{content:'';position:absolute;top:4px;bottom:4px;left:4px;z-index:-1;width:calc(50% - 4px);border-radius:var(--pill);background:var(--text);box-shadow:var(--shadow-md);transition:transform .5s var(--spring)}
.season-switch:has([data-season=spring][aria-pressed=true])::before{transform:translateX(100%)}
.season-switch button{flex:1;min-width:116px;min-height:38px;border:0;border-radius:var(--pill);background:none;box-shadow:none;color:var(--text-3);font-size:13px;font-weight:550;transition:color .3s}
.season-switch button:hover{background:none;color:var(--text)}
.season-switch button[aria-pressed=true]{color:var(--sheet)}
.sync-box{display:flex;flex-direction:row-reverse;align-items:center;gap:14px}
.sync-box button{min-height:44px;padding:10px 20px}
#refresh:disabled .icon{animation:spin .9s linear infinite}
#syncMeta{color:var(--text-3);font-size:12px}
.notice{display:flex;gap:10px;margin:0 0 16px;padding:13px 18px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-2);font-size:13px;line-height:1.55;animation:pop-in .4s var(--ease)}
/* A new version is ready (sw.js already has its files, so the reload is instant): a pill floating under the header. */
.update-toast{position:fixed;left:50%;top:calc(var(--bar) + 10px);z-index:49;display:flex;align-items:center;gap:6px;max-width:calc(100vw - 24px);padding:5px 5px 5px 18px;border-radius:var(--pill);background:var(--surface);box-shadow:var(--shadow-lg),var(--highlight);font-size:13.5px;font-weight:550;white-space:nowrap;transform:translateX(-50%);animation:toast-in .4s var(--ease) both}
.update-toast[hidden]{display:none}
.update-toast span{margin-right:6px}
@keyframes toast-in{from{opacity:0;transform:translate(-50%,-10px) scale(.96)}}
/* After a refresh or an import: what changed against the previous data, collapsed right under the notice. */
.update-report{margin:-6px 0 16px;padding:0 18px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface);font-size:13px;line-height:1.55;animation:pop-in .4s var(--ease)}
.update-report summary{display:flex;align-items:center;gap:10px;padding:13px 0;list-style:none;font-weight:600;cursor:pointer}
.update-report summary::-webkit-details-marker{display:none}
.update-report summary::before{content:'';width:7px;height:7px;border-right:2px solid var(--text-3);border-bottom:2px solid var(--text-3);transform:rotate(-45deg);transition:transform .3s var(--ease);flex-shrink:0}
.update-report[open] summary{border-bottom:1px solid var(--line)}
.update-report[open] summary::before{transform:rotate(45deg)}
.update-report h3{margin:14px 0 6px;font-size:13px;font-weight:600}
.update-report ul{display:grid;gap:6px;margin:0 0 14px;padding:0;list-style:none}
.update-report li,.update-report p{color:var(--text-2)}
.update-report li strong{color:var(--text);font-weight:600}
.update-report p{margin:12px 0}
.notice.error{border-color:color-mix(in srgb,var(--danger) 30%,transparent);background:var(--danger-soft);color:var(--text)}
.period-label{margin:2px 0 14px}

/* ---------- Workspace: a slim course rail beside an edge-to-edge week ---------- */
.workspace{display:grid;grid-template-columns:var(--side) minmax(0,1fr);align-items:start;border-top:1px solid var(--line)}
.sidebar{container-type:inline-size;position:sticky;top:calc(var(--bar) + 8px);display:flex;flex-direction:column;min-width:0;max-height:calc(100dvh - var(--bar) - 20px);padding:22px 24px 16px 0}
.panel-heading{display:flex;align-items:center;justify-content:space-between;gap:12px}
.panel-heading h2{font-size:20px;font-weight:600;letter-spacing:-.025em}
.mono{padding:3px 10px;border-radius:var(--pill);background:var(--surface-2);color:var(--text-2);font-size:11.5px;font-weight:600}
.search-wrap{position:relative;display:flex;align-items:center}
.search-wrap .icon{position:absolute;left:16px;color:var(--text-3);pointer-events:none}
.search-wrap input{width:100%;height:44px;padding-left:42px}
.filters{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px}
.filters select{padding:0 10px 0 14px}
@container (max-width:270px){.filters{grid-template-columns:1fr}}
.subtle{color:var(--text-3);font-size:12.5px;line-height:1.55}
.list-help{margin:12px 4px 8px}
.course-list{flex:1;min-height:120px;margin:0;padding:0 2px 0 0;overflow:auto}
.course{position:relative;display:grid;grid-template-columns:12px minmax(0,1fr) 32px;column-gap:12px;align-items:start;padding:13px 2px;border-bottom:1px solid var(--line)}
.course.selected .course-title{font-weight:650}
.course.just{animation:row-flash .9s var(--ease)}
/* State swatch: fill = selected, ring = hidden meetings, green = passed, amber dashes = review. */
.link-mark{width:12px;height:12px;margin-top:4px;border:1.5px solid var(--line-strong);border-radius:var(--pill);transition:background-color .25s,border-color .25s,transform .35s var(--ease)}
.selected .link-mark{background:var(--course-color);border-color:var(--course-color)}
.gapped .link-mark{background:transparent;border:3px solid var(--course-color)}
.review .link-mark{background:var(--warning-soft);border:1.5px dashed var(--warning)}
.passed .link-mark{background:var(--success);border-color:var(--success)}
.course.just .link-mark{animation:pop .5s var(--ease)}
.course-title{display:block;min-height:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none;text-align:left;font-size:13.5px;font-weight:600;line-height:1.35;white-space:normal}
.course-title:hover{background:none;text-decoration:underline;text-decoration-color:var(--accent);text-decoration-thickness:2px;text-underline-offset:3px}
.course-meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;margin-top:3px;color:var(--text-3);font-size:12px}
.course-meta:empty{display:none}
.course-meta .sep{width:1px;height:10px;background:var(--line-strong)}
.selected .course-meta{color:var(--text-2)}
.credit-tag{margin-left:auto;padding:1px 8px;border-radius:var(--pill);background:var(--text);color:var(--sheet);font-size:11px;font-weight:600}
.tag,.badge{display:inline-flex;align-items:center;gap:4px;padding:2px 10px;border-radius:var(--pill);background:var(--surface-3);color:var(--text-2);font-size:11.5px;font-weight:600;line-height:1.5}
.tag .icon{width:12px;height:12px;stroke-width:2.4}
.tag:has(use[href="#i-check"]){background:var(--success-soft);color:var(--success)}
.tag.warn{background:var(--warning-soft);color:var(--warning)}
.course-add{width:32px;min-height:32px;height:32px;padding:0;box-shadow:none;color:var(--text-2);background:var(--surface)}
.course-add .icon{width:15px;height:15px;stroke-width:2.2}
.course-add:hover{border-color:var(--accent);background:var(--accent);color:var(--on-accent)}
.course.selected .course-add{border-color:var(--text);background:var(--text);color:var(--sheet)}
.course.selected .course-add:hover{background:var(--danger);border-color:var(--danger);color:#fff}
.course.just .course-add{animation:pop .5s var(--ease)}
.course-pass{grid-column:2/-1;display:flex;align-items:center;gap:8px;margin-top:8px;color:var(--text-2);font-size:12px}
.rail-close{display:none;margin-left:auto}
.rail-fab{position:fixed;left:50%;bottom:max(18px,env(safe-area-inset-bottom));z-index:46;display:none;gap:10px;min-height:52px;padding:0 22px 0 18px;border:0;background:var(--text);color:var(--sheet);box-shadow:var(--shadow-lg);font-size:14px;font-weight:600;translate:-50% 0;transition:opacity .3s,visibility .3s,scale .3s var(--ease)}
.rail-fab:hover{background:color-mix(in srgb,var(--text) 80%,var(--accent))}
.rail-fab .icon{width:18px;height:18px;stroke-width:2.2}
.rail-fab-count{min-width:24px;padding:2px 8px;border-radius:var(--pill);background:var(--accent);color:var(--on-accent);font-size:12px;text-align:center}
.rail-open .rail-fab{opacity:0;visibility:hidden;scale:.9}
/* The floating button waits for the planner: it rises in as the sheet docks under the header. */
@supports (animation-timeline:scroll()){
 .rail-fab{animation:fab-in linear both;animation-timeline:scroll(root);animation-range:calc(var(--dock) - var(--bar) - 260px) calc(var(--dock) - var(--bar) - 120px)}
 @keyframes fab-in{from{opacity:0;visibility:hidden;translate:-50% 28px}}
}
.sidebar-foot{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);color:var(--text-3);font-size:11.5px;line-height:1.7}
.no-results{padding:36px 8px;text-align:center;color:var(--text-3);font-size:13px}

/* ---------- The week ---------- */
.schedule-panel{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;padding-left:28px;border-left:1px solid var(--line)}
.sheet-main{min-width:0}
.schedule-panel.notes-aside{grid-template-columns:minmax(0,1fr) 320px}
.schedule-top{display:grid;gap:18px;padding:22px 0 22px}
.schedule-heading{display:flex;align-items:center;gap:10px;min-width:0}
.schedule-top h2{font-size:20px;font-weight:600;letter-spacing:-.025em}
/* Stats read as display type: the numbers are the headline of the week. */
.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr))}
.stats>span{display:flex;flex-direction:column;gap:2px;min-width:0;padding:2px 22px 4px;border-left:1px solid var(--line);transition:color .3s}
.stats>span:first-child{padding-left:0;border-left:0}
.tb-label{color:var(--text-3);font-size:12.5px;font-weight:500}
.stats b{font-size:clamp(40px,4.4vw,66px);font-weight:560;line-height:1.2;letter-spacing:-.04em}
.stats b.has-conflict,.stats>span:has(.has-conflict) .tb-label{color:var(--danger)}
/* Odometer: each digit is a 0-9 column clipped to one line. */
.odo{display:inline-flex}
.digit{display:inline-block;height:1.2em;overflow:hidden}
.digit>span{display:block;transform:translateY(calc(var(--n,0) * -1.2em));transition:transform .9s var(--spring) calc(var(--i,0) * 40ms)}
.tb-note{display:block;margin-top:2px;color:var(--text-3);font-size:12px}
.inline-note{margin:0 0 12px;padding:11px 16px;border:1px solid color-mix(in srgb,var(--warning) 30%,transparent);border-radius:var(--r-md);background:var(--warning-soft);font-size:12.5px;line-height:1.55}
.scroll-hint{margin:0 0 8px;color:var(--text-3);font-size:12px;text-align:center;text-wrap:balance}
.calendar-scroll{overflow-x:auto}
/* The scroll morph hands the week over in two layers (week3d.js setTarget): grid chrome first, blocks last. */
#calendarWrap{--chrome:1;--blocks:1}
.calendar .day-head,.calendar .time-axis,.calendar .hour-rule{opacity:var(--chrome)}
.calendar .day-column{border-left-color:color-mix(in srgb,var(--line) calc(var(--chrome) * 100%),transparent)}
/* A block a 3D tile has just been dealt onto shows on its own, ahead of the rest (stamp() in week3d.js). */
.calendar .meeting{opacity:max(var(--blocks),var(--dealt,0))}
/* Keyboard focus always reveals the real week, even mid-morph. */
#calendarWrap:focus-within{--chrome:1!important;--blocks:1!important}
body:has(#calendarWrap:focus-within) .hero-stage{visibility:hidden}
/* The course drawer or phone sheet is open: the planner is in use, so the real week shows and the 3D layer steps aside. */
.rail-open .hero-stage{visibility:hidden}
.rail-open #calendarWrap{--chrome:1!important;--blocks:1!important}
.rail-open .empty-state{opacity:1!important}
/* Five equal day columns whatever they hold; parallel lessons share their day instead of widening it. */
.calendar{display:grid;grid-template-columns:54px repeat(5,minmax(0,1fr));padding:0 2px 18px 0}
.day-head{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:3px;height:66px;padding:0 12px;text-align:center;border-bottom:1px solid var(--line);background:var(--sheet)}
.day-head b{font-size:14.5px;font-weight:600;letter-spacing:-.01em}
.day-head small{max-width:100%;min-height:2.7em;max-height:2.7em;overflow:hidden;color:var(--text-3);font-size:11.5px;line-height:1.35}
.day-head .today-word{display:block;color:var(--text-2);font-weight:600}
.day-head.today b{padding:1px 10px;border-radius:var(--pill);background:var(--accent);color:var(--on-accent)}
.time-head{justify-content:flex-end;align-items:flex-end;padding:0 10px 10px 0;color:var(--text-3);font-size:11px;font-weight:600}
.time-axis,.time-head{position:sticky;left:0;z-index:4;background:var(--sheet)}
.time-axis{color:var(--text-3);font-size:11px;font-weight:500}
.time-axis,.day-column{height:calc(var(--hours)*var(--hour))}
.day-column{position:relative;border-left:1px solid var(--line)}
.time-tick{position:absolute;width:100%;padding-right:10px;text-align:right;transform:translateY(-50%)}
.time-tick:first-child{transform:translateY(3px)}
.hour-rule{position:absolute;left:0;right:0;border-top:1px solid color-mix(in srgb,var(--line) 75%,transparent);pointer-events:none}
.day-column.today{background:color-mix(in srgb,var(--accent) 5%,transparent)}
.hour-rule:first-child{border-color:transparent}

/* Event blocks: full course colour, ink or white text picked for contrast in app.js. */
.meeting{
 --block-fill:var(--event-color);
 position:absolute;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;min-width:0;min-height:0;padding:0;container-type:inline-size;
 border:0;border-radius:var(--r-block);background:linear-gradient(180deg,#ffffff40,transparent 46%) var(--block-fill);color:var(--event-on,var(--text));box-shadow:var(--shadow-sm),inset 0 0 0 1px #0e0e0e14;
 font-size:12px;font-weight:400;line-height:1.35;text-align:left;white-space:normal;transform-origin:center top;
 transition:transform .25s var(--ease),box-shadow .25s var(--ease),outline-offset .25s var(--ease)}
:root[data-theme=dark] .meeting{--block-fill:color-mix(in srgb,var(--event-color) 90%,var(--surface));background:linear-gradient(180deg,#ffffff26,transparent 46%) var(--block-fill);box-shadow:var(--shadow-sm),inset 0 0 0 1px #ffffff17}
@media (hover:hover) and (pointer:fine){.meeting:hover{background-color:var(--block-fill);z-index:3;transform:translateY(-2px);box-shadow:var(--shadow-md)}}
.meeting-content{display:flex;flex-direction:column;flex-grow:1;flex-shrink:0;width:100%;padding:9px 11px;overflow:hidden}
.meeting-content>*{flex-shrink:0;overflow-wrap:anywhere}
.calendar.measuring .meeting-content{flex-grow:0}
.meeting .event-time{display:flex;align-items:center;gap:5px;margin-bottom:3px;font-size:11px;font-weight:600;opacity:.84}
.meeting .event-time .icon{width:13px;height:13px;stroke-width:2.2}
.meeting strong{font-size:13px;font-weight:600;line-height:1.25;letter-spacing:-.01em}
.meeting .event-room{padding-top:4px;font-size:11px;line-height:1.4;opacity:.86}
.meeting .event-teacher{font-size:11px;line-height:1.4;opacity:.72}
.meeting:is([data-duration="30"],[data-duration="45"],[data-duration="60"]) .event-teacher{display:none}
/* Parallel lessons get narrow lanes: start time and the name, set compact (app.js puts soft hyphens at Greek syllables).
   Lanes too narrow for a name, and one-hour lessons, show the course code; full detail stays in the tooltip and dialog. */
.meeting .event-code{display:none}
@media screen{@container (max-width:112px){
 .meeting-content{padding:7px 6px}
 .meeting .event-end,.meeting .event-room,.meeting .event-teacher,.meeting .event-time .icon{display:none}
 .meeting .event-time,.meeting .event-code{overflow:hidden;white-space:nowrap;overflow-wrap:normal}
 .meeting .full-title{font-size:12px;line-height:1.2;letter-spacing:-.015em}
 .meeting .event-code{font-size:11.5px;letter-spacing:-.01em}
 .meeting:is([data-duration="30"],[data-duration="45"],[data-duration="60"]) .full-title{display:none}
 .meeting:is([data-duration="30"],[data-duration="45"],[data-duration="60"]) .event-code{display:block}}
@container (max-width:80px){
 .meeting .full-title{display:none}
 .meeting .event-code{display:block}}
@container (max-width:60px){
 .meeting-content{padding:6px 4px}
 .meeting .event-time{display:none}
 .meeting .event-code{font-size:11px;letter-spacing:-.02em}}
/* Four or more parallel lessons: the shared ECE prefix goes, the course number stays (full name in tooltip and dialog). */
@container (max-width:50px){
 .meeting-content{padding:6px 3px}
 .meeting .code-pre{display:none}}
}
/* Review: dashed amber outline and hatch. Conflict: a red outline and a warning icon. */
/* Rings sit inside the block (negative offset) so neighbouring lanes never collide; a light inner band separates red from the fill. */
.meeting.review{outline:2px dashed var(--warning);outline-offset:-2px;background-image:repeating-linear-gradient(135deg,transparent 0 7px,color-mix(in srgb,currentColor 16%,transparent) 7px 9px)}
.meeting.conflict{z-index:2;outline:2px solid var(--danger);outline-offset:-2px;box-shadow:var(--shadow-sm),inset 0 0 0 3.5px color-mix(in srgb,var(--sheet) 80%,transparent)}
.meeting.course-highlight{z-index:3;outline:3px solid var(--text);outline-offset:-3px}
.meeting.trace{z-index:3;outline:3px solid var(--danger);outline-offset:-3px;animation:trace .8s var(--ease) infinite alternate}
@keyframes trace{to{outline-offset:-6px}}

.empty-state{display:grid;justify-items:center;align-content:center;gap:8px;min-height:420px;margin:0 0 20px;padding:40px;border:1px dashed var(--line-strong);border-radius:var(--r-lg);text-align:center;background:radial-gradient(420px 220px at 50% 0%,var(--accent-soft),transparent 70%)}
.empty-icon{display:grid;grid-template-columns:repeat(5,14px);align-items:end;gap:6px;height:64px;margin-bottom:10px;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface);box-shadow:var(--shadow-md),var(--highlight)}
.empty-icon i{display:block;border-radius:var(--pill);background:var(--text);animation:bar-rise .8s var(--ease) both}
.empty-icon i:nth-child(1){height:40%;background:var(--surface-3)}
.empty-icon i:nth-child(2){height:75%;background:var(--accent);animation-delay:.06s}
.empty-icon i:nth-child(3){height:55%;background:var(--line-strong);animation-delay:.12s}
.empty-icon i:nth-child(4){height:90%;animation-delay:.18s}
.empty-icon i:nth-child(5){height:30%;background:var(--surface-3);animation-delay:.24s}
.empty-state h3{font-size:22px;font-weight:600;letter-spacing:-.025em}
.empty-state p{margin:0;color:var(--text-2);font-size:13.5px}

.legend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;padding:18px 0;border-top:1px solid var(--line);font-size:12.5px;color:var(--text-2)}
.legend-bar select{height:34px;padding:0 14px;font-size:12.5px}
.legend{display:flex;flex-wrap:wrap;gap:6px;width:100%;font-size:12px}
.legend-item{position:relative;display:flex;align-items:center;gap:7px;padding:4px 12px 4px 4px;border-radius:var(--pill);background:var(--surface-2)}
.legend-item .color-swatch{display:inline-block;width:18px;height:18px;border-radius:var(--pill);box-shadow:inset 0 0 0 1px #0e0e0e1f;flex-shrink:0}
.legend-item input{position:absolute;left:4px;top:4px;width:18px;height:18px;opacity:0}
.legend-item:focus-within{outline:2px solid var(--text);outline-offset:1px}
.legend-hatch,.legend-conflict{display:inline-block;width:18px;height:18px;border-radius:var(--pill)}
.legend-hatch{border:1.5px dashed var(--warning);background:var(--warning-soft)}
.legend-conflict{border:2px solid var(--danger);background:var(--danger-soft)}

/* Checks: semantic callouts. */
.checks{padding:0 0 8px;border-top:1px solid var(--line)}
.notes-aside .checks{align-self:stretch;margin-left:24px;padding:0 0 18px 24px;border-top:0;border-left:1px solid var(--line)}
.checks summary{display:flex;align-items:center;flex-wrap:wrap;gap:8px 10px;padding:18px 0;list-style:none;font-size:15px;font-weight:600}
.checks summary::-webkit-details-marker{display:none}
.checks summary::before{content:'';width:8px;height:8px;border-right:2px solid var(--text-3);border-bottom:2px solid var(--text-3);transform:rotate(-45deg);transition:transform .3s var(--ease);flex-shrink:0}
.checks[open] summary::before{transform:rotate(45deg)}
.checks summary>span:first-child{margin-right:auto}
.badge{white-space:nowrap}
.badge[data-tone=success]{background:var(--success-soft);color:var(--success)}
.badge[data-tone=warning]{background:var(--warning-soft);color:var(--warning)}
.badge[data-tone=danger]{background:var(--danger-soft);color:var(--danger)}
.checks p{margin:0 0 12px;font-size:13px}
.checks p.subtle{color:var(--text-3);font-size:12px}
.checks ul{display:grid;gap:8px;margin:0 0 14px;padding:0;list-style:none}
.checks li{position:relative;padding:11px 14px 11px 32px;border:1px solid color-mix(in srgb,var(--warning) 24%,transparent);border-radius:var(--r-md);background:var(--warning-soft);font-size:12.5px;line-height:1.5}
.checks li::before{content:'';position:absolute;left:14px;top:17px;width:8px;height:8px;border-radius:var(--pill);background:var(--warning)}
.checks li.danger{border-color:color-mix(in srgb,var(--danger) 28%,transparent);background:var(--danger-soft)}
.checks li.danger::before{background:var(--danger)}
.checks[open]>div li{animation:rise .5s var(--ease) both}
.checks[open]>div li:nth-child(2){animation-delay:.04s}.checks[open]>div li:nth-child(3){animation-delay:.08s}.checks[open]>div li:nth-child(4){animation-delay:.12s}.checks[open]>div li:nth-child(n+5){animation-delay:.16s}

footer{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 24px;margin-top:8px;padding:28px 0 8px;border-top:1px solid var(--line);color:var(--text-3);font-size:12px}
footer>span:first-child{font-weight:600;color:var(--text)}

/* ---------- Dialogs ---------- */
dialog{width:min(720px,calc(100% - 32px));max-height:86dvh;padding:28px;border:1px solid transparent;border-radius:var(--r-sheet);background:var(--surface);color:var(--text);box-shadow:var(--shadow-lg),var(--highlight)}
dialog::backdrop{background:#0e0e0e73;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
dialog[open]{animation:dialog-in .45s var(--ease)}
dialog[open]::backdrop{animation:fade .3s ease-out}
.dialog-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:18px}
.dialog-head h2{font-size:24px;font-weight:600;line-height:1.2;letter-spacing:-.03em}
.dialog-meta{margin:6px 0 0;color:var(--text-3);font-size:12.5px;font-weight:500}
/* Parts split by hairlines, not middle dots. Each part after the first carries its hairline in front, and the row is
   pulled left by one hairline's width with that strip clipped, so a part that wraps to a new line never leaves a hairline behind. */
.meta-parts{display:flex;flex-wrap:wrap;row-gap:2px;margin-left:-21px;clip-path:inset(0 0 0 21px)}
.meta-parts>span{display:inline-flex;align-items:center}
.meta-parts>span::before{content:'';width:1px;height:10px;margin:0 10px;background:var(--line-strong)}
.dialog-head>button{width:38px;min-height:38px;padding:0;flex-shrink:0}
dialog h3{margin:24px 0 8px;font-size:15px;font-weight:600}
dialog p,dialog li{font-size:13.5px;line-height:1.65}
dialog label{display:flex;flex-direction:column;gap:6px;font-size:12.5px;font-weight:550;color:var(--text-2)}
dialog .check-label{flex-direction:row;align-items:center;gap:10px;font-weight:500;color:var(--text);line-height:1.5}
dialog summary{cursor:pointer;font-weight:600}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:18px 0}
.source-links{display:grid;gap:8px;margin:18px 0;font-size:13.5px}
.source-links>div{padding:11px 16px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-2)}
.source-links a{display:inline-flex;align-items:center;gap:4px;font-weight:600}
.source-links small{display:block;margin-top:2px;color:var(--text-3);font-size:12px}
hr{margin:22px 0;border:0;border-top:1px solid var(--line)}
.detail-actions{display:flex;flex-wrap:wrap;align-items:center;gap:16px;margin:0 0 20px}
.meeting-option{display:flex!important;flex-direction:row!important;align-items:flex-start;gap:12px!important;margin:4px 0;padding:11px 14px;border:1px solid var(--line);border-radius:var(--r-md);font-weight:500!important;color:var(--text)!important;transition:background-color .2s}
.meeting-option:hover{background:var(--surface-2)}
.meeting-option span{display:block}
.meeting-option small{display:block;margin-top:2px;color:var(--text-3)}
.guide-rule{padding:11px 16px;border-radius:var(--r-md);background:var(--surface-2)}
#detailBody ul{padding-left:0;list-style:none}
#detailBody li{display:flex;align-items:baseline;gap:6px}
#detailBody li .icon{width:14px;height:14px;align-self:center;color:var(--success)}
#detailBody li .icon:has(use[href="#i-circle"]){color:var(--text-3)}
.transfer-code{width:100%;min-height:90px;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-2);color:var(--text);font:12px/1.6 Consolas,monospace;overflow-wrap:anywhere;resize:vertical}
.transfer-actions{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0}
.transfer-preview{margin:16px 0;padding:16px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-2)}
.transfer-preview p{margin:0 0 10px}.transfer-preview p:last-child{margin:0}

/* ---------- Skeletons shaped like the list and the week ---------- */
.skeleton-row,.sheet-skeleton{display:none}
.is-loading .skeleton-row{display:grid;grid-template-columns:12px 1fr;gap:6px 12px;padding:12px 10px}
.skeleton-row i,.skeleton-row b,.sk-days i,.sk-field b,.hs-field b{display:block;background:linear-gradient(90deg,var(--surface-2) 0%,var(--surface-3) 45%,var(--surface-2) 90%) 0 0/300% 100%;animation:shimmer 1.6s ease-in-out infinite}
.skeleton-row i{grid-row:span 2;width:12px;height:12px;margin-top:3px;border-radius:var(--pill)}
.skeleton-row b{height:12px;width:82%;border-radius:var(--pill)}
.skeleton-row b+b{width:46%;height:10px}
.skeleton-row:nth-child(3n) b:first-of-type{width:64%}
.skeleton-row:nth-child(4n) b:first-of-type{width:92%}
.skeleton-row:nth-child(2n) *{animation-delay:.12s}
.is-loading .sheet-skeleton{display:block;margin:0 0 16px}
.is-loading .empty-state,.is-loading .legend-bar,.is-loading .checks{display:none}
.is-loading .tb-note{visibility:hidden}
.is-loading .stats b{width:1.4em;height:1em;margin-top:.1em;border-radius:var(--r-sm);color:transparent;background:linear-gradient(90deg,var(--surface-2) 0%,var(--surface-3) 45%,var(--surface-2) 90%) 0 0/300% 100%;animation:shimmer 1.6s ease-in-out infinite}
.sk-days{display:grid;grid-template-columns:54px repeat(5,1fr);height:66px;border-bottom:1px solid var(--line)}
.sk-days i{height:12px;width:48%;margin:27px 12px;border-radius:var(--pill)}
.sk-days i:first-child{grid-column:2}
.sk-field{position:relative;height:460px;margin-left:54px;background:linear-gradient(var(--line) 1px,transparent 1px) 0 0/100% 56px}
.sk-field b{position:absolute;left:calc((var(--d) - 1) * 20% + 4px);top:var(--t);width:calc(20% - 8px);height:var(--h);border-radius:var(--r-block);animation-delay:calc(var(--d) * 90ms)}

/* ---------- Motion ---------- */
/* Hero: a skeleton of the 3D week in its hero pose until the board is live, then it fades as the board rises in.
   Only with html.has-3d (set where the 3D can load, cleared if it cannot), so it never waits forever. */
.hero-skeleton{display:none;position:absolute;inset:13% 1% 20% 44%;place-items:center;perspective:1800px;pointer-events:none;transition:opacity .9s var(--ease)}
.has-3d .hero-skeleton{display:grid}
.hs-board{width:min(88%,calc(62svh * 1.3));aspect-ratio:1.3;padding:8% 3% 4% 10%;border-radius:var(--r-sheet);background:color-mix(in srgb,var(--surface) 72%,transparent);transform:rotateX(55deg) rotateZ(-33deg)}
.hs-field{position:relative;height:100%;background:linear-gradient(color-mix(in srgb,var(--line-strong) 55%,transparent) 1px,transparent 1px) 0 0/100% 12.5%}
.hs-field b{position:absolute;left:calc((var(--d) - 1) * 20% + 3px);top:var(--t);width:calc(20% - 6px);height:var(--h);border-radius:10px;animation-delay:calc(var(--d) * 90ms)}
.hero:has(~.hero-stage.live) .hero-skeleton{opacity:0}
.hero:has(~.hero-stage.live) .hs-field b{animation:none}
@keyframes shimmer{from{background-position:100% 0}to{background-position:0 0}}
@keyframes pop-in{from{opacity:0;transform:scale(.96) translateY(-4px)}}
@keyframes dialog-in{from{opacity:0;transform:translateY(14px) scale(.97)}}
@keyframes fade{from{opacity:0}}
@keyframes fade-out{to{opacity:0}}
@keyframes rise{from{opacity:0;transform:translateY(10px)}}
@keyframes bar-rise{from{transform:scaleY(0);transform-origin:bottom}}
@keyframes pop{40%{transform:scale(1.3)}}
@keyframes row-flash{from{background:color-mix(in srgb,var(--accent) 14%,transparent)}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes line-in{from{transform:translateY(110%)}}
@keyframes fade-up{from{opacity:0;transform:translateY(16px)}}
@keyframes sheet-in{from{transform:translateY(9svh)}}
/* A course added from the list flies into its slot (fly() in app.js). */
.flight{position:fixed;z-index:80;pointer-events:none}
.flight i{display:block;width:100%;height:100%;border-radius:var(--r-block);background:var(--event-color);box-shadow:var(--shadow-md)}
/* A removed block fades out where it stood (leave() in app.js); below the header and the course drawer. */
.leaving{position:fixed;z-index:2;pointer-events:none}
/* Wakes the docked 3D week when the page scrolls back up (week3d.js). */
.dock-mark{position:absolute;left:0;width:1px;height:1px;pointer-events:none}
/* A dialog's exit (closeDialog() in app.js) takes its backdrop with it. */
dialog.closing::backdrop{opacity:0;transition:opacity .16s var(--ease)}
/* Entry once the catalog arrives: header, then the two panels settle in. */
.is-intro .intro,.is-intro .sidebar{animation:fade .6s .05s ease-out both}
.is-intro .schedule-panel{animation:rise .9s .12s var(--ease) both}
.is-intro .course{animation:rise .5s var(--ease) both}
.is-intro .course:nth-child(1){animation-delay:.22s}.is-intro .course:nth-child(2){animation-delay:.25s}.is-intro .course:nth-child(3){animation-delay:.28s}.is-intro .course:nth-child(4){animation-delay:.31s}.is-intro .course:nth-child(5){animation-delay:.34s}.is-intro .course:nth-child(6){animation-delay:.37s}.is-intro .course:nth-child(7){animation-delay:.4s}.is-intro .course:nth-child(n+8){animation-delay:.43s}
/* Course details grow out of the block or title that opened them; the rest of the page crossfades quickly around it. */
::view-transition-old(root),::view-transition-new(root){animation-duration:.25s}
:root[data-vt=detail] dialog[open]{animation:none}
::view-transition-group(course-detail){animation-duration:.4s;animation-timing-function:var(--ease)}
::view-transition-old(course-detail){height:100%;object-fit:cover;animation:fade-out .2s ease-in both}
::view-transition-new(course-detail){height:100%;object-fit:cover;object-position:top;animation:fade .32s .05s ease-out both}

/* ---------- Responsive ---------- */
@media screen and (min-width:1181px){
 .wide-layout .rail-fab{display:inline-flex}
 .wide-layout .workspace{grid-template-columns:minmax(0,1fr)}
 .wide-layout .sidebar{display:none}
 .wide-layout .schedule-panel{padding-left:0;border-left:0}
}
@media screen and (max-width:1180px){
 .brand-text small{display:none}
 .rail-fab{display:inline-flex}
 .rail-close{display:inline-flex}
}
/* Tablet: the week takes the full width and the course rail slides over it. */
@media screen and (min-width:861px) and (max-width:1180px){
 .workspace{grid-template-columns:minmax(0,1fr)}
 .schedule-panel{padding-left:0;border-left:0}
 .sidebar{position:fixed;top:calc(var(--bar) + 2px);left:12px;bottom:12px;z-index:45;width:min(340px,calc(100vw - 24px));max-height:none;padding:20px 18px 16px;border:1px solid transparent;border-radius:var(--r-lg);background:var(--surface);box-shadow:var(--shadow-lg),var(--highlight);transform:translateX(calc(-100% - 24px));visibility:hidden;transition:transform .5s var(--ease),visibility 0s .5s}
 .rail-open .sidebar{transform:none;visibility:visible;transition:transform .5s var(--ease)}
 :root[data-theme=dark] .sidebar{border-color:var(--line)}
}
@media screen and (max-width:860px){
 :root{--bar:68px;--dock:calc(max(92svh,540px) - 9svh)}
 .masthead{gap:8px;padding:8px 18px}
 .masthead::before{inset:8px}
 .brand-text,.btn-label,.btn-extra{display:none}
 .header-actions>button,.data-menu>summary{width:40px;min-height:40px;padding:0;justify-content:center}
 .theme-control select{width:auto}
 .hero{height:92svh;min-height:540px}
 /* Phones have no grid to land on: the stage stays under the sheet, which slides over it. */
 .hero-stage{z-index:0}
 .hero-stage.still{height:max(92svh,540px)}
 .hero-copy{left:16px;right:16px;bottom:calc(9svh + 30px);max-width:none}
 .hero-skeleton{inset:9% 0 auto;height:38%}
 .hs-board{width:min(86%,calc(34svh * 1.3))}
 .hero-title{font-size:clamp(44px,13vw,64px)}
 .hero-sub{margin-top:14px;font-size:15px}
 .hero-cta{min-height:46px;margin-top:20px}
 main{margin-top:-9svh;padding:0 16px 20px;border-radius:24px 24px 0 0}
 .intro{padding:18px 0 14px}
 .intro-actions{flex-wrap:wrap}
 .season-switch{flex:1}
 .season-switch button{min-width:0}
 .sync-box{display:contents}
 #syncMeta{order:3;width:100%}
 #focusSchedule{display:none}
 .workspace{display:block}
 .sidebar{position:fixed;inset:auto 0 0;z-index:47;height:min(84dvh,760px);max-height:none;padding:10px 16px 12px;border-radius:24px 24px 0 0;background:var(--surface);box-shadow:0 -24px 60px -24px #0e0e0e66;transform:translateY(105%);visibility:hidden;transition:transform .5s var(--ease),visibility 0s .5s}
 .sidebar::before{content:'';flex-shrink:0;width:40px;height:4px;margin:0 auto 12px;border-radius:var(--pill);background:var(--line-strong)}
 .rail-open .sidebar{transform:none;visibility:visible;transition:transform .5s var(--ease)}
 .rail-open .workspace::after{content:'';position:fixed;inset:0;z-index:44;background:#0e0e0e59;animation:fade .3s ease-out}
 :root[data-theme=dark] .sidebar{border-top:1px solid var(--line)}
 .sidebar-foot{display:none}
 .calendar-scroll{overflow:visible}
 .calendar{grid-template-columns:minmax(0,1fr)!important;padding:0 0 8px}
 .calendar>.time-head,.calendar>.time-axis,.hour-rule{display:none}
 .calendar>:nth-child(2){grid-row:1}.calendar>:nth-child(8){grid-row:2}
 .calendar>:nth-child(3){grid-row:3}.calendar>:nth-child(9){grid-row:4}
 .calendar>:nth-child(4){grid-row:5}.calendar>:nth-child(10){grid-row:6}
 .calendar>:nth-child(5){grid-row:7}.calendar>:nth-child(11){grid-row:8}
 .calendar>:nth-child(6){grid-row:9}.calendar>:nth-child(12){grid-row:10}
 .day-head small{min-height:0}
 .day-head .today-word{display:inline}
 .day-head .today-word::after{content:', '}
 .day-head{position:sticky;top:var(--bar);z-index:5;flex-direction:row;justify-content:flex-start;align-items:baseline;gap:10px;height:auto;padding:16px 0 10px 10px;border-bottom:0;box-shadow:-6px 0 0 var(--sheet),6px 0 0 var(--sheet)}
 .day-column{display:grid;gap:8px;height:auto;border-left:0}
 .day-column.today{background:none}
 .meeting{position:static;width:auto!important;height:auto!important;min-height:64px}
 .meeting-content{padding:12px 14px}
 .schedule-panel{width:100%;padding-left:0;border-left:0}
 .schedule-top{gap:14px;padding:18px 0 14px}
 .stats{grid-template-columns:1fr 1fr;row-gap:12px}
 .stats>span{padding:2px 14px}
 .stats>span:nth-child(3){padding-left:0;border-left:0}
 .stats b{font-size:38px}
 .empty-state{margin:0 0 14px;padding:28px 16px}
 .legend-bar{padding:14px 0}
 .legend-bar>.subtle{display:none}
 .checks{padding:0 0 6px}
 .form-grid{grid-template-columns:1fr}
 /* Dialogs become bottom sheets on phones. */
 dialog{width:100%;max-width:none;max-height:88dvh;margin:auto 0 0;border-radius:24px 24px 0 0;padding:22px 18px}
 dialog[open]{animation:sheet-up .5s var(--ease)}
 @keyframes sheet-up{from{transform:translateY(100%)}}
}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}}
@media(prefers-reduced-transparency:reduce){.masthead::before,.hero-tip{background:var(--surface);backdrop-filter:none;-webkit-backdrop-filter:none}}

/* ---------- Print: one A4 landscape sheet; fitCalendar() sets --print-width and --print-scale ---------- */
@page{size:A4 landscape;margin:0}
@media print{
 /* The sheet prints in the selected theme, exactly as on screen (data-theme is always light or dark). */
 :root,:root[data-theme=dark]{--shadow-sm:none;--highlight:none}
 /* No viewport units on the page: Chrome's print preview resolves them against the browser window, not the paper,
    so a 100dvh body spilled a blank second page. */
 html,body{min-height:0;height:auto;background:var(--sheet);color:var(--text);print-color-adjust:exact;-webkit-print-color-adjust:exact}
 .workspace::after{display:none!important}
 .sprite,.hero,.hero-stage,.flight,.leaving,.dock-mark,.rail-fab,#focusSchedule,.masthead,.intro,.sidebar,footer,.notice,.update-report,.update-toast,.checks,.legend-bar>label,.legend-bar>.subtle,.empty-state,.sheet-skeleton,.inline-note,.scroll-hint,dialog{display:none!important}
 main{width:297mm;max-width:none;margin:0;padding:9mm;border-radius:0;box-shadow:none;background:var(--sheet);animation:none}
 .workspace{display:block;width:100%;height:191mm;border:0}
 /* Printing mid-morph (or from the hero) still prints the whole week. */
 #calendarWrap,.empty-state{opacity:1!important}
 #calendarWrap{--chrome:1!important;--blocks:1!important}
 .schedule-panel{display:block;width:var(--print-width,279mm);zoom:var(--print-scale,1);margin-inline:auto;padding:2mm;border:0;border-radius:0;box-shadow:none;background:var(--sheet)}
 .schedule-top{display:flex;align-items:center;justify-content:space-between;flex-wrap:nowrap;padding:0 0 5mm}
 .schedule-top h2{font-size:22px;white-space:nowrap}
 .stats{display:flex;flex-wrap:nowrap;width:auto}
 /* A printed week is used on other days: no today marking. */
 .day-column.today{background:none}
 .day-head.today b{margin:0;padding:0;background:none;color:inherit}
 .day-head .today-word{display:none}
 .stats>span{min-width:70px;padding:0 10px}
 .stats b{font-size:20px;letter-spacing:-.02em}
 .calendar-scroll{margin:0;overflow:visible}
 .calendar{min-width:0;padding-bottom:2mm}
 .time-axis,.time-head{position:relative;left:auto}
 .day-head{height:auto;min-height:10mm;padding-block:1mm}
 .day-head b{font-size:11px}
 .day-head small{min-height:0;font-size:8px}
 .meeting{padding:0;overflow:hidden;transform:none!important;opacity:1!important;box-shadow:none!important;print-color-adjust:exact;-webkit-print-color-adjust:exact}
 .meeting.course-highlight,.meeting.trace{outline:none!important}
 .meeting-content{padding:1.2mm}
 .meeting strong{font-size:9px;line-height:1.25}
 .meeting .event-time,.meeting .event-room{font-size:8px;line-height:1.4}
 .meeting .event-teacher{display:none}
 .legend-bar{margin:0;padding:0;border:0}
 .legend{font-size:9px;gap:4px}
 .legend-item .color-swatch{width:12px;height:12px;print-color-adjust:exact;-webkit-print-color-adjust:exact}
 *{transition:none!important;animation:none!important}
}
