/* ==========================================================================
   Recall — beta styles. Mobile first: every rule is written for a phone,
   then widened at lg (≥992px) for laptops and desktops.

   Colour comes from §2.4.2: two fixed themes as CSS variables, mapped onto
   Bootstrap's own variables so buttons, links and cards follow the theme
   without per-component overrides.
   ========================================================================== */

/* Warning and danger have to be told apart without hue.

   They sit side by side as chips — "3 overdue" beside "1 failed" — and a dark
   amber next to a brick red is the pair that collapses hardest: six themes had
   them under 10 dE apart for a red-green deficiency, three of them at 6 or 7.
   Hue could not fix it, so danger is darker: lightness is the channel that
   survives.

   Only the text colour moved. --rc-danger-fill is a different job — a delete
   button, judged against the primary button beside it rather than against a
   warning — and stays in the house 5-7:1 range for its label. */
/* Every colour here is measured against WCAG 2.1 AA: 4.5:1 for text, 3:1 for
   an interface edge. Two tokens exist purely to keep that true —

   --rc-accent      the brand hue, for edges, washes and focus rings, where
                    3:1 is the bar and the colour can stay bright;
   --rc-accent-fill the same hue darkened (lightened, at night) until a label
                    on top of it clears 4.5:1, for anything filled.

   Using --rc-accent as a button fill is what put white labels at 3.39:1 on
   teal and 2.79:1 on lemon, so filled controls must take --rc-accent-fill and
   pair it with --rc-on-accent. */
:root,
[data-theme="teal"] {
    --rc-bg: #E8EEED;
    --rc-text: #1B3B35;
    --rc-muted: #52716C;
    --rc-accent: #1C9971;
    --rc-accent-rgb: 28, 153, 113;
    --rc-accent-pale: #D7F4EB;
    /* Darkened from #0F6E56, which sat 8.2 dE from --rc-muted under
       deuteranopia — under this file's floor for a pair that carries meaning,
       and this pair carries a lot of it: accent-dark is link text, a heading
       and a lit tab's label, and muted is the line beside all three. Moved in
       LIGHTNESS only, L* 41.1 to 35.7, with the chroma held at 31 so the
       theme keeps its colour. 12.3 dE now, and 6.9:1 on a card.
       --rc-accent-fill is a separate job — a button ground, judged against
       its own label — and stays where the house range wants it. */
    --rc-accent-dark: #006049;
    --rc-accent-dark-rgb: 0, 96, 73;
    --rc-accent-fill: #0F6E56;
    --rc-accent-fill-hover: #0A5343;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F0F5F5;
    --rc-border: #D3E3DE;
    --rc-warn: #8A5008;
    --rc-warn-pale: #F6E7D5;
    --rc-danger: #78252A;
    --rc-danger-rgb: 120, 37, 42;
    --rc-danger-pale: #F6E1DA;
    --rc-danger-fill: #9A3F22;
    --rc-spark: #A6790A;
    --rc-topbar: rgba(240, 245, 245, .85);
    --rc-hero-bg: #0F6E56;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #006049;
    --rc-hero-spark: #F4D35E;
    --rc-flame: #A84C15;
}


/* Night (§2.4.2). Students revise late, and a bright screen at midnight is
   the one colour effect with real evidence behind it. The accent inverts its
   role here: it is the light element, so labels on top of it are dark. */
[data-theme="night"] {
    --rc-bg: #0E1A17;
    --rc-text: #C2DCD3;
    --rc-muted: #A0BAB1;
    --rc-accent: #4FD1A0;
    --rc-accent-rgb: 79, 209, 160;
    --rc-accent-pale: #172B24;
    --rc-accent-dark: #7FE3BC;
    --rc-accent-dark-rgb: 127, 227, 188;
    --rc-accent-fill: #4FD1A0;
    --rc-accent-fill-hover: #6FDDB3;
    --rc-on-accent: #06110E;
    --rc-card: #152521;
    --rc-border: #2E423B;
    --rc-warn: #E0A758;
    --rc-warn-pale: #282015;
    --rc-danger: #F0A184;
    --rc-danger-rgb: 240, 161, 132;
    --rc-danger-pale: #281A15;
    --rc-danger-fill: #F0A184;
    --rc-spark: #F4D35E;
    --rc-topbar: rgba(21, 37, 33, .85);
    --rc-hero-bg: #16332B;
    --rc-hero-text: #E3EFEB;
    --rc-hero-btn-bg: #4FD1A0;
    --rc-hero-btn-text: #06110E;
    --rc-flame: #F0955B;
}

/* Focus — Cool indigo at low saturation. Arousal tracks saturation and brightness far more
   reliably than hue, so this ground is barely tinted and the indigo only lands where
   the eye needs to go. */
[data-theme="focus"] {
    --rc-bg: #ECECF1;
    --rc-text: #2C3453;
    --rc-muted: #5A617C;
    --rc-accent: #7681DD;
    --rc-accent-rgb: 118, 129, 221;
    --rc-accent-pale: #D7DAF4;
    --rc-accent-dark: #4251D2;
    --rc-accent-dark-rgb: 66, 81, 210;
    --rc-accent-fill: #4C5BD4;
    --rc-accent-fill-hover: #3344CE;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F3F3F8;
    --rc-border: #D3D5E3;
    --rc-warn: #995A0C;
    --rc-warn-pale: #F6E7D5;
    --rc-danger: #792234;
    --rc-danger-rgb: 121, 34, 52;
    --rc-danger-pale: #F6E1DA;
    --rc-danger-fill: #B04A29;
    --rc-spark: #B1861B;
    --rc-topbar: rgba(243, 243, 248, .85);
    --rc-hero-bg: #171D4F;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #4C5BD4;
    --rc-hero-spark: #D9A521;
    --rc-flame: #B04A29;
}

/* Paper — A paper-toned ground rather than pure white: dropping the ground a little lowers
   glare without costing contrast, which is why e-readers have long offered sepia. */
[data-theme="paper"] {
    --rc-bg: #EFEAE0;
    --rc-text: #403322;
    --rc-muted: #766856;
    --rc-accent: #47966B;
    --rc-accent-rgb: 71, 150, 107;
    --rc-accent-pale: #D7F4E4;
    /* Darkened from #35704F, which sat 3.9 dE from --rc-muted under
       deuteranopia — the worst of the three. Paper's muted is a warm brown
       and its accent a green, so the two collapse onto each other almost
       completely for a red-green deficiency, and hue could do nothing about
       it. Moved in LIGHTNESS only, L* 42.6 to 30.9, chroma held at 30.
       15.5 dE now, and 8.1:1 on a card. */
    --rc-accent-dark: #155334;
    --rc-accent-dark-rgb: 21, 83, 52;
    --rc-accent-fill: #35704F;
    --rc-accent-fill-hover: #2B5B40;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F4F3ED;
    --rc-border: #D3E3DB;
    --rc-warn: #96590C;
    --rc-warn-pale: #F6E7D5;
    --rc-danger: #752921;
    --rc-danger-rgb: 117, 41, 33;
    --rc-danger-pale: #F6E0DA;
    --rc-danger-fill: #A8452A;
    --rc-spark: #B7831D;
    --rc-topbar: rgba(244, 243, 237, .85);
    --rc-hero-bg: #174F30;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #155334;
    --rc-hero-spark: #C08A1E;
    --rc-flame: #A8452A;
}

/* Quiet — The lowest-stimulus option: almost no colour except the slate that marks what is
   interactive, for learners who find a busy interface distracting. */
[data-theme="quiet"] {
    --rc-bg: #EBEDEE;
    --rc-text: #2C383E;
    --rc-muted: #586F7A;
    --rc-accent: #6B8D9E;
    --rc-accent-rgb: 107, 141, 158;
    --rc-accent-pale: #D7EAF4;
    /* Darkened from #4E6A78, which sat 3.3 dE from --rc-muted below — the two
       were the same blue-grey at the same lightness, so an accent-coloured
       link and the muted line under it were one colour to everybody, not
       just to a red-green deficiency. Separated by LIGHTNESS rather than by
       hue, per the rule at the top of this file: L* 43.2 down to 33.5, with
       the chroma held at 13 so the theme keeps its restraint. 12.1 dE now,
       and 7.4:1 on a card. */
    --rc-accent-dark: #365360;
    --rc-accent-dark-rgb: 54, 83, 96;
    --rc-accent-fill: #4E6A78;
    --rc-accent-fill-hover: #425A65;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F3F4F5;
    --rc-border: #D3DEE3;
    --rc-warn: #8A5A12;
    --rc-warn-pale: #F6E9D5;
    --rc-danger: #792234;
    --rc-danger-rgb: 121, 34, 52;
    --rc-danger-pale: #F6E1DA;
    --rc-danger-fill: #A8492C;
    --rc-spark: #B08519;
    --rc-topbar: rgba(243, 244, 245, .85);
    --rc-hero-bg: #173C4F;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    /* Kept equal to --rc-accent-dark, which it has always been in this theme. */
    --rc-hero-btn-text: #365360;
    --rc-hero-spark: #B08519;
    --rc-flame: #A8492C;
}

/* Ember — The warm counterpart to Night. Same late-night purpose, pulled to the warm end and
   kept low in short-wavelength light.

   The warning colour is the pale gold rather than the amber it used to be:
   amber on amber put warn 8 dE from the accent for a deuteranope, so a
   "3 overdue" chip and a button were the same colour to them. Hue could not
   fix it — every warm hue collapses towards the same axis — so the separation
   is lightness, which everyone sees.

   Danger moved for the same reason, redder and a shade darker: amber and
   salmon at the same lightness left a delete button and a primary one 10 dE
   apart, which is not a distinction to hang a deletion on. */
[data-theme="ember"] {
    --rc-bg: #191412;
    --rc-text: #E4CEB8;
    --rc-muted: #BAADA0;
    --rc-accent: #E0A264;
    --rc-accent-rgb: 224, 162, 100;
    --rc-accent-pale: #2B2117;
    --rc-accent-dark: #E0A264;
    --rc-accent-dark-rgb: 224, 162, 100;
    --rc-accent-fill: #E0A264;
    --rc-accent-fill-hover: #E6B482;
    --rc-on-accent: #160F08;
    --rc-card: #241C18;
    --rc-border: #42382E;
    --rc-warn: #EEDCA0;
    --rc-warn-pale: #282115;
    --rc-danger: #E66A63;
    --rc-danger-rgb: 230, 106, 99;
    --rc-danger-pale: #281915;
    --rc-danger-fill: #E66A63;
    --rc-spark: #F0C46A;
    --rc-topbar: rgba(36, 28, 24, .85);
    --rc-hero-bg: #2E241A;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #E0A264;
    --rc-hero-btn-text: #160F08;
    --rc-hero-spark: #F0C46A;
    --rc-flame: #E8836A;
}

/* Spark — The one theme built for arousal rather than against it. Good for short bursts and
   the games; a poorer choice for a long revision session, which is the honest trade. */
[data-theme="spark"] {
    --rc-bg: #EEEBF3;
    --rc-text: #3F2D59;
    --rc-muted: #685A7C;
    --rc-accent: #9875D6;
    --rc-accent-rgb: 152, 117, 214;
    --rc-accent-pale: #E2D7F4;
    --rc-accent-dark: #7443C7;
    --rc-accent-dark-rgb: 116, 67, 199;
    --rc-accent-fill: #8459CE;
    --rc-accent-fill-hover: #7242C6;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F5F3FA;
    --rc-border: #D9D3E3;
    --rc-warn: #9B580B;
    --rc-warn-pale: #F6E7D5;
    --rc-danger: #792234;
    --rc-danger-rgb: 121, 34, 52;
    --rc-danger-pale: #F6E1DA;
    --rc-danger-fill: #B04A29;
    --rc-spark: #B98314;
    --rc-topbar: rgba(245, 243, 250, .85);
    --rc-hero-bg: #2E2145;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #8459CE;
    --rc-hero-spark: #C98F16;
    --rc-flame: #B04A29;
}

/* Knowledge Grove — Goldenrod on cream over a forest-green frame. The palette
   arrived with forest green as its primary, but that put it 4.2 dE from Paper —
   closer than any two themes here had ever been — so the gold and the green
   swap jobs. Gold is what the palette itself reserved for "due now", and it is
   the one hue nothing else in the app leads with; the forest green keeps the
   hero band. The ground is held at the lightness and chroma every other light
   theme uses, because a large surface is the one place chroma is felt for hours
   rather than glanced at. The warning colour is pushed to burnt orange: a
   warning that matched the primary would read as a button, and gold
   beside amber is a pair deuteranopes cannot separate. */
[data-theme="grove"] {
    --rc-bg: #EFECE1;
    --rc-text: #36454F;
    --rc-muted: #616A6D;
    --rc-accent: #A57D29;
    --rc-accent-rgb: 165, 125, 41;
    --rc-accent-pale: #F8E9D2;
    --rc-accent-dark: #7D5B18;
    --rc-accent-dark-rgb: 125, 91, 24;
    --rc-accent-fill: #7D5B18;
    --rc-accent-fill-hover: #694B0A;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F9F3EA;
    --rc-border: #E1DDD1;
    --rc-warn: #7C400E;
    --rc-warn-pale: #F6E4D2;
    --rc-danger: #95233E;
    --rc-danger-rgb: 149, 35, 62;
    --rc-danger-pale: #FBE9EC;
    --rc-danger-fill: #A32D42;
    --rc-spark: #8C6508;
    --rc-topbar: rgba(249, 243, 234, .85);
    --rc-hero-bg: #1E5235;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #7D5B18;
    --rc-hero-spark: #DAA520;
    --rc-flame: #A8452A;
}

/* Ink — Navy over an unsaturated ground and warm paper cards. Two temptations
   were declined: a near-white ground, which is the brightest thing a screen can
   do and the reason the other light themes stop short of it; and navy at full
   strength as a button, which put a 16:1 label on a near-black chip. The navy
   is lifted until its label sits where every other theme's does. */
[data-theme="ink"] {
    --rc-bg: #ECECEC;
    --rc-text: #333333;
    --rc-muted: #5E5E5E;
    --rc-accent: #4682B4;
    --rc-accent-rgb: 70, 130, 180;
    --rc-accent-pale: #DCE6F4;
    --rc-accent-dark: #22458F;
    --rc-accent-dark-rgb: 34, 69, 143;
    --rc-accent-fill: #22458F;
    --rc-accent-fill-hover: #12377F;
    --rc-on-accent: #FFFFFF;
    --rc-card: #F5F3EF;
    --rc-border: #E1DCD3;
    --rc-warn: #8A5A12;
    --rc-warn-pale: #F4E8D4;
    --rc-danger: #792234;
    --rc-danger-rgb: 121, 34, 52;
    --rc-danger-pale: #FAE9E4;
    --rc-danger-fill: #C13A1E;
    --rc-spark: #9A7412;
    --rc-topbar: rgba(245, 243, 239, .85);
    --rc-hero-bg: #012869;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #F6F6F6;
    --rc-hero-btn-text: #22458F;
    --rc-hero-spark: #E8B04B;
    --rc-flame: #C13A1E;
}

/* Cosmic Recall — The third dark theme. Indigo carries the ground and the
   cards, electric blue does the work the accent does at night, and amethyst
   takes the links. The saturation lives in the small bright things rather than
   in the surfaces: a card at the chroma this palette arrived with was six times
   anything else here, and a saturated field under text is what tires eyes.
   Deep indigo is too near the ground to read as a button, so it takes the hero
   band, where nothing has to be read against it for long. */
[data-theme="cosmic"] {
    --rc-bg: #191A24;
    --rc-text: #D7D6E0;
    --rc-muted: #B3B2BD;
    --rc-accent: #00BFFF;
    --rc-accent-rgb: 0, 191, 255;
    --rc-accent-pale: #262637;
    --rc-accent-dark: #BBA7E2;
    --rc-accent-dark-rgb: 187, 167, 226;
    --rc-accent-fill: #00BFFF;
    --rc-accent-fill-hover: #81D0FD;
    --rc-on-accent: #1A1B25;
    --rc-card: #22232F;
    --rc-border: #3A3A49;
    --rc-warn: #F0BE7F;
    --rc-warn-pale: #362A1B;
    --rc-danger: #F8AEA6;
    --rc-danger-rgb: 248, 174, 166;
    --rc-danger-pale: #3D2625;
    --rc-danger-fill: #F7A39A;
    --rc-spark: #F4D35E;
    --rc-topbar: rgba(34, 35, 47, .85);
    --rc-hero-bg: #3C235E;
    --rc-hero-text: #FFFFFF;
    --rc-hero-btn-bg: #00BFFF;
    --rc-hero-btn-text: #1A1B25;
    --rc-hero-spark: #F4D35E;
    --rc-flame: #F09373;
}

/* ---- Bootstrap bridge ------------------------------------------------- */
/* No pinch-zoom on a phone — the page lays itself out for the screen and a
   zoomed view left people stranded off to one side of it. The viewport tag
   in every layout says the same (maximum-scale=1, user-scalable=no), but iOS
   Safari ignores that on purpose, and this is the half it does honour:
   pans are allowed and the two-finger gesture is not. Gated on `pointer`
   rather than `any-pointer` because this TAKES a capability away — a touch
   laptop driven by a mouse keeps its zoom. Same reasoning as the study
   screen's user-select rule. */
@media (pointer: coarse) {
    html { touch-action: pan-x pan-y; }
}

/* No accidental text selection on a phone.
   
   A long press on anything that is not prose used to raise the platform's
   own selection toolbar — Copy, Select all, and on Android a Google search
   sheet over the top of the app. On a heading, a tile or a button that is
   never what was meant; the press was a press that missed, or a scroll that
   started too slowly.
   
   Off by default here and switched back ON below for the text people DO mean
   to select. The study screen has its own, narrower version of this rule
   further down; it predates this one and stays, because it also has to say
   which parts of a graded card go back to being selectable.
   
   `pointer: coarse` and not `any-pointer`, for the same reason the zoom rule
   above uses it: this TAKES a capability away, so a touch laptop driven by a
   mouse keeps it, and a desktop is untouched. */
@media (pointer: coarse) {
    body {
        -webkit-user-select: none; user-select: none;
        -webkit-touch-callout: none;
    }

    /* The exceptions, and each is somebody's own words or their material:
       anything being typed into, a note being read (selecting a line there
       is how a card is cut from it — see §5.1 Selection → Card), a rendered
       note or card body, a graded card's explanation, and code, which is
       copied far more often than it is read. */
    input, textarea, select, [contenteditable="true"],
    .rc-prose, .rc-read-body, .rc-md, .rc-explain, .rc-card-preview,
    code, pre, kbd, samp {
        -webkit-user-select: text; user-select: text;
        -webkit-touch-callout: default;
    }
}

.rc-body {
    --bs-body-bg: var(--rc-bg);
    --bs-body-color: var(--rc-text);
    --bs-secondary-color: var(--rc-muted);
    --bs-border-color: var(--rc-border);
    --bs-link-color: var(--rc-accent-dark);
    --bs-link-hover-color: var(--rc-accent-fill-hover);
    /* Bootstrap paints links from the -rgb variable, so it must carry the
       AA-safe colour too or --bs-link-color is silently ignored. */
    --bs-link-color-rgb: var(--rc-accent-dark-rgb);
    --bs-primary: var(--rc-accent);
    --bs-primary-rgb: var(--rc-accent-rgb);
    --bs-body-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --bs-body-font-size: 1rem;
    --bs-body-line-height: 1.5;
    background: var(--rc-bg);
    color: var(--rc-text);
    margin: 0;
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
}

.btn-primary {
    --bs-btn-bg: var(--rc-accent-fill);
    --bs-btn-border-color: var(--rc-accent-fill);
    --bs-btn-hover-bg: var(--rc-accent-fill-hover);
    --bs-btn-hover-border-color: var(--rc-accent-fill-hover);
    --bs-btn-hover-color: var(--rc-on-accent);
    --bs-btn-active-bg: var(--rc-accent-fill-hover);
    --bs-btn-active-border-color: var(--rc-accent-fill-hover);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-focus-shadow-rgb: var(--rc-accent-rgb);
    --bs-btn-color: var(--rc-on-accent);
    --bs-btn-disabled-bg: var(--rc-accent-fill);
    --bs-btn-disabled-border-color: var(--rc-accent-fill);
    --bs-btn-disabled-color: var(--rc-on-accent);
}
.btn-outline-primary {
    --bs-btn-color: var(--rc-accent-dark);
    --bs-btn-border-color: var(--rc-accent);
    --bs-btn-hover-bg: var(--rc-accent-fill);
    --bs-btn-hover-border-color: var(--rc-accent-fill);
    --bs-btn-hover-color: var(--rc-on-accent);
    --bs-btn-active-bg: var(--rc-accent-fill);
    --bs-btn-active-border-color: var(--rc-accent-fill);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-focus-shadow-rgb: var(--rc-accent-rgb);
    --bs-btn-disabled-color: var(--rc-accent-dark);
    --bs-btn-disabled-border-color: var(--rc-accent);
}
.btn-soft {
    --bs-btn-bg: var(--rc-accent-pale);
    --bs-btn-border-color: var(--rc-accent-pale);
    --bs-btn-color: var(--rc-accent-dark);
    --bs-btn-hover-bg: var(--rc-accent-fill);
    --bs-btn-hover-border-color: var(--rc-accent-fill);
    --bs-btn-hover-color: var(--rc-on-accent);
    --bs-btn-active-bg: var(--rc-accent-fill);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-disabled-bg: var(--rc-accent-pale);
    --bs-btn-disabled-border-color: var(--rc-accent-pale);
    --bs-btn-disabled-color: var(--rc-accent-dark);
}
/* Destructive actions: the app's clay, never Bootstrap's red, and shaped
   like .btn-soft so a delete sits beside a Save without shouting. */
.btn-soft-danger {
    --bs-btn-bg: var(--rc-danger-pale);
    --bs-btn-border-color: var(--rc-danger-pale);
    --bs-btn-color: var(--rc-danger);
    --bs-btn-hover-bg: var(--rc-danger-fill);
    --bs-btn-hover-border-color: var(--rc-danger-fill);
    --bs-btn-hover-color: var(--rc-on-accent);
    --bs-btn-active-bg: var(--rc-danger-fill);
    --bs-btn-active-border-color: var(--rc-danger-fill);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-focus-shadow-rgb: var(--rc-danger-rgb);
    --bs-btn-disabled-bg: var(--rc-danger-pale);
    --bs-btn-disabled-border-color: var(--rc-danger-pale);
    --bs-btn-disabled-color: var(--rc-danger);
}
/* A delete that repeats down a list — one per card, one per challenge.
   .btn-soft-danger is right for a single deliberate delete sitting among other
   buttons, but five of them stacked painted a pink stripe down the right of
   the list and gave a destructive action more weight than the cards it belongs
   to. Quiet at rest; the danger colour arrives on hover and press, which is
   when it means something.

   focus-visible is styled deliberately: a keyboard user never hovers, and this
   is the one control in the row that cannot be undone, so it must not be the
   one control with no visible state. */
.rc-row-delete {
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-color: var(--rc-muted);
    --bs-btn-hover-bg: var(--rc-danger-pale);
    --bs-btn-hover-border-color: var(--rc-danger-pale);
    --bs-btn-hover-color: var(--rc-danger);
    --bs-btn-active-bg: var(--rc-danger-fill);
    --bs-btn-active-border-color: var(--rc-danger-fill);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-focus-shadow-rgb: var(--rc-danger-rgb);
}
.rc-row-delete:focus-visible { background: var(--rc-danger-pale); color: var(--rc-danger); }

.btn-outline-danger {
    --bs-btn-color: var(--rc-danger);
    --bs-btn-border-color: var(--rc-danger);
    --bs-btn-hover-bg: var(--rc-danger-fill);
    --bs-btn-hover-border-color: var(--rc-danger-fill);
    --bs-btn-hover-color: var(--rc-on-accent);
    --bs-btn-active-bg: var(--rc-danger-fill);
    --bs-btn-active-border-color: var(--rc-danger-fill);
    --bs-btn-active-color: var(--rc-on-accent);
    --bs-btn-focus-shadow-rgb: var(--rc-danger-rgb);
    --bs-btn-disabled-color: var(--rc-danger);
    --bs-btn-disabled-border-color: var(--rc-danger);
}
.btn { --bs-btn-border-radius: 12px; --bs-btn-padding-y: .6rem; --bs-btn-padding-x: 1rem; --bs-btn-font-weight: 600; }
.btn-sm { --bs-btn-padding-y: .35rem; --bs-btn-padding-x: .7rem; --bs-btn-border-radius: 10px; }
.btn-lg { --bs-btn-padding-y: .85rem; --bs-btn-font-size: 1.05rem; }

/* Touch targets. A phone is the primary device here, and .btn-sm — which is
   most of the buttons in the app — came out 34px tall, well under the 44px a
   thumb needs. Grow the padding rather than set a min-height: .btn is
   inline-block, so a min-height taller than the line box would strand the
   label at the top of the control.

   `any-pointer: coarse` is there alongside `pointer: coarse` for the machines
   that report a mouse as their primary input but also have a touchscreen — a
   convertible laptop reports `pointer: fine`, so on that alone it would be
   handed 34px controls to tap at. A mouse user on such a machine gets slightly
   roomier controls, which is a far smaller cost than a finger getting mouse
   ones. A plain desktop matches neither and keeps its denser toolbars.

   Consequence worth knowing: resizing a desktop browser to phone width does
   NOT show this treatment, because the pointer is still a mouse. Use device
   emulation, or a real phone, when checking it. */
@media (pointer: coarse), (any-pointer: coarse) {
    .btn { --bs-btn-padding-y: .65rem; }
    .btn-sm { --bs-btn-padding-y: .6rem; --bs-btn-padding-x: .85rem; }
    /* Restated because .btn here sits after the .btn-lg above it and would
       otherwise win on source order, shrinking the largest button of the set. */
    .btn-lg { --bs-btn-padding-y: .85rem; }
    /* Bootstrap sizes this content-box, so width/height is the glyph and the
       padding is the target. The modal-header rule resets that padding and has
       to be named, or the close X on every dialog stays 24px. */
    .btn-close,
    .modal-header .btn-close { width: 20px; height: 20px; padding: 11px; background-size: 20px 20px; }
}

.form-control, .form-select {
    --bs-border-color: var(--rc-border);
    --bs-border-radius: 12px;
    font-size: 16px; /* under 16px iOS zooms the page on focus */
    padding: .65rem .85rem;
    background: var(--rc-card);
    color: var(--rc-text);
}
.form-control:focus, .form-select:focus {
    border-color: var(--rc-accent);
    box-shadow: 0 0 0 .2rem rgba(var(--rc-accent-rgb), .2);
}
.form-check-input:checked { background-color: var(--rc-accent); border-color: var(--rc-accent); }
.form-label { font-weight: 600; font-size: .9rem; color: var(--rc-text); margin-bottom: .3rem; }
.form-text { color: var(--rc-muted); }

/* Chrome paints its own pale background over an autofilled field, so the two
   most prominent boxes on the sign-in card came out a blue that belongs to no
   theme here. There is no property that turns it off — an inset shadow the
   depth of the field is the only way to cover it, and the text colour has to
   be set separately because the shadow sits behind the glyphs.

   The focus rules restate the ring from .form-control:focus: box-shadow is one
   property, so covering the fill would otherwise drop the ring with it. */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:hover {
    -webkit-box-shadow: inset 0 0 0 100px var(--rc-card);
    -webkit-text-fill-color: var(--rc-text);
    caret-color: var(--rc-text);
}
.form-control:-webkit-autofill:focus {
    -webkit-box-shadow: inset 0 0 0 100px var(--rc-card), 0 0 0 .2rem rgba(var(--rc-accent-rgb), .2);
}
.form-control.is-invalid:-webkit-autofill:focus {
    -webkit-box-shadow: inset 0 0 0 100px var(--rc-card), 0 0 0 .2rem rgba(var(--rc-danger-rgb), .2);
}

/* The show/hide button inside a password field's right edge, from the
   x-password-field component. The wrapper exists to give it something to be
   positioned against.

   Both the button and the room made for it are gated on .rc-js, which
   partials/password-reveal puts on <html>. The control is rendered rather
   than injected — a Livewire morph deletes anything the server did not send —
   so without that gate a page with JavaScript off would draw an eye that does
   nothing, over a gap held open for it. */
.rc-reveal { position: relative; }
/* Restated for .is-invalid, which sets its own padding-right further up to
   drop Bootstrap's warning icon — and would otherwise run the text under
   the eye exactly when the field is being corrected. */
.rc-js .rc-reveal .form-control,
.rc-js .rc-reveal .form-control.is-invalid { padding-right: 48px; }
.rc-reveal-btn { display: none; }
.rc-js .rc-reveal-btn {
    position: absolute; top: 0; right: 0; bottom: 0; width: 44px;
    display: flex; align-items: center; justify-content: center;
    padding: 0; border: 0; background: none; cursor: pointer;
    color: var(--rc-muted); font-size: 1.05rem; line-height: 1;
    border-radius: 0 12px 12px 0;
}
.rc-reveal-btn:hover { color: var(--rc-text); }
.rc-reveal-btn:focus-visible { color: var(--rc-text); outline: 2px solid var(--rc-accent); outline-offset: -3px; }
/* Bootstrap shows .invalid-feedback for a sibling .is-invalid, and the wrapper
   now stands where the input did. The script mirrors the class onto it. */
.rc-reveal.is-invalid ~ .invalid-feedback { display: block; }

.card {
    --bs-card-bg: var(--rc-card);
    --bs-card-border-color: var(--rc-border);
    --bs-card-border-radius: 16px;
    --bs-card-spacer-y: 1rem;
    --bs-card-spacer-x: 1rem;
    box-shadow: 0 1px 2px rgba(22, 48, 43, .04);
}
.list-group { --bs-list-group-bg: var(--rc-card); --bs-list-group-border-color: var(--rc-border); --bs-list-group-border-radius: 14px; --bs-list-group-active-bg: var(--rc-accent); --bs-list-group-active-border-color: var(--rc-accent); }
.list-group-item-action:hover { background: var(--rc-accent-pale); }
.alert { --bs-alert-border-radius: 12px; }
.alert-success { --bs-alert-bg: var(--rc-accent-pale); --bs-alert-color: var(--rc-accent-dark); --bs-alert-border-color: var(--rc-border); }
.badge.text-bg-primary { background-color: var(--rc-accent) !important; }
.progress { --bs-progress-bar-bg: var(--rc-accent); --bs-progress-bg: var(--rc-accent-pale); --bs-progress-border-radius: 99px; }
.pagination { --bs-pagination-active-bg: var(--rc-accent); --bs-pagination-active-border-color: var(--rc-accent); --bs-pagination-color: var(--rc-accent-dark); --bs-pagination-border-color: var(--rc-border); }
/* The wrapper Livewire's pagination view does not give itself.

   That view is a <nav> with no margin of its own, holding a "Showing 1 to
   24 of 110" paragraph and the page list in one align-items-center row.
   Dropped straight under a grid of cards it sits flush against them, and
   the list's opaque page buttons then cover the last card's shadow — which
   reads as the pager lying on top of the card. Both halves also carry
   Reboot's 1rem bottom margin, which is dead space under a row that is
   already the end of the list.

   18px above, which is the grid's own 12px gap and a little more, so the
   pager reads as following the cards rather than being one of them. */
.rc-pager { margin: 18px 0 4px; }
.rc-pager .pagination { margin-bottom: 0; }
.rc-pager p { margin-bottom: 0; }
.dropdown-menu { --bs-dropdown-border-color: var(--rc-border); --bs-dropdown-border-radius: 12px; --bs-dropdown-link-active-bg: var(--rc-accent); }
.modal-content { border-radius: 18px; border-color: var(--rc-border); }
/* A scrollable dialog pins its header and footer by making .modal-body a flex
   child of .modal-content, which is a flex column with a bounded height. A
   <form> wrapping the body and the footer — the natural way to write a dialog
   that submits — breaks that chain: the form is a plain block, so the body
   loses the height bound it scrolls against and simply grows, and the footer
   is pushed outside .modal-content, which is `overflow: hidden`. The submit
   button is then not merely off screen, it is unreachable.
   Give the form the column layout it displaced. */
.modal-dialog-scrollable .modal-content > form {
    display: flex; flex-direction: column;
    min-height: 0; overflow: hidden;
}
.text-muted { color: var(--rc-muted) !important; }
.text-danger { color: var(--rc-danger) !important; }
.invalid-feedback { color: var(--rc-danger); }
.form-control.is-invalid, .form-select.is-invalid, .was-validated .form-control:invalid {
    border-color: var(--rc-danger);
    /* Bootstrap's stock warning icon is its own red and crowds the text. */
    background-image: none;
    padding-right: .85rem;
}
.form-control.is-invalid:focus, .form-select.is-invalid:focus {
    border-color: var(--rc-danger);
    box-shadow: 0 0 0 .2rem rgba(var(--rc-danger-rgb), .2);
}
.dropdown-item.text-danger:hover, .dropdown-item.text-danger:focus {
    background: var(--rc-danger-pale);
    color: var(--rc-danger) !important;
}
.alert-danger { --bs-alert-bg: var(--rc-danger-pale); --bs-alert-color: var(--rc-danger); --bs-alert-border-color: var(--rc-danger-pale); }
.btn-close { --bs-btn-close-color: var(--rc-text); }
a { text-underline-offset: 2px; }
h1, h2, h3, h4 { letter-spacing: -.01em; }
h1 { font-size: 1.5rem; font-weight: 700; }
h2 { font-size: 1.2rem; font-weight: 700; }
h3 { font-size: 1.05rem; font-weight: 700; }
code, .font-monospace { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* ---- App chrome -------------------------------------------------------- */
:root { --rc-topbar-h: 56px; }
.rc-topbar {
    position: sticky; top: 0; z-index: 1020;
    background: var(--rc-topbar);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--rc-border);
}
.rc-topbar-inner {
    display: flex; align-items: center; justify-content: space-between;
    /* Named on :root above so anything sticking below the bar derives its
       offset from it, the way bottom-docked things derive theirs from
       --rc-tabbar-h. Growing the bar then moves them with it. */
    height: var(--rc-topbar-h); padding: 0 14px;
    max-width: 1200px; margin: 0 auto;
}
.rc-brand { display: inline-flex; align-items: center; gap: 8px; font-weight: 800; font-size: 1.1rem; color: var(--rc-text); text-decoration: none; letter-spacing: -.02em; }
.rc-logo { border-radius: 24%; flex: none; }
.rc-topbar-stats { display: flex; gap: 6px; }
.rc-chip {
    display: inline-flex; align-items: center; gap: 5px;
    min-height: 32px; padding: 0 10px; border-radius: 99px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    font-size: .85rem; font-weight: 700; text-decoration: none;
}
.rc-chip .bi-fire { color: var(--rc-flame); }
.rc-chip-level { gap: 8px; }
.rc-xpbar { display: inline-block; width: 44px; height: 6px; border-radius: 99px; background: rgba(var(--rc-accent-rgb), .25); overflow: hidden; }
.rc-xpbar span { display: block; height: 100%; background: var(--rc-accent); border-radius: 99px; }
@media (prefers-reduced-motion: no-preference) {
    /* Only ever a transition, never an entrance animation: the chip is in the
       top bar of every page, and a bar that refilled itself on each one would
       be pure decoration. This fires when the width actually changes across a
       wire:navigate — study something, move to another page, and the bar
       glides to its new value instead of snapping. */
    .rc-xpbar span { transition: width .45s ease-out; }

    /* The bar cannot show the new total until the next render, so the moment
       XP is earned is marked on the chip instead. Driven by the same
       xp-awarded event the toast listens for, so it fires exactly when
       something was actually awarded. */
    .rc-chip-level.is-bumped { animation: rc-xp-bump .55s cubic-bezier(.34, 1.56, .64, 1); }
    @keyframes rc-xp-bump {
        0%   { transform: none; }
        35%  { transform: scale(1.12); }
        100% { transform: none; }
    }
}

.rc-shell { display: flex; max-width: 1200px; margin: 0 auto; }
.rc-main {
    flex: 1; min-width: 0;
    padding: 16px 14px calc(var(--rc-tabbar-h) + 18px);
}
.rc-alert { margin-bottom: 14px; }

/* Everything that docks to the bottom of a phone screen — the feedback button,
   toasts, the bulk bar, the review screen's answer bar — has to stand clear of
   the tab bar. One variable carries the bar's real height so that growing it
   moves all of them together, instead of leaving a dozen hand-tuned offsets to
   be rediscovered one bug report at a time. It is zero wherever the bar is not
   drawn: on a focused screen, and from 992px up. */
:root { --rc-tabbar-h: calc(65px + env(safe-area-inset-bottom, 0px)); }
/* The saved-copy banner's height, declared once: it is fixed at the top, and
   both the page's padding and the app bar's sticky offset are derived from
   it. See .rc-stale further down. */
:root { --rc-stale-h: 36px; }
body.rc-focused { --rc-tabbar-h: env(safe-area-inset-bottom, 0px); }
@media (min-width: 992px) { :root, body.rc-focused { --rc-tabbar-h: 0px; } }

/* Bottom tab bar — phones */
.rc-tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1030;
    display: flex;
    background: var(--rc-card);
    border-top: 1px solid var(--rc-border);
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
.rc-tab {
    flex: 1 1 0; min-width: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    height: 64px; color: var(--rc-muted); text-decoration: none; font-size: .7rem; font-weight: 600;
    /* More is a button and the rest are links; they have to be indistinguishable. */
    background: none; border: 0; padding: 0; font-family: inherit;
}
.rc-tab .bi { font-size: 1.5rem; line-height: 1; }
/* A flex item will not shrink below its own text, so a long label would push a
   tab off a narrow phone. Let them shrink, and let the label lose its tail
   rather than the bar lose a tab. */
.rc-tab > span:not(.rc-tab-badge):not(.rc-tab-ico):not(.visually-hidden) {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Which page you are on, said in three ways that are not hue.
 *
 * It used to be said in one: --rc-muted became --rc-accent-dark and nothing
 * else changed. Measured against the 10 dE floor this file sets for meaning-
 * carrying colours, that pair FAILED in three themes under Viénot simulation
 * — teal 8.2, paper 3.9, and quiet 3.3 even with ordinary colour vision,
 * which is below the point at which two greys look different at all. On quiet
 * the bar had never shown the active tab to anybody. In greyscale — high
 * contrast mode, e-ink, a phone in the sun — every theme failed.
 *
 * So the lit tab gains a RAIL and a heavier label. The rail is the channel
 * that does the measurable work, and it does it by being a shape rather than
 * a colour: it is either there or it is not, which no simulation and no
 * greyscale can take away. 4.4:1 against the bar in the worst theme.
 *
 * Two discs came and went behind the icon on the way here. A FILLED one with
 * the icon reversed white measured well and looked wrong — the only solid
 * shape on a bar of thin line drawings, shouting about the one place you are
 * already standing. A pale one replaced it, quieter but the same idea, and it
 * went too: the rail is what carries the meaning and the disc was a second
 * mark saying the thing the first mark already said. On the themes where
 * accent-pale sits close to the card (night, ember, cosmic) it had been
 * invisible anyway, and that version of the bar was the one nobody complained
 * about — which is the evidence that the rail alone is enough.
 *
 * .rc-tab-ico stays. It is the 34px box that centres the glyph and gives the
 * rail something to sit above; only its background is gone.
 *
 * No transition: wire:navigate replaces the page, so this only ever appears
 * on a fresh one and an animation would never be seen. */
.rc-tab.active { color: var(--rc-accent-dark); font-weight: 800; }
/* The rail, at the bottom edge, under the label. It sat at the top edge —
   the inner edge, where most bottom bars put it — and was moved here on
   request. Narrower than the tab so it reads as a marker rather than a
   border, and its rounded corners follow it: the exposed side is now the
   top one.

   It rides the tab, not the bar, so it stays clear of the home indicator —
   .rc-tabbar carries env(safe-area-inset-bottom) as padding below the tabs,
   and this sits at the foot of the 64px tab above it. */
.rc-tab.active::before {
    content: ""; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
    width: 32px; height: 3px; border-radius: 3px 3px 0 0;
    background: var(--rc-accent-fill);
}
/* 34 square around a 24x25 glyph: about five pixels of room on every side.
   The first attempt was 52x28, which left two pixels above and below the
   icon and fourteen at each end — a flat oval squeezing the glyph rather
   than a shape holding it. Even padding is what makes it read as deliberate,
   and a square box does that for every icon on the bar regardless of how
   wide its own drawing is. */
.rc-tab-ico {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: 99px;
}
.rc-tab.active .bi { color: var(--rc-accent-dark); }
/* The due count over the Study icon. Absolute so it rides the icon instead
   of joining the column, which would push the label out of the 64px tab. */
.rc-tab { position: relative; }
.rc-tab-badge {
    position: absolute; top: 2px; left: 50%; margin-left: 7px;
    min-width: 17px; height: 17px; padding: 0 5px; border-radius: 99px;
    background: var(--rc-accent-fill); color: var(--rc-on-accent);
    font-size: .62rem; font-weight: 700; line-height: 17px;
    font-variant-numeric: tabular-nums; text-align: center;
}
/* The badge needs no special case on a lit tab, and that is a point in the
   pale disc's favour rather than an accident. Against accent-pale it still
   measures 3.5:1 in the worst theme, so it keeps one set of colours in both
   states — the filled disc had forced it to change appearance depending on
   which page you were on, and a notification count that recolours itself as
   you navigate is a thing to explain rather than a thing to read. */

/* The More sheet — the destinations the bar has no room to seat. It slides out
   from behind the bar, which stays lit and above it so the button that opened
   it is also the button that closes it. z-order is deliberate and tight:
   scrim (1028) < sheet (1029) < tab bar (1030), all of it below Bootstrap's
   modal backdrop at 1050 so a dialog still covers the lot. */
.rc-sheet-scrim { position: fixed; inset: 0; z-index: 1028; background: rgba(12, 26, 24, .45); }
.rc-sheet {
    position: fixed; left: 0; right: 0; z-index: 1029;
    bottom: var(--rc-tabbar-h);
    max-height: 68vh; overflow-y: auto; overscroll-behavior: contain;
    padding: 4px 12px 12px;
    background: var(--rc-card);
    border-top: 1px solid var(--rc-border);
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -10px 30px rgba(22, 48, 43, .18);
}
.rc-sheet-grip { width: 40px; height: 4px; border-radius: 99px; background: var(--rc-border); margin: 8px auto 10px; }
/* A row here is a primary way to move around, not a sidebar afterthought, so
   it gets the same 44px a button gets. */
.rc-sheet .rc-navlink { padding: 12px; }
.rc-sheet-move { transition: transform .22s ease, opacity .22s ease; }
.rc-sheet-away { transform: translateY(100%); opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    .rc-sheet-move { transition: opacity .12s ease; }
    .rc-sheet-away { transform: none; }
}

/* A focused screen — the reader, an editor, a review — hides the tab bar.
   Everything that was standing clear of it comes back down on its own, via
   --rc-tabbar-h; what is left here is the chrome an immersive round drops. */
@media (max-width: 991.98px) {

    /* A round or an editor on a phone gives up the brand — it is a way out
       of a page that already offers one — and keeps the streak and level,
       which are why somebody is here rather than chrome around it. The bar
       stays in the markup and keeps its background either way: the desktop
       layout above this breakpoint measures the sidebar against it, and
       without a ground behind them the chips would sit over scrolling text. */
    .rc-immersive .rc-brand { display: none; }
    .rc-immersive .rc-topbar-inner { height: 44px; justify-content: flex-end; }
}

/* Sidebar — laptops and up */
.rc-sidebar {
    flex-direction: column; width: 220px; flex: none;
    position: sticky; top: 64px; height: calc(100vh - 64px);
    padding: 16px 10px; border-right: 1px solid var(--rc-border);
    /* The nav is fixed to the viewport height, so anything past it is simply
       unreachable without this: an admin on a short screen loses Sign out
       entirely, and the sidebar reads as broken rather than as scrollable.
       overscroll-behavior stops the page behind it scrolling on once the
       list hits its end. */
    overflow-y: auto;
    overscroll-behavior: contain;
}
/* A full-width scrollbar beside a 220px nav is most of a nav link wide, so
   it is thinned to a hairline that only colours in on hover. */
.rc-sidebar { scrollbar-width: thin; scrollbar-color: transparent transparent; }
.rc-sidebar:hover { scrollbar-color: var(--rc-border) transparent; }
.rc-sidebar::-webkit-scrollbar { width: 6px; }
.rc-sidebar::-webkit-scrollbar-thumb { background: transparent; border-radius: 99px; }
.rc-sidebar:hover::-webkit-scrollbar-thumb { background: var(--rc-border); }
.rc-navlink {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 10px 12px; border: 0; border-radius: 12px; background: none;
    color: var(--rc-text); text-decoration: none; font-weight: 600; text-align: left;
}
.rc-navlink .bi { font-size: 1.15rem; color: var(--rc-muted); width: 22px; text-align: center; }
.rc-navlink:hover { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-navlink.active { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-navlink.active .bi { color: var(--rc-accent); }
/* Pushed to the end of the row so the count sits at the edge of the sidebar
   rather than trailing the word and moving as the number grows. */
.rc-nav-badge {
    margin-left: auto; flex: none;
    min-width: 22px; padding: 1px 7px; border-radius: 99px;
    background: var(--rc-accent-fill); color: var(--rc-on-accent);
    font-size: .72rem; font-weight: 700; font-variant-numeric: tabular-nums; text-align: center;
}
/* The entries that put the number on the badge, on the visit that clears it. */
/* Studying towards a date: a panel rather than a stat, because it answers
   what to do today rather than reporting a number. */
.rc-exam { background: var(--rc-accent-pale); border: 1px solid var(--rc-border); border-radius: 16px; padding: 14px; }
.rc-exam-bar { height: 8px; background: var(--rc-card); }
.mt--1 { margin-top: -6px; }

/* Release notes: one block per release, grouped inside it. Plain type on the
   page ground rather than cards — a list of sentences is what this is. */
.rc-whatsnew { padding-top: 20px; border-top: 1px solid var(--rc-border); margin-top: 24px; }
.rc-whatsnew:first-of-type { border-top: 0; padding-top: 8px; margin-top: 8px; }
/* Says a folder listing has reached below itself. Quiet, because it is a
   statement about the list rather than a warning about it. */
.rc-deep-note { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; font-size: .86rem; color: var(--rc-muted); }
.rc-deep-note .bi { color: var(--rc-accent); }
/* The way out of a page that only gets longer. Sticks under the top bar
   rather than over it, and carries the page's own background so the entries
   do not scroll through it. Sits below the bar's z-index for the same
   reason. */
.rc-whatsnew-bar {
    position: sticky; top: var(--rc-topbar-h); z-index: 1010;
    margin: -16px -14px 8px; padding: 10px 14px;
    background: var(--rc-bg); border-bottom: 1px solid var(--rc-border);
}
.rc-whatsnew-head h2 { font-size: 1.25rem; font-weight: 800; letter-spacing: -.01em; }
.rc-whatsnew-summary { color: var(--rc-muted); margin: 8px 0 0; }
/* Version then date. Six of these landed on one day, so the date alone left
   a reader nothing to tell one release from the next by, and nothing to name
   one with. Monospaced and tabular so a column of them lines up. */
.rc-whatsnew-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rc-version {
    font-family: ui-monospace, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums;
    font-size: .74rem; font-weight: 700; padding: 1px 8px; border-radius: 99px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
}
.rc-whatsnew-group {
    font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--rc-accent-dark); margin: 20px 0 6px;
}
.rc-whatsnew-list { padding-left: 1.15em; margin: 0; }
.rc-whatsnew-list li { margin-bottom: .45em; line-height: 1.6; }
/* The door to the rest of the history, in the same shape as a release
   divider so it reads as the end of the list rather than a stray button. */
.rc-whatsnew-more {
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    padding-top: 20px; border-top: 1px solid var(--rc-border); margin-top: 24px;
    text-align: center;
}
.rc-whatsnew-more p { margin: 0; color: var(--rc-muted); font-size: .9rem; }

/* A tag that reports rather than warns — for facts about a card that are
   worth knowing and are not problems with it. */
.rc-tag-quiet { background: transparent; border: 1px dashed var(--rc-border); color: var(--rc-muted); font-weight: 500; }
.rc-note-quiet { background: var(--rc-card); border: 1px dashed var(--rc-border); border-radius: 12px; padding: 10px 12px; color: var(--rc-muted); font-size: .88rem; }
.rc-note-quiet i { margin-right: 6px; }

.rc-fb-new { border-left: 3px solid var(--rc-accent); }
.rc-fb-solved { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
/* The tick needs the gap the markup's space does not survive inside a pill
   with its own letter spacing — it was reading as "✓Solved". */
.rc-fb-solved .bi { margin-right: 4px; }

/* One report. Stacked rather than two columns: the old shape had a badges
   column and a time/name/address/button column, neither of them given a
   width, so the longest address on the screen decided the layout for every
   row. Each part owns a row now and wraps inside it. */
.rc-fb-item { display: flex; flex-direction: column; gap: 6px; }
/* Badges and the age. Wrapping, because a Suggestion that is also Solved and
   also rated is four things and a phone is 360px wide. */
.rc-fb-top { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
/* Five stars are one thing to read. Broken after the first — which is what
   happened — it reads as one star beside a separate four. */
.rc-fb-stars { white-space: nowrap; flex: none; letter-spacing: 1px; }
.rc-fb-stars .bi-star-fill { color: var(--rc-spark); }
.rc-fb-stars .bi-star { color: var(--rc-muted); }
/* Pushed to the end of whatever line it lands on, and never broken in half:
   "10 hours ago" over two lines is not a timestamp. */
.rc-fb-when { margin-left: auto; flex: none; font-size: .8rem; color: var(--rc-muted); white-space: nowrap; }
/* anywhere, not break-word: an address has no space to break at, so a long
   one either sets the width of the card or runs off the edge of it. */
.rc-fb-who { font-size: .8rem; color: var(--rc-muted); overflow-wrap: anywhere; }
.rc-fb-msg { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.rc-fb-from { font-size: .8rem; color: var(--rc-muted); min-width: 0; overflow-wrap: anywhere; }
/* Source and action share a line while both fit; the button keeps the right
   whichever line it ends up on. */
.rc-fb-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.rc-fb-act { margin-left: auto; flex: none; }

/* The average, sitting beside the sort in the .rc-listbar-tools row. It
   carries its own size and colour rather than inheriting them from the row:
   that row is shared with the users screen, where the things in it are form
   controls and not a sentence. */
.rc-fb-avg { font-size: .85rem; color: var(--rc-muted); white-space: nowrap; }
.rc-fb-avg b { color: var(--rc-text); }
.rc-filter-sep { width: 1px; align-self: stretch; background: var(--rc-border); margin: 0 4px; }

/* Admin → Users. A shut account is marked down the edge rather than by
   colouring the whole row: the row still has to be read, and the two actions
   on it still have to be legible while it is shut. */
/* One row per account: who on the left, when on the right, and everything
   you can do to them behind the ⋯ at the end. It wraps rather than squeezes,
   so a long name takes a second line instead of crushing the dates. */
.rc-user-row { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.rc-user-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The name block takes the slack, so the dates land in the same place on
   every row instead of trailing off the end of whatever address precedes
   them — a ragged column is the thing an eye keeps having to re-find. */
.rc-user-who { flex: 1 1 200px; min-width: 0; }
.rc-user-when { flex: none; text-align: right; white-space: nowrap; }
.rc-user-more { flex: none; }

.rc-user-shut { border-left: 3px solid var(--rc-danger); }
.rc-user-note { color: var(--rc-danger); font-size: .86rem; }
.rc-user-note i { margin-right: 4px; }
.rc-user-ask { border-top: 1px dashed var(--rc-border); padding-top: 12px; }

/* On a phone the row stacks, and a right-aligned dates-and-buttons column
   under a left-aligned name reads as two unrelated things. */
/* On a phone the row stacks: who, then when, both left-aligned. The dates
   right-aligned under a left-aligned name read as two unrelated things. */
@media (max-width: 575.98px) {
    .rc-user-who { flex: 1 1 100%; min-width: 0; }
    .rc-user-when { text-align: left; white-space: normal; flex: 1 1 100%; }
    .rc-user-more { margin-left: 0; order: -1; position: absolute; right: 12px; }
    .rc-user-row { position: relative; padding-right: 44px; }
}

/* The one card on a screen that cannot be undone. Edged rather than filled:
   a red panel would shout over everything above it, and this belongs at the
   foot of a settings page, not at the front of it. */
.rc-danger-card { border-color: var(--rc-danger); }
.rc-danger-list { margin: 0 0 10px; padding-left: 20px; color: var(--rc-text); font-size: .92rem; }
.rc-danger-list li { margin-bottom: 3px; }

/* Not .btn-danger: suspending is reversible, and a red button says the thing
   next to it cannot be undone. The word carries the weight; the colour only
   keeps it from being pressed by accident. */
/* Marked in the menu, not filled. These sit under a divider among ordinary
   items; a red ground there would read as an alert about the account rather
   than as what the item does. */
.rc-user-danger { color: var(--rc-danger); }
.rc-user-danger:hover, .rc-user-danger:focus { background: var(--rc-danger-pale); color: var(--rc-danger); }
.rc-fb-dot { display: inline-block; width: 7px; height: 7px; border-radius: 99px; background: var(--rc-accent); margin-right: 7px; vertical-align: 2px; }

.rc-nav-section { font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--rc-muted); padding: 16px 12px 6px; }
.rc-sidebar-foot { margin-top: auto; }

@media (min-width: 992px) {
    .rc-main { padding: 28px 32px 48px; }
    h1 { font-size: 1.75rem; }

    /* The bar earns more height on a wide screen: 56px reads thin at 1900px. */
    .rc-topbar-inner { height: 64px; padding: 0 24px; }
    .rc-brand { font-size: 1.3rem; gap: 10px; }
    .rc-brand .rc-logo { width: 34px; height: 34px; }
    .rc-topbar-stats { gap: 8px; }
    .rc-chip { min-height: 38px; padding: 0 14px; font-size: .92rem; }
    .rc-xpbar { width: 64px; height: 7px; }
}

/* ---- Page pieces -------------------------------------------------------- */
/* The action cluster wraps. The notes head carries seven controls, and a
   nowrap row simply ran them off the right edge of a phone — New, the one
   thing somebody came to press, was the one that went. */
.rc-page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.rc-page-head h1 { margin: 0; }
.rc-page-head > div { flex-wrap: wrap; justify-content: flex-end; }

/* An editor's head stays put on a phone, because Preview lives in it.
   A note or a card is taller than the screen, so the head had scrolled away
   by the time you were typing — the same reach problem the Markdown toolbar
   had, and the reason moving Preview down into that toolbar keeps getting
   suggested. It cannot go there: the toolbar only exists while a field has
   focus, and preview's whole job is to dismiss the keyboard, which would
   take the button away with it. So the head follows you instead.

   Opt-in rather than every .rc-page-head: a list screen's head scrolling
   away is fine, and pinning all of them would cost 52px on every page to
   solve a problem two of them have.

   Bleeds sideways to the edges of .rc-main's 14px padding and carries an
   opaque page background, or the form scrolls through it. No negative top
   margin — an image error renders an alert above this, and pulling up over
   it would clip the alert. Below the top bar's z-index, and under it in the
   layout, for the reason .rc-whatsnew-bar gives. */
@media (max-width: 991.98px) {
    .rc-page-head.is-sticky {
        position: sticky; top: var(--rc-topbar-h); z-index: 1010;
        margin-left: -14px; margin-right: -14px;
        padding: 8px 14px;
        background: var(--rc-bg); border-bottom: 1px solid var(--rc-border);
    }
}
/* ---- The way back from a delete ---------------------------------------
   A bar on the page rather than a toast. Somebody who has just deleted the
   wrong note is looking at the list, and a toast is gone in three seconds —
   which is about how long it takes to realise. */
.rc-undo {
    /* Keyed on the trashed note in the view, so each delete mounts a fresh
       node and this runs again rather than only on the first one. */
    animation: rc-undo-in .2s ease-out;
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; margin-bottom: 12px;
    border-radius: 12px;
    background: var(--rc-warn-pale); color: var(--rc-warn);
    border: 1px solid var(--rc-warn);
}
.rc-undo .btn { flex: none; }

/* The way out of the guide, top and bottom. Quiet — it is an exit, not a
   call to action — but never smaller than a comfortable tap. */
.rc-guide-back { margin: 0 0 18px; }
.rc-guide-back a {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 2px; font-size: .9rem; font-weight: 600;
    color: var(--rc-muted); text-decoration: none;
}
.rc-guide-back a:hover, .rc-guide-back a:focus-visible { color: var(--rc-accent-dark); }

.rc-section-title { font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--rc-muted); margin: 22px 0 10px; }
/* Linked to by id (Settings → #reminders from the sign-in prompt): land
   clear of the sticky top bar, and flash the card so the eye finds it. */
.rc-section-title[id] { scroll-margin-top: calc(var(--rc-topbar-h) + 12px); }
.rc-section-title:target + .card { animation: rc-target-flash 1.8s ease-out; }
@keyframes rc-target-flash {
    0%, 35% { box-shadow: 0 0 0 3px var(--rc-accent); }
    100% { box-shadow: 0 0 0 3px transparent; }
}
@media (prefers-reduced-motion: reduce) { .rc-section-title:target + .card { animation: none; } }

/* The sign-in prompt to set up reminders. */
.rc-nudge .modal-body { padding: 28px 24px 22px; }
.rc-nudge-icon {
    width: 56px; height: 56px; margin: 0 auto 14px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-size: 1.6rem;
}
/* Balanced: at 375px "Your reminders have nowhere to go" left "go" alone on
   the second line of a centred title. */
.rc-nudge h2 { text-wrap: balance; }

/* The two halves of the note<->cards round trip: the "cards from this note"
   bar on a note editor, and the filter banner it lands on over a set. Both
   are a wrapping row so long deck or note names fall onto the next line
   rather than off the screen. Colours come from tokens, so both themes are
   covered without a dark override. */
.rc-note-cards { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rc-note-cards-label { font-size: .85rem; color: var(--rc-muted); }
.rc-note-cards-label .bi, .rc-note-filter .bi { margin-right: 4px; color: var(--rc-muted); }
.rc-note-filter {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
    padding: 8px 12px; margin-bottom: 16px;
    background: var(--rc-accent-pale); border-radius: 8px; font-size: .9rem;
}
.rc-note-filter-clear { margin-left: auto; }

/* ---- A ranked row on the learning screen ------------------------------
   Name, bar, figure, sample size. The sample size is part of the row rather
   than a footnote: 92% off eleven answers and 92% off four hundred look
   identical without it, and only one of them means anything. */
.rc-rankrow { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center; padding: 8px 0; }
.rc-rankrow + .rc-rankrow { border-top: 1px solid var(--rc-border); }
.rc-rankrow-head { grid-column: 1; display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.rc-rankrow-name { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-rankrow-flag { flex: none; font-size: .75rem; font-weight: 600; color: var(--rc-danger); }
.rc-rankrow-n { font-weight: 700; font-variant-numeric: tabular-nums; }
.rc-rankrow-bar { grid-column: 1; height: 6px; border-radius: 999px; background: var(--rc-border); overflow: hidden; }
.rc-rankrow-bar > span { display: block; height: 100%; background: var(--rc-accent-fill); }
.rc-rankrow-sub { grid-column: 2; font-size: .75rem; color: var(--rc-muted); text-align: right; }

/* Weak rows are marked by the words beside the name and by the figure, never
   by the bar. The words are real markup rather than generated content, so
   they survive truncation of a long name and are read aloud — which is why
   the colour was never the signal that mattered.
 *
 * The bar itself used to be filled with --rc-danger, and under a heading
 * reading "What to practice" that was saying the same thing twice in the
 * loudest available voice: a 64% deck drew as a long blood-red stripe across
 * the screen, which reads as a failure rather than as the next thing to work
 * on. The bar now carries the accent every other row does, so its LENGTH is
 * the comparison and the red is spent on the two small elements that name
 * the problem in words and in a number. Saturation belongs in small bright
 * things, per this file's own palette rules. */
.rc-rankrow.is-weak .rc-rankrow-n { color: var(--rc-danger); }

.rc-stat-unit { font-size: .9rem; font-weight: 600; margin-left: 2px; }

/* Centred: a stat tile is a number with its name under it, and left-aligning
   that in a box wider than the number leaves it adrift against one edge. */
/* Four tiles that must line up with each other, not merely sit beside each
   other. A Bootstrap `row` wrapping to two lines stretches each line on its
   own, so on a phone the pair holding the tallest tile came out taller than
   the pair above it — a staircase down the screen. `grid-auto-rows: 1fr`
   gives every row the same height whatever is in it.

   `minmax(0, 1fr)` rather than `1fr`: a grid column's default minimum is its
   content, so one long label ("184 XP · 16 to next") would push its column
   wider than its neighbour instead of wrapping inside it. */
.rc-statgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; grid-auto-rows: 1fr; }
@media (min-width: 992px) { .rc-statgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* height:100% is for the tiles still laid out in a Bootstrap column — the
   admin analytics rows — where the column stretches and the tile inside it
   did not. Centring is what makes the extra height read as deliberate: a
   short tile beside a tall one used to hang from the top with the gap all
   at the bottom. Do not add `d-block` to one of these: Bootstrap's display
   utilities carry !important and would undo the flex. */
.rc-stat { display: flex; flex-direction: column; justify-content: center; height: 100%; padding: 14px; border-radius: 16px; background: var(--rc-card); border: 1px solid var(--rc-border); text-align: center; }
.rc-stat-n { font-size: 1.6rem; font-weight: 800; line-height: 1.1; font-variant-numeric: tabular-nums; }
.rc-stat-l { font-size: .78rem; color: var(--rc-muted); font-weight: 600; }
.rc-stat.accent { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-stat.accent .rc-stat-l { color: var(--rc-on-accent); opacity: .85; }

.rc-hero {
    background: var(--rc-hero-bg); color: var(--rc-hero-text);
    border-radius: 18px; padding: 18px;
    background-image: radial-gradient(circle at 100% 0, rgba(255, 255, 255, .12), transparent 55%);
}
.rc-hero .btn-light { --bs-btn-bg: var(--rc-hero-btn-bg); --bs-btn-border-color: var(--rc-hero-btn-bg); --bs-btn-color: var(--rc-hero-btn-text); --bs-btn-hover-color: var(--rc-hero-btn-text); --bs-btn-font-weight: 700; }

/* The hero's action row. Not .rc-actions: every action here starts a session
   with a POST, so the flex items are the <form>s wrapping the buttons and a
   rule on .btn cannot reach them.

   Below 576px each action takes its own full-width line. Growing only the
   primary — which is what the markup's flex-grow-1 asks for — left the
   secondary at its content width once it wrapped, so "Surprise me" sat as a
   ragged half-button under a full-width "Study everything due". Wider than
   that they share one line and the primary still takes the room left over. */
.rc-hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
@media (max-width: 575.98px) {
    .rc-hero-actions > form { flex: 1 1 100%; }
    .rc-hero-actions > form > .btn,
    .rc-hero-actions > .btn { width: 100%; }
}

/* The alternative to the hero's primary, as a sentence rather than a second
   full-width button. It exists because the resume button took the band: the
   fresh start is still reachable, it just stops competing for the eye with
   the thing somebody is nine times out of ten coming back to finish.

   A button and not a link because starting a session is a POST. It has to be
   styled back down to text by hand — inherit, not a fixed colour, so it
   follows whatever --rc-hero-text the theme sets. */
.rc-hero-alt { margin-top: 10px; font-size: .82rem; opacity: .85; }
.rc-hero-alt button {
    padding: 0; border: 0; background: none; color: inherit;
    font: inherit; font-weight: 700; text-decoration: underline;
}
.rc-hero-alt button:hover { text-decoration: none; }

.rc-item { display: block; padding: 12px 14px; color: var(--rc-text); text-decoration: none; }
.rc-item:hover { background: var(--rc-accent-pale); }
.rc-item-title { font-weight: 700; margin-bottom: 2px; }
.rc-item-meta { font-size: .8rem; color: var(--rc-muted); }

/* The one Bootstrap utility this app kept reaching for and Bootstrap does
   not ship. Seven rows across the deck list, the notes list, the card list,
   the trash and the home screen were already written `flex-grow-1 min-w-0`,
   and every one of them was doing nothing.

   It matters because a flex item's automatic minimum size is its CONTENT,
   not zero: a row whose text is longer than the space refuses to shrink, and
   the buttons beside it are pushed off the right edge of the card instead.
   `text-truncate` on the title inside cannot save it — the truncation has
   nowhere to happen until the item itself is allowed to be narrower than
   what it holds. That is how a deck called "Philippine Natural Hazards" put
   its own Review button past the edge of the phone.

   Named after the Tailwind/Bootstrap 5.4 utility it stands in for, so the
   markup already written against it is correct rather than merely tolerated. */
.min-w-0 { min-width: 0; }

/* ---- How well known a card is ----------------------------------------
   The stage is read off the schedule (CardStage), so this is a window onto
   the interval rather than a second record of it.

   Three signals, because one is never enough: the number of lit pips, the
   word beside them, and a lightness ramp of the theme accent. Hue is the
   channel that fails first — the green/amber pairing an emoji ladder would
   use sits under 10 dE apart for deuteranopes — so nothing here depends on
   hue alone. Only Slipping steps outside the ramp, because it is the one
   state that means "do something". */
.rc-stage { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.rc-stage-pips { display: inline-flex; gap: 2px; }
.rc-stage-pips span {
    width: 6px; height: 6px; border-radius: 2px;
    background: var(--rc-border);
}
.rc-stage-pips span.is-on { background: var(--rc-accent); }
.rc-stage-slipping .rc-stage-pips span.is-on { background: var(--rc-danger); }
.rc-stage-label { font-size: .78rem; font-weight: 600; color: var(--rc-muted); }
.rc-stage-retained .rc-stage-label { color: var(--rc-accent-dark); }
.rc-stage-slipping .rc-stage-label { color: var(--rc-danger); }

/* ---- A deck's cards by stage -----------------------------------------
   The bar carries the shape of a deck at a glance; the key underneath
   carries the numbers, because a bar alone cannot be read off precisely and
   should not pretend otherwise. */
.rc-stages { display: flex; flex-direction: column; gap: 6px; }
.rc-stages-bar {
    display: flex; height: 8px; border-radius: 999px; overflow: hidden;
    background: var(--rc-border);
}
.rc-stages.is-compact .rc-stages-bar { height: 5px; }
.rc-stages-bar > span { display: block; min-width: 2px; }

/* The ladder as a lightness ramp of one hue. Ordered by weight, so the eye
   reads progress even where the colours themselves cannot be told apart. */
.rc-stages-bar .rc-stage-new { background: var(--rc-border); }
.rc-stages-bar .rc-stage-learning { background: rgba(var(--rc-accent-rgb), .35); }
.rc-stages-bar .rc-stage-familiar { background: rgba(var(--rc-accent-rgb), .65); }
.rc-stages-bar .rc-stage-retained { background: var(--rc-accent-fill); }
.rc-stages-bar .rc-stage-slipping { background: var(--rc-danger); }

.rc-stages-key { display: flex; flex-wrap: wrap; gap: 10px; font-size: .78rem; color: var(--rc-muted); }
.rc-stages-key > span { display: inline-flex; align-items: center; gap: 5px; }
.rc-stages-key i { width: 8px; height: 8px; border-radius: 3px; background: var(--rc-border); }
.rc-stages-key .rc-stage-learning i { background: rgba(var(--rc-accent-rgb), .35); }
.rc-stages-key .rc-stage-familiar i { background: rgba(var(--rc-accent-rgb), .65); }
.rc-stages-key .rc-stage-retained i { background: var(--rc-accent-fill); }
.rc-stages-key .rc-stage-slipping i { background: var(--rc-danger); }

/* The learning screen's variant: this bar is the headline there, not a note
   under one, so it is tall enough to read at a glance and its key is sized
   to be read rather than checked. */
.rc-stages.is-large .rc-stages-bar { height: 14px; border-radius: 999px; }
.rc-stages.is-large .rc-stages-key { font-size: .84rem; gap: 14px; row-gap: 6px; }
.rc-stages.is-large .rc-stages-key i { width: 10px; height: 10px; }

/* A stage holding nothing, named anyway. Hollow rather than a fainter fill:
   at these sizes a 55%-opacity dot and a full one are the same dot, and the
   ring is what says "none of these yet" without relying on the colour at
   all. Its own colour rule is overridden, hence the !important — the five
   .rc-stages-key .rc-stage-* rules above are more specific than this. */
.rc-stages-key > span.is-empty { opacity: .7; }
.rc-stages-key > span.is-empty i { background: transparent !important; box-shadow: inset 0 0 0 1.5px var(--rc-border); }

/* Every card on a long interval. Deliberately not styled as a trophy: the
   cards come back, and the line says so. */
.rc-stages-done { color: var(--rc-accent-dark); font-weight: 600; }

/* ---- the learning screen ------------------------------------------------
 *
 * The headline is a sentence, not a percentage. "Retained" is zero for a
 * learner's first month whatever they do, so a hero tile showing it greeted
 * somebody who had done nothing wrong with a failure — the biggest number on
 * the screen being a nought. Where the cards sit reads true on day one and
 * day four hundred.
 */
.rc-learn-lead { font-size: 1.05rem; font-weight: 600; margin: 0 0 12px; }
.rc-learn-lead b { color: var(--rc-accent-dark); }

/* Every caveat on this screen — what a figure counts, what it rests on —
   shares one class, so none of them can drift into a different size and
   start reading as more or less trustworthy than its neighbour. */
.rc-learn-note { font-size: .82rem; color: var(--rc-muted); margin: 10px 0 0; }

/* The lifetime figures that used to be four hero tiles. A 84px basis, not
   120: three have to sit on one phone row, and at 120 the third wrapped onto
   a line of its own where it read as a fourth figure somebody forgot. */
.rc-figstrip { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; }
.rc-figstrip > div { flex: 1 1 84px; min-width: 0; background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 12px; padding: 9px 10px; }
.rc-figstrip b { display: block; font-size: 1.1rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.15; }
.rc-figstrip span { font-size: .73rem; color: var(--rc-muted); font-weight: 600; }

.rc-learn-group + .rc-learn-group { margin-top: 14px; }
.rc-learn-subtitle { font-size: .82rem; font-weight: 700; color: var(--rc-muted); margin: 0 0 2px; }

/* Fourteen days of effort — the only figure here that is not a lifetime
   total, and the only one that answers "am I getting anywhere".
 *
 * Each day is a TRACK holding a fill, not a bare bar. Bare bars gave the
 * chart no structure: a quiet fortnight drew as a row of two-pixel specks on
 * an empty ground, which reads as something failing to render rather than as
 * a quiet fortnight. The tracks say "fourteen days" before any fill says how
 * busy one was.
 *
 * 64px, not 46: at 46 the difference between a four-review day and a
 * fourteen-review day was about six pixels.
 */
.rc-spark { display: flex; align-items: flex-end; gap: 3px; height: 64px; margin-top: 6px; }
.rc-spark-day { flex: 1; min-width: 0; height: 100%; display: flex; align-items: flex-end;
                background: rgba(var(--rc-accent-rgb), .10); border-radius: 4px; overflow: hidden; }
/* The fill carries the app's accent at full strength. At .30 on a ground
   this pale it read as grey — and a chart is exactly the "small bright
   thing" this file's palette rules reserve saturation for, unlike a surface
   text sits on for an hour. */
.rc-spark-day > i { display: block; width: 100%; background: var(--rc-accent); border-radius: 4px; }
/* Today is the one bar worth picking out, and it is picked out by weight as
   well as by hue — the axis underneath names it in words either way. */
.rc-spark-day.is-today > i { background: var(--rc-accent-fill); }
.rc-spark-day.is-today { box-shadow: inset 0 0 0 1.5px var(--rc-accent); }
.rc-spark-axis { display: flex; justify-content: space-between; gap: 8px; font-size: .72rem; color: var(--rc-muted); margin-top: 5px; }
/* The home screen's week, inside a .rc-stat tile beside three counts. Seven
   days rather than fourteen, so the bars stay wide enough to read at this
   height — the 64px reasoning above is about telling four reviews from
   fourteen, and a week's worth of wider bars does that in less room. */
.rc-spark.is-mini { height: 42px; margin-top: 0; margin-bottom: 6px; }

/* Whether a note has been read, as a shape rather than a hue: filled for
   never opened, a ring for edited since. Accent against warn is one of the
   three pairs this file watches, and two dots differing only in colour is
   exactly the case that collapses — so they differ in fill instead. The
   label beside them says it in words regardless, and a note that has been
   read keeps the empty slot so the titles stay in a line. */
.rc-readdot { flex: none; width: 8px; height: 8px; margin-top: 7px; border-radius: 50%; }
.rc-readdot.is-unread { background: var(--rc-accent); }
.rc-readdot.is-edited { box-shadow: inset 0 0 0 2px var(--rc-warn); }
.rc-excerpt { color: var(--rc-muted); font-size: .9rem; margin: 2px 0 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* ---- Notes as cards ---------------------------------------------------
   A note has a body worth previewing, which is what a card layout is for.
   One column on a phone — two side by side would leave no room for the
   excerpt that is the whole point — widening from there. */
.rc-brick-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
/* Tiles that can be expanded in place must not drag their row-mates taller
   with them: a stretched empty tile reads as one that opened by itself. */
.rc-brick-grid.rc-grid-top { align-items: start; }
/* A preview numbers up to 50 questions. The markers sit in the list's left
   padding, and this list scrolls — so anything wider than the padding is
   clipped, not overflowed: at ps-3's 1rem, 19 read as "9." and 20 as "0.".
   2.2em holds two digits and the point at any font size. Only on the list:
   the challenge panel borrows this class for its scrolling and has no
   markers to make room for. */
.rc-brick-peek { max-height: 40vh; overflow: auto; overscroll-behavior: contain; }
ol.rc-brick-peek { padding-left: 2.2em; }
@media (min-width: 576px) { .rc-brick-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); } }

/* The note's colour is the card's own top border, not a bar laid over it: a
   separate strip keeps its square ends past the card's rounded corners and
   reads as a line sitting above the card rather than part of it. As a border
   it follows the corner radius for free, and the card needs no overflow
   clipping — which would have cut off the ⋯ menu inside it.

   The three properties are written onto the element by NoteColor; a note with
   no colour of its own sets none of them and falls through to the theme
   accent here. */
.rc-brick {
    position: relative;
    display: flex; flex-direction: column; gap: 8px;
    background: var(--rc-card); border-radius: 16px;
    border: 1px solid var(--rc-nc-edge, var(--rc-border));
    border-top: 3px solid var(--rc-nc, var(--rc-accent));
    padding: 12px 14px 10px;
    box-shadow: 0 1px 2px rgba(22, 48, 43, .04);
    transition: border-color .15s, box-shadow .15s;
}
.rc-brick:hover { border-color: var(--rc-nc, var(--rc-accent)); box-shadow: 0 3px 10px rgba(22, 48, 43, .07); }
.rc-brick .rc-tag { background: var(--rc-nc-pale, var(--rc-accent-pale)); color: var(--rc-nc-ink, var(--rc-accent-dark)); }
.rc-brick .dropdown { position: absolute; top: 10px; right: 10px; z-index: 1; }
.rc-brick .dropdown .btn { --bs-btn-bg: transparent; --bs-btn-border-color: transparent; --bs-btn-color: var(--rc-muted); padding: 2px 6px; }

.rc-brick-body { display: block; color: var(--rc-text); text-decoration: none; }
.rc-brick-body:hover { color: var(--rc-text); }
.rc-brick-title { font-weight: 700; line-height: 1.3; padding-right: 28px; overflow-wrap: anywhere; }
.rc-brick-excerpt {
    color: var(--rc-muted); font-size: .875rem; line-height: 1.5; margin: 4px 0 0;
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.rc-brick-tags { display: flex; flex-wrap: wrap; gap: 4px; }

.rc-brick-foot {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin-top: auto; padding-top: 9px; border-top: 1px solid var(--rc-border);
    font-size: .75rem; color: var(--rc-muted);
}
.rc-brick-pill {
    display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 99px;
    background: var(--rc-nc-pale, var(--rc-accent-pale)); color: var(--rc-nc-ink, var(--rc-accent-dark)); font-weight: 700;
}
/* The exam pill on a card set's tile, and the same fact in the list layout.
   Warn rather than the accent the counts wear: the others say how much there
   is, this says how long there is.

   The border is not decoration. Warn and accent sit far apart in most themes,
   but Grove's accent is a warm gold and the two pills came out 6.4 dE under
   deuteranopia — under this file's own 10 dE floor, and exactly the trap the
   rule about warm accents describes. Rather than retune one theme, the pill is
   outlined and the counts beside it are not: a shape difference survives every
   theme and every kind of colour vision. Padding drops by the border's width
   so it still lines up with the solid pills next to it. */
.rc-brick-pill.is-exam {
    background: var(--rc-warn-pale); color: var(--rc-warn);
    border: 1px solid var(--rc-warn); padding: 1px 8px;
}
.rc-exam-when { font-weight: 600; opacity: .75; }
.rc-exam-inline { color: var(--rc-warn); font-weight: 600; }
/* The nudge is a link, not a label: on a note that has produced nothing yet,
   a prompt is worth more than a zero. */
.rc-brick-nudge {
    display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: 99px;
    border: 1px dashed var(--rc-accent); color: var(--rc-accent-dark); font-weight: 700; text-decoration: none;
}
.rc-brick-nudge:hover { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }

/* The Study screen's toolbar: search, order, filter, one row. The search
   gives up its full width — it is the least-used of the three on a screen
   whose usual question is "what has something waiting", not "where is X". */
.rc-deck-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rc-deck-search { position: relative; flex: 1 1 260px; max-width: 420px; }
.rc-deck-search > .bi { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--rc-muted); pointer-events: none; }
.rc-deck-search .form-control { padding-left: 34px; }
.rc-deck-search-busy { position: absolute; top: 50%; right: 12px; transform: translateY(-50%); }
.rc-deck-filter { margin-bottom: 0; }
/* The filter wraps onto its own line before the segmented control does: a
   switch alone on a line reads fine, half an order control does not. */
@media (max-width: 575.98px) {
    .rc-deck-filter { flex-basis: 100%; }

    /* Once the order control has a line to itself it takes the whole of it,
       and its three buttons share that width equally. `inline-flex` sized it
       to its own labels, so it stopped a third of the way across under a
       search box running the full width — two left edges lined up and two
       right edges that did not, which reads as something half-drawn.

       Only here. The layout switch in the page head is the same component
       and must stay at its own width: it sits beside a button, not under a
       full-width field. */
    .rc-deck-toolbar > .rc-seg { display: flex; flex-basis: 100%; }
    .rc-deck-toolbar > .rc-seg button { flex: 1; }
}

/* Everything due, across every set, with the button that starts it. */
.rc-duebar {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    background: var(--rc-accent-pale); border: 1px solid rgba(var(--rc-accent-rgb), .25);
    border-radius: 14px; padding: 10px 14px; margin-bottom: 14px;
}
.rc-duebar-n { font-size: 1.35rem; font-weight: 800; line-height: 1; color: var(--rc-accent-dark); }
.rc-duebar-t { color: var(--rc-accent-dark); font-weight: 600; }
@media (max-width: 575.98px) { .rc-duebar .btn { width: 100%; } }

/* The tile's one big number. Due is why the screen is open; the card total
   stays in the footer, where a figure nobody acts on belongs. */
.rc-brick-metric { display: flex; align-items: baseline; gap: 6px; }
.rc-brick-metric-n { font-size: 1.6rem; font-weight: 800; line-height: 1; color: var(--rc-nc-ink, var(--rc-accent-dark)); }
.rc-brick-metric-l { font-weight: 700; font-size: .9rem; color: var(--rc-nc-ink, var(--rc-accent-dark)); }
/* Nothing due, said out loud. Muted: it is the good outcome, not a task. */
.rc-brick-calm { display: flex; align-items: baseline; gap: 6px; font-size: .875rem; color: var(--rc-muted); }
.rc-brick-calm .bi { color: var(--rc-accent); }

/* A set with no cards is drawn as the outline of one: unfilled, dashed, and
   ending where its content ends rather than padding to its row-mates. The
   coloured top edge stays solid so the set is still identifiable. */
.rc-brick.is-empty { background: transparent; box-shadow: none; border-style: dashed; border-top-style: solid; }
.rc-brick-blank { margin-top: 0; }
.rc-brick-starters { display: flex; flex-wrap: wrap; gap: 6px; }

/* A link on the notes grid, a button on the Study grid — the one opens a
   page, the other reveals a form — so the button's own chrome is reset. */
.rc-brick-new {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
    width: 100%; min-height: 140px; padding: 0;
    background: transparent; border: 1px dashed var(--rc-accent); border-radius: 16px;
    color: var(--rc-accent-dark); font-weight: 600; font-size: 1rem; text-decoration: none;
}
.rc-brick-new:hover { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }

/* Sets that have been put away, at the foot of the Study screen. Quiet on
   purpose — this is a drawer, not a second list of things to do — but not so
   quiet that archiving reads as deleting. */
.rc-archived-group { margin-top: 26px; padding-top: 16px; border-top: 1px solid var(--rc-border); }
.rc-archived-toggle {
    display: inline-flex; align-items: center; gap: 8px;
    border: 0; background: transparent; padding: 8px 2px;
    font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--rc-muted);
}
.rc-archived-toggle:hover { color: var(--rc-accent-dark); }
.rc-archived-toggle:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); border-radius: 8px; }
.rc-archived-toggle .bi { font-size: .7rem; }
.rc-archived-note { font-size: .82rem; color: var(--rc-muted); margin: 2px 0 10px; max-width: 60ch; }
/* Why the shut drawer is worth opening when a search is running: it says the
   term reached in here too, so nobody concludes their note is gone. Lower
   case against the toggle's caps, because it is an aside rather than a label. */
.rc-archived-hit { text-transform: none; letter-spacing: 0; font-weight: 600; opacity: .8; }
/* On the set's own page. Not an alert colour: nothing has gone wrong and
   nothing was lost, so it reads as a note rather than a warning. */
.rc-archived-banner {
    display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-radius: 14px; padding: 12px 14px; margin-bottom: 16px;
    font-size: .88rem; line-height: 1.5;
}
.rc-archived-banner > .bi { font-size: 1.1rem; line-height: 1.4; }
.rc-archived-banner > div { min-width: 0; flex: 1 1 16rem; }
.rc-brick-new .bi { font-size: 1.4rem; }

/* The colour of a note or card set, in a layout that has no coloured edge. */
.rc-dot {
    display: inline-block; width: 8px; height: 8px; border-radius: 99px;
    background: var(--rc-nc, var(--rc-accent)); margin-right: 7px; vertical-align: 1px;
}

/* The colour swatches inside a note's ⋯ menu. Tap targets first: 28px is the
   smallest square that is still comfortable on a phone. */
.rc-swatches { display: flex; flex-wrap: wrap; gap: 6px; max-width: 210px; padding-bottom: 4px; }
.rc-swatch, .rc-swatch-custom {
    position: relative; width: 28px; height: 28px; flex: none;
    padding: 0; border: 2px solid transparent; border-radius: 9px; background: transparent; cursor: pointer;
}
.rc-swatch span { position: absolute; inset: 2px; border-radius: 6px; }
.rc-swatch.active { border-color: var(--rc-text); }
.rc-swatch:focus-visible, .rc-swatch-custom:focus-within { outline: none; border-color: var(--rc-text); }
/* "No colour of its own" — an empty square, not a white one, so it does not
   read as another colour on the row. */
.rc-swatch-none span { border: 1px dashed var(--rc-muted); background: transparent; }

/* ---- A note's own mark ------------------------------------------------- */
/* The picker, shaped after .rc-swatches above so the two sit together in the
   editor without either looking bolted on. Wider than the colour row because
   an emoji needs the room a 28px square does not have. */
.rc-icons { display: flex; flex-wrap: wrap; gap: 6px; max-width: 260px; padding-bottom: 4px; }
.rc-icon-swatch, .rc-icon-custom {
    width: 34px; height: 34px; flex: none;
    display: flex; align-items: center; justify-content: center;
    padding: 0; border: 2px solid transparent; border-radius: 9px;
    background: var(--rc-bg); cursor: pointer; font-size: 17px; line-height: 1;
}
.rc-icon-swatch.active { border-color: var(--rc-text); }
.rc-icon-swatch:focus-visible, .rc-icon-custom:focus-within { outline: none; border-color: var(--rc-text); }
.rc-icon-none { color: var(--rc-muted); font-size: 15px; }
/* One character wide on purpose: this is a way to reach the emoji keyboard,
   not a text field. NoteIcon refuses letters and digits, so nothing typed
   here can turn the mark into a caption. */
.rc-icon-custom input {
    width: 100%; height: 100%; border: 0; background: transparent; text-align: center;
    font-size: 16px; color: var(--rc-muted); padding: 0;
}
.rc-icon-custom input:focus { outline: none; }

/* The mark where notes are listed. Inline with the title rather than above it:
   the point is to be caught in peripheral vision while reading down a column
   of titles, which something on its own line is not. */
.rc-note-icon { font-size: 1em; margin-right: 6px; line-height: 1; }

/* And on the note itself, where there is room for it to be the first thing
   seen. Sits in the same rounded square the app uses for the brand mark, so
   an emoji with a transparent ground still reads as one object. */
.rc-note-icon-lg {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; margin: 0 0 10px;
    border-radius: 12px; font-size: 24px; line-height: 1;
    background: var(--rc-card); border: 1px solid var(--rc-nc-edge, var(--rc-border));
}

/* The band behind a coloured note's heading — the note's own colour finally
   doing more than a 7px dot.

   Only ever behind the TITLE block, never behind the body. The body is a
   surface somebody reads for an hour, and this file's own rule about ground
   chroma applies to it; a heading is glanced at. --rc-nc-pale is already
   mixed by NoteColor to keep its ink readable, so there is no colour a
   learner can pick that turns this unreadable. */
.rc-read-head.has-color {
    background: var(--rc-nc-pale, var(--rc-accent-pale));
    border-left: 3px solid var(--rc-nc, var(--rc-accent));
    border-radius: 0 14px 14px 0;
    padding: 16px 18px;
    margin: 0 0 18px;
}
.rc-read-head.has-color .rc-read-facts { margin-bottom: 0; }
.rc-read-head.has-color .rc-note-icon-lg { margin-top: 2px; }
.rc-swatch-custom { border: 1px dashed var(--rc-muted); display: inline-flex; align-items: center; justify-content: center; overflow: hidden; }
.rc-swatch-custom input { width: 34px; height: 34px; padding: 0; border: 0; background: none; cursor: pointer; }

/* A two-way switch between layouts, sized to sit beside the small buttons
   in a page head. */
/* The cards/list switch. Sized to the .btn-sm it sits beside in a page head:
   it used to be 26.7px against that button's 33.7px, which read as small
   twice over — small in itself, and visibly short of its neighbour. Its
   labels are visually-hidden, so the icon is the whole control and carries
   the size rather than any text. */
.rc-seg { display: inline-flex; gap: 2px; padding: 3px; border-radius: 11px; background: var(--rc-accent-pale); }
.rc-seg button {
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; background: transparent; color: var(--rc-accent-dark);
    border-radius: 8px; min-height: 28px; min-width: 36px; padding: 0 10px;
    line-height: 1; font-size: 1.05rem;
}
.rc-seg button.active { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-seg button:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); }

/* An inline label inside metadata — small on purpose. A <button> keeps the
   browser's default border unless it is told otherwise. */
.rc-tag {
    display: inline-flex; align-items: center; padding: 3px 9px; border: 0; border-radius: 99px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-size: .75rem; font-weight: 700; text-decoration: none;
}
a.rc-tag:hover { background: var(--rc-accent-fill); color: var(--rc-on-accent); }

/* A tag that reports something wrong. Must sit AFTER .rc-tag: same
   specificity, and the later rule is the one that paints. */
.rc-tag-shut { background: var(--rc-danger-pale); color: var(--rc-danger); }

/* Filter chips are controls, not labels: big enough to tap, obviously
   pressable, and with an explicit "everything" chip so clearing a filter
   never means guessing that you tap the active one again. */
.rc-filters {
    display: flex; align-items: center; gap: 8px;
    overflow-x: auto; padding-bottom: 4px; margin-bottom: 16px;
    -webkit-overflow-scrolling: touch;
}
.rc-filters::-webkit-scrollbar { display: none; }
.rc-filter {
    display: inline-flex; align-items: center; gap: 7px; flex: none;
    min-height: 38px; padding: 0 14px;
    border: 1px solid var(--rc-border); border-radius: 99px;
    background: var(--rc-card); color: var(--rc-text);
    font-size: .875rem; font-weight: 600; line-height: 1; white-space: nowrap; text-decoration: none;
}
.rc-filter:hover { border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-filter:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); }
.rc-filter.active { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-filter-count {
    font-size: .75rem; font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-radius: 99px; padding: 2px 7px;
}
.rc-filter.active .rc-filter-count { background: rgba(var(--rc-accent-rgb), .22); color: var(--rc-on-accent); }

/* An admin list screen has two bars: the chips, and the search and sort that
   narrow them. One line from 768px up, stacked below it.

   They are two containers rather than one wrapping run, which is what these
   screens used to do with .rc-filters.flex-wrap. .rc-filters is a sideways
   scroller, and overflow-x: auto computes the OTHER axis to auto as well —
   so a wrapped second line lands in a box that scrolls instead of growing.
   On a phone that clipped the second row of chips through the middle, and
   the sort box still ran off the right edge because a 14rem search and a
   "Last signed in" select do not fit 375px side by side. */
.rc-listbar { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.rc-listbar > .rc-filters { margin-bottom: 0; }
.rc-listbar-tools { display: flex; align-items: center; gap: 8px; }
/* The search absorbs the slack and the select keeps its natural width. A
   select's width is set by its longest option, and one that shrinks does
   not ellipsise — the text runs under the arrow and "Newest first" reads
   as "Newest fi". The search has a placeholder it can afford to lose the
   end of, so it is the half that gives. */
.rc-listbar-search { flex: 1 1 8rem; min-width: 0; }
/* margin-left: auto so the sort sits at the right edge on the feedback
   screen, where the thing beside it is a sentence rather than a search box
   that fills the row. On the users screen the search absorbs the free space
   first, so the margin has nothing left to push against and changes
   nothing. */
.rc-listbar-tools .form-select { flex: 0 0 auto; width: auto; margin-left: auto; }

@media (min-width: 768px) {
    .rc-listbar { flex-direction: row; align-items: center; gap: 12px; }
    /* The scroller takes the slack so the tools keep their measured width. */
    .rc-listbar > .rc-filters { flex: 1 1 auto; min-width: 0; }
    .rc-listbar-tools { flex: none; margin-left: auto; }
    .rc-listbar-search { flex: 0 0 14rem; }
}
.rc-tags { display: flex; flex-wrap: wrap; gap: 6px; }

.rc-match { color: var(--rc-accent-dark); font-weight: 600; }
.rc-row-selected { background: var(--rc-accent-pale); }
/* The label around a card row's checkbox. On a mouse it is the box itself;
   on touch the block at the end of this file grows it to a thumb. */
.rc-pick { display: flex; align-items: center; justify-content: center; margin: 4px 0 0; cursor: pointer; }
.rc-device-remove { padding: 0; }
@keyframes rc-undo-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* A row the learner has just confirmed the deletion of.
   
   Applied by recall.js the moment Confirm is pressed — not when the server
   answers — so the row starts leaving while the request is still in flight.
   That is most of the point: it says the press registered, and it covers the
   round trip that otherwise ends in a row blinking out of existence.
   
   Livewire's morph removes the element for real a moment later, so nothing
   here has to clean up after itself. grid-template-rows is not animated: the
   notes grid would reflow every frame, and opacity plus a small collapse
   reads the same for a fraction of the cost. */
@media (prefers-reduced-motion: no-preference) {
    /* Longer than the others and deliberately so: this one is running over a
       network round trip that happens whether it animates or not, so the time
       costs nothing that was not already being waited out. */
    .is-removing {
        animation: rc-row-out .28s ease-in forwards;
        pointer-events: none;
    }
    @keyframes rc-row-out {
        to { opacity: 0; transform: scale(.94); }
    }
}

/* Bulk actions dock above the phone tab bar and stay reachable while the
   list scrolls, so a selection made at the top is still actionable further down. */
/* Three deliberate rows on a phone rather than one wrapping run. Seven
   controls in a single wrap container, with a 64px gutter reserved on every
   row for the feedback button, came out as three ragged lines with the count
   stranded on one and Clear pushed to the far edge of another. Stacking them
   says what each row is for: how many, where to, what to do. */
.rc-bulkbar {
    position: sticky; bottom: calc(var(--rc-tabbar-h) + 14px); z-index: 1010;
    display: flex; flex-direction: column; gap: 8px;
    padding: 10px 12px; margin-top: 12px;
    background: var(--rc-card); border: 1px solid var(--rc-accent);
    border-radius: 16px; box-shadow: 0 8px 24px rgba(22, 48, 43, .14);
}
.rc-bulkbar-actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
    /* The feedback button sits in this corner at every width, and this is the
       row that reaches it — so the gutter is paid once rather than per row. */
    padding-right: 56px;
}
/* Away from the four that act on the selection, so leaving it is not one more
   button in the same run. */
.rc-bulkbar-clear { margin-left: auto; }
@media (min-width: 992px) {
    .rc-bulkbar { flex-direction: row; align-items: center; bottom: 16px; }
    .rc-bulkbar-actions { flex: 1 1 auto; }
}
.rc-bulkbar-count { font-size: .9rem; color: var(--rc-accent-dark); }
.rc-bulkpanel {
    position: sticky; bottom: calc(var(--rc-tabbar-h) + 74px); z-index: 1010;
    background: var(--rc-card); border: 1px solid var(--rc-border);
    border-radius: 16px; padding: 14px; margin-top: 12px;
    box-shadow: 0 8px 24px rgba(22, 48, 43, .12);
}
@media (min-width: 992px) { .rc-bulkpanel { bottom: 76px; } }
.rc-bulkpanel-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; max-height: 96px; overflow-y: auto; }
.rc-bulkpanel-tags .rc-tag { cursor: pointer; }
.rc-bulkpanel-tags .rc-tag:hover { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
/* Full width on a phone: it is the widest thing in the bar and capping it
   only left a stub with dead space beside it. */
.rc-bulkbar-deck { width: 100%; }
@media (min-width: 992px) { .rc-bulkbar-deck { width: auto; min-width: 150px; max-width: 220px; } }
.form-check-input:focus { border-color: var(--rc-accent); box-shadow: 0 0 0 .2rem rgba(var(--rc-accent-rgb), .2); }
.rc-empty { text-align: center; padding: 36px 16px; color: var(--rc-muted); }
.rc-empty .bi { font-size: 2rem; color: var(--rc-accent); display: block; margin-bottom: 8px; }

.rc-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.rc-actions .btn { flex: 1 1 auto; }
@media (min-width: 576px) { .rc-actions .btn { flex: 0 0 auto; } }

/* The save buttons at the foot of the note and card editors. Centred and left
   to wrap, they floated: on a phone a short button over a wider one, on a
   desktop one small button adrift under a 1200px editor, lined up with
   nothing. A rule above parts them from the last field. On a phone they stack
   at full width, the save you meant on top; from sm up they sit at the
   editor's right edge, where a form ends, with the main save rightmost —
   row-reverse, so the markup keeps it first for the phone's order. */
.rc-save-row {
    display: flex; flex-direction: column; gap: 8px;
    margin-top: 4px; padding-top: 16px; border-top: 1px solid var(--rc-border);
}
.rc-save-row .btn { width: 100%; }
@media (min-width: 576px) {
    .rc-save-row { flex-direction: row-reverse; justify-content: flex-start; align-items: center; }
    .rc-save-row .btn { width: auto; min-width: 140px; }
}

.rc-recovery-codes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 16px; }
.rc-recovery-codes code { font-size: .95rem; }
@media (max-width: 400px) { .rc-recovery-codes { grid-template-columns: 1fr; } }

/* Note body */
.rc-prose { font-size: 1.02rem; line-height: 1.65; word-wrap: break-word; }
.rc-prose h1 { font-size: 1.4rem; margin-top: 1.2em; }
.rc-prose h2 { font-size: 1.2rem; margin-top: 1.1em; }
.rc-prose h3 { font-size: 1.05rem; margin-top: 1em; }
.rc-prose pre { background: var(--rc-accent-pale); padding: 12px; border-radius: 12px; overflow-x: auto; font-size: .88rem; }
.rc-prose code { background: var(--rc-accent-pale); padding: 1px 5px; border-radius: 5px; font-size: .9em; }
.rc-prose pre code { background: none; padding: 0; }
.rc-prose blockquote { border-left: 3px solid var(--rc-accent); padding-left: 12px; color: var(--rc-muted); }
.rc-prose img { max-width: 100%; }
.rc-prose table { width: 100%; font-size: .92rem; }
.rc-prose td, .rc-prose th { border-bottom: 1px solid var(--rc-border); padding: 6px 4px; }
.rc-prose a { font-weight: 600; }
/* The formatting table is the only wide one in the guide — four columns, two
   of them holding Markdown that must not be broken across lines. The WRAPPER
   scrolls sideways on a phone rather than squeezing the prose column that
   carries the rest of the page; making the table itself the scroller lets its
   filled header band shrink-wrap and stop short of the right edge.

   The header takes --rc-accent-fill rather than --rc-accent, which is what
   guarantees its label 5-7:1 in every theme (see the palette rule at the top
   of this file). The striping is derived from the accent rather than a fixed
   grey, so it follows a theme instead of fighting it. */
.rc-guide-tablewrap { overflow-x: auto; border: 1px solid var(--rc-border); border-radius: 12px; margin-bottom: 12px; }
.rc-guide-table { width: 100%; margin: 0; border-collapse: collapse; }
.rc-guide-table thead th {
    background: var(--rc-accent-fill); color: var(--rc-on-accent);
    border-bottom: 0; padding: 10px 12px;
    font-size: .84rem; font-weight: 600; text-align: left; white-space: nowrap;
}
.rc-guide-table td { padding: 9px 12px; border-bottom: 1px solid var(--rc-border); vertical-align: top; }
.rc-guide-table tbody tr:nth-child(even) td { background: rgba(var(--rc-accent-rgb), .06); }
.rc-guide-table tbody tr:last-child td { border-bottom: 0; }
.rc-guide-table code { white-space: nowrap; }
.rc-guide-key { text-align: center; width: 2.6rem; color: var(--rc-accent-dark); font-weight: 800; }
.rc-guide-key .bi { font-size: 1.05rem; }
.rc-link-missing { color: var(--rc-muted); border-bottom: 1px dashed var(--rc-muted); text-decoration: none; }
/* ---- Reading a note ---------------------------------------------------
   The note is the page, not a widget inside a panel, so there is no card
   around it. Everything it links to sits in a rail beside it on a laptop and
   simply follows it on a phone. */
/* minmax(0, 1fr), not 1fr. A bare 1fr is minmax(auto, 1fr), and `auto` will
   not go below the column's min-content — which the previous/next stepper
   at the foot of the note sets to BOTH neighbouring titles laid side by side
   unwrapped (.rc-step-title is nowrap so it can truncate). On a phone that
   was 453px in a 375px viewport: the article grew to fit, the page scrolled
   sideways, and every line was cut off on the left. With the floor at 0 the
   column takes the viewport and the halves truncate the way they mean to. */
.rc-read { display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px; }
@media (min-width: 992px) { .rc-read { grid-template-columns: minmax(0, 1fr) 260px; gap: 36px; } }

.rc-read-eyebrow {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    font-size: .7rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
    color: var(--rc-muted); margin-bottom: 10px;
}
.rc-read-eyebrow .rc-tag { letter-spacing: 0; text-transform: none; }
/* The tags travel together. As loose siblings in a wrapping row they break
   mid-group on a phone — "read only #biology" on the first line and "#study"
   stranded on the second, which reads as two unrelated things. One flex item
   drops whole, and only wraps inside itself when it is too wide alone. */
.rc-eyebrow-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.rc-read-title { font-size: 1.9rem; line-height: 1.15; letter-spacing: -.015em; margin: 0 0 10px; overflow-wrap: anywhere; }
@media (min-width: 992px) { .rc-read-title { font-size: 2.25rem; } }

/* What a reader wants before starting: how long this will take, how fresh it
   is, and whether it has already earned its keep. */
.rc-read-facts {
    display: flex; flex-wrap: wrap; gap: 6px 14px;
    font-size: .82rem; color: var(--rc-muted);
    padding-bottom: 16px; border-bottom: 1px solid var(--rc-border); margin-bottom: 22px;
}
.rc-read-facts > span + span::before { content: "·"; margin-right: 14px; opacity: .6; }

/* A measure, not a container width. Long study notes were running to 110
   characters a line on a laptop, about double what stays readable. */
.rc-read-body { max-width: 62ch; font-size: 1.06rem; line-height: 1.75; }
.rc-read-body > :first-child { margin-top: 0; }
/* The opening paragraph carries the note the way a standfirst does. */
.rc-read-body > p:first-of-type { font-size: 1.16rem; line-height: 1.6; }
.rc-read-body h1, .rc-read-body h2, .rc-read-body h3 {
    letter-spacing: .04em; text-transform: uppercase; color: var(--rc-accent-dark);
    font-weight: 700; margin: 1.8em 0 .6em;
}
.rc-read-body h1 { font-size: .95rem; }
.rc-read-body h2 { font-size: .88rem; }
.rc-read-body h3 { font-size: .82rem; }
/* A term worth remembering, marked the way a reviewer would mark it. */
.rc-read-body strong { font-weight: 700; background: linear-gradient(transparent 62%, var(--rc-accent-pale) 62%); }
.rc-read-body blockquote {
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-radius: 0 12px 12px 0; padding: 12px 16px; margin: 0 0 1.05em;
}
.rc-read-body ul, .rc-read-body ol { padding-left: 1.3em; }
.rc-read-body li { margin-bottom: .35em; }

/* The shortcut states itself rather than hiding behind a highlight nobody
   knows to make, and turns into the selection's own button once there is one. */
.rc-read-bar {
    position: sticky; bottom: calc(var(--rc-tabbar-h) + 8px);
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 14px;
    padding: 10px 64px 10px 12px; margin-top: 28px;
    box-shadow: 0 2px 10px rgba(22, 48, 43, .06);
}
@media (min-width: 992px) { .rc-read-bar { bottom: 12px; } }
/* The selection's own button, pinned to the words rather than to the screen.
   The bottom strip is the one place it cannot live on Android: Chrome's Touch
   to Search sheet lands there and buries it, which is exactly what it used to
   do. Directly under the selection also clears the platform's own selection
   toolbar, which floats above the text. */
.rc-sel-cta {
    position: fixed; z-index: 1035;
    transform: translateX(-50%);
    box-shadow: 0 6px 20px rgba(22, 48, 43, .24);
    white-space: nowrap;
}
.rc-sel-cta.is-above { transform: translate(-50%, -100%); }

.rc-read-hint { font-size: .82rem; color: var(--rc-muted); min-width: 0; overflow-wrap: anywhere; }

/* The rail is a reference, not a second article: hairline rows, no boxes. */
@media (min-width: 992px) { .rc-read-rail { position: sticky; top: 80px; align-self: start; } }
.rc-rail-group { margin-bottom: 22px; }
.rc-rail-title {
    font-size: .68rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
    color: var(--rc-muted); margin: 0 0 6px;
}
.rc-rail-item {
    display: block; padding: 8px 0; border-bottom: 1px solid var(--rc-border);
    color: var(--rc-text); text-decoration: none;
}
.rc-rail-item:hover { color: var(--rc-accent-dark); }
.rc-rail-label { display: block; font-size: .88rem; line-height: 1.35; }
.rc-rail-sub { display: block; font-size: .72rem; color: var(--rc-muted); margin-top: 2px; }
.rc-rail-empty { font-size: .8rem; color: var(--rc-muted); margin: 0; }
.rc-rail-more {
    border: 0; background: transparent; color: var(--rc-accent-dark);
    padding: 8px 0 0; font-size: .78rem; font-weight: 700;
}
.rc-rail-more:hover { color: var(--rc-accent); }
/* Alpine hides these until it boots; without the rule the tail of a long
   rail flashes on every load. */
[x-cloak] { display: none !important; }
/* The share control brings its own card, which reads as a box dropped into a
   column of hairlines. In the rail it gives that up and stacks instead. */
.rc-read-rail .card { border: 0; box-shadow: none; background: transparent; }
.rc-read-rail .card-body { padding: 0; }
/* The link itself takes a line of its own; the buttons share the next one. */
.rc-read-rail .card-body .form-control { flex: 1 0 100%; min-width: 0 !important; }


/* A textarea that follows its content (see recall.js). The minimum is the
   empty state; JS takes it from there, up to 60vh. */
textarea.form-control[data-rc-grow] { min-height: 7.5rem; resize: vertical; }
textarea.form-control[data-rc-grow].rc-grow-sm { min-height: 3.25rem; }

/* Editor */
/* A .btn-sm that drops its words below sm and is left as a bare icon.
   
   At .btn-sm padding that is about a 28px target. The touch block at the
   foot of this file would have sized it, but that block is gated on a
   coarse pointer and a narrow DESKTOP window reports a fine one — which is
   where this was reported from. Width is the right question here anyway:
   what shrinks these buttons is the label going, not the kind of pointer.
   
   Put it on any button whose only visible content below sm is an <i>. */
@media (max-width: 575.98px) {
    .rc-icon-sm { min-width: 44px; min-height: 44px; }
}

.rc-editor-grid { display: grid; gap: 12px; align-items: stretch; }
.rc-editor-col { display: flex; flex-direction: column; }
@media (min-width: 992px) {
    .rc-editor-grid.with-preview { grid-template-columns: 1fr 1fr; }
}
.rc-preview {
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 12px;
    padding: 14px 16px; overflow-y: auto; max-height: 40vh;
}
@media (min-width: 992px) { .rc-preview { max-height: none; min-height: 46vh; } }
.rc-preview-empty { color: var(--rc-muted); font-style: italic; }
.rc-card-preview .rc-study-card { min-height: 0; padding: 20px 16px; }
.rc-card-preview .rc-study-q { font-size: 1.1rem; }
.rc-card-preview .rc-choice .btn { cursor: default; }
.rc-choice-correct { --bs-btn-bg: var(--rc-accent-fill); --bs-btn-border-color: var(--rc-accent-fill); --bs-btn-color: var(--rc-on-accent); }
@media (min-width: 992px) { .rc-card-preview { position: sticky; top: 80px; align-self: start; } }
.rc-preview > div > :first-child { margin-top: 0; }
.rc-wikilink { color: var(--rc-accent-dark); font-weight: 600; border-bottom: 1px solid rgba(var(--rc-accent-rgb), .4); }
.rc-wikilink.rc-link-missing { color: var(--rc-muted); border-bottom: 1px dashed var(--rc-muted); font-weight: 400; }
.rc-md-toolbar {
    display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
    padding: 4px 6px;
    background: var(--rc-accent-pale);
    border: 1px solid var(--rc-border); border-bottom: 0;
    border-radius: 12px 12px 0 0;
}
.rc-md-toolbar + textarea.form-control { border-top-left-radius: 0; border-top-right-radius: 0; }
.rc-md-btn {
    border: 0; background: transparent; color: var(--rc-accent-dark);
    border-radius: 8px; min-width: 40px; min-height: 40px; /* thumb-sized */
    font-size: .95rem; display: inline-flex; align-items: center; justify-content: center;
}
.rc-md-btn:hover, .rc-md-btn:focus-visible { background: var(--rc-card); outline: none; box-shadow: 0 0 0 2px rgba(var(--rc-accent-rgb), .25); }
.rc-md-btn .bi { font-size: 1.1rem; }
.rc-md-btn.rc-md-h { font-size: .82rem; font-weight: 800; letter-spacing: -.02em; }
/* The caret is inside what this button writes. It is the only formatting
   feedback a phone gets: below lg the preview REPLACES the editor rather than
   sitting beside it, so "did that bold take?" otherwise meant leaving the note
   and coming back. Filled rather than outlined, because an outline here reads
   as the focus ring two lines above.

   Declared after :hover and :focus-visible, at equal specificity, so an
   active button keeps its fill while hovered. It sets no box-shadow, which
   is deliberate: the focus ring is a box-shadow and therefore still lands on
   an active button rather than being overwritten by it. */
.rc-md-btn.is-on { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-md-btn.is-on:hover { background: var(--rc-accent-fill); }

/* The offer of a starting shape, above the toolbar and gone the moment a key
   is pressed. Square-bottomed on purpose: `.rc-md-toolbar + textarea` joins
   their corners, and this sits above the pair rather than between them. */
.rc-example {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 8px 12px; margin-bottom: 8px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-radius: 12px; font-size: .86rem;
}
.rc-example > .bi { font-size: 1rem; flex: none; }
.rc-example-text { flex: 1 1 200px; min-width: 0; }
/* Its quiet form, for writers past the panel: one line above the box. */
.rc-example-link { margin: -2px 0 8px; font-size: .86rem; }
.rc-example-link .btn { font-size: inherit; font-weight: 600; text-decoration: none; }
.rc-example-link .btn:hover { text-decoration: underline; }

/* Words this browser kept when the tab went away. Warm rather than green: it
   is a thing that happened TO you, not an offer, and it should not read as
   another suggestion beside the example above it. */
.rc-draft {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 8px 12px; margin-top: 8px;
    background: var(--rc-warn-pale); color: var(--rc-warn);
    border-radius: 12px; font-size: .86rem;
}
.rc-draft > .bi { font-size: 1rem; flex: none; }
.rc-draft-text { flex: 1 1 140px; min-width: 0; }
.rc-draft-acts { display: flex; gap: 8px; flex: none; }

/* Why the shelf is showing one set, and the way past it. Accent rather than
   warn: nothing has gone wrong, this is a step with an end. */
.rc-starter {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 10px 14px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-radius: 12px; font-size: .88rem;
}
.rc-starter-said { display: flex; align-items: center; gap: 10px; flex: 1 1 260px; min-width: 0; }
.rc-starter-said > .bi { font-size: 1.15rem; flex: none; }
/* Quiet, and deliberately not a button: taking the set is the thing being
   asked for, and this is the door beside it rather than a rival to it. */
.rc-starter-skip { color: var(--rc-accent-dark); flex: none; padding: 0; }

/* Silent until the body is close to the column's limit, then louder once it
   is past it. */
.rc-length {
    display: flex; align-items: center; gap: 7px; margin-top: 6px;
    font-size: .82rem; color: var(--rc-warn);
}
.rc-length.is-over { color: var(--rc-danger); font-weight: 600; }

/* Folder, tags, colour and icon, folded away so the last word typed and the
   Save button are not four controls apart.

   It is a control, so it has to look like one. `display: flex` costs the
   summary its disclosure triangle (the marker belongs to the list-item
   display it no longer has), and what was left — a bold line of text on a
   card ground — reads as a heading nobody thinks to tap. The affordance is
   restated by hand: a leading icon, a pale accent ground, and a chevron
   that turns when it opens. */
.rc-note-details > summary {
    cursor: pointer; padding: 10px 14px; border-radius: 12px;
    background: var(--rc-accent-pale);
    /* The label colour, not --rc-border: the pale ground is only ~1.06 off
       the card in EVERY theme, so the edge is the whole of the shape, and a
       neutral border measured 1.14-1.39 against that ground — it drew
       nothing. The light themes got away with it because the pale ground
       reads by hue; the dark ones have no hue to spare and the row went
       flat. Solid --rc-accent-dark lands at 4.53-9.67 across all ten themes
       (an accent tint at 55% managed only 1.63-1.83 in the light ones), and
       matching the words inside it is a rule that needs no per-theme
       tuning. */
    border: 1px solid var(--rc-accent-dark);
    font-size: .88rem; font-weight: 600; color: var(--rc-accent-dark);
    /* A thumb's worth, since this is the control standing between a phone
       and every optional field on the screen. */
    min-height: 44px; display: flex; align-items: center; gap: 10px;
    list-style: none;
}
.rc-note-details > summary::-webkit-details-marker { display: none; }
.rc-note-details > summary > .bi { flex: none; opacity: .85; }
.rc-note-details-label { flex: 1 1 auto; min-width: 0; }
/* What is behind the fold, in the words of the form: none of it is
   required, so nobody has to open this to save a note. */
.rc-note-details-hint {
    flex: none; font-size: .72rem; font-weight: 600; letter-spacing: .04em;
    text-transform: uppercase; color: var(--rc-muted);
}
.rc-note-details-caret { font-size: .75rem; transition: transform .15s ease; }
.rc-note-details[open] > summary .rc-note-details-caret { transform: rotate(180deg); }
/* Doubled rather than recoloured: the resting border is already the
   strongest colour the row has, so hover thickens it. A shadow, not a
   wider border — 2px would move every field below it by a pixel. */
.rc-note-details > summary:hover { box-shadow: 0 0 0 1px var(--rc-accent-dark); }
.rc-note-details > summary:focus-visible {
    outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25);
}
.rc-note-details[open] > summary { margin-bottom: 12px; }
@media (prefers-reduced-motion: reduce) { .rc-note-details-caret { transition: none; } }
.rc-note-details-body > .mb-3:last-child { margin-bottom: 0 !important; }
/* Undo before anything has changed. Faded rather than hidden: a button that
   comes and goes shifts every other button along the bar, and on a phone that
   bar is a row you have already learned the shape of. */
.rc-md-btn:disabled { opacity: .3; cursor: default; }
.rc-md-btn:disabled:hover, .rc-md-btn:disabled:focus-visible { background: transparent; box-shadow: none; }
.rc-md-sep { width: 1px; align-self: stretch; margin: 4px 3px; background: var(--rc-border); flex: none; }
@media (max-width: 575.98px) {
    .rc-md-toolbar { gap: 1px; padding: 4px; }
    .rc-md-btn { min-width: 36px; }
    .rc-md-sep { display: none; }
}

/* ---- The cheatsheet behind the ? button ---------------------------------

   Fixed, so it takes no space in the flow: the panel is rendered before the
   bar (see partials/markdown-toolbar) and anything occupying space there
   would come between `.rc-md-toolbar` and the textarea whose corners it
   joins.

   Centred on a desktop and docked to the bottom on a phone, where the bar
   that opened it is itself pinned above the keyboard. */
.rc-editor-help-scrim { position: fixed; inset: 0; z-index: 1040; background: rgba(12, 26, 24, .45); }
.rc-editor-help-panel {
    position: fixed; z-index: 1041;
    left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: min(30rem, calc(100vw - 32px));
    max-height: min(34rem, calc(100vh - 64px));
    display: flex; flex-direction: column;
    background: var(--rc-card);
    border: 1px solid var(--rc-border);
    border-radius: 16px;
    box-shadow: 0 18px 44px rgba(22, 48, 43, .28);
    overflow: hidden;
}
.rc-editor-help-head {
    flex: none;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 12px 14px;
    background: var(--rc-accent-fill); color: var(--rc-on-accent);
    font-weight: 700;
}
/* Bootstrap's close glyph is a dark SVG; on the filled header it needs the
   same inversion the framework's own .btn-close-white does. */
.rc-editor-help-head .btn-close { filter: invert(1) grayscale(100%) brightness(200%); opacity: .85; }
/* flex: 1 1 auto with min-height: 0 — without the min-height a flex child
   refuses to shrink below its content, and the head and foot take the panel
   between them while the list it exists for shows one row. */
.rc-editor-help-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 6px 14px; }
.rc-editor-help-row {
    display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: baseline;
    padding: 7px 0;
    border-bottom: 1px solid var(--rc-border);
}
.rc-editor-help-row:last-child { border-bottom: 0; }
.rc-editor-help-key { text-align: center; color: var(--rc-accent-dark); font-weight: 800; font-size: .8rem; }
.rc-editor-help-key .bi { font-size: 1rem; }
.rc-editor-help-does { font-size: .9rem; }
.rc-editor-help-writes { font-family: var(--bs-font-monospace); font-size: .78rem; color: var(--rc-muted); white-space: nowrap; }
/* The three behaviours nobody discovers by looking. They scroll with the
   list rather than sitting under it — see the note in the partial. */
.rc-editor-help-habits { border-top: 1px solid var(--rc-border); margin-top: 8px; padding-top: 10px; font-size: .84rem; }
.rc-editor-help-habits p { margin: 0 0 8px; }
.rc-editor-help-habits p:last-child { margin-bottom: 4px; }
.rc-editor-help-habits strong { color: var(--rc-accent-dark); }
/* One line, so the panel never gives more room to its chrome than its
   contents. flex: none keeps it that height while the body absorbs the rest. */
.rc-editor-help-foot {
    flex: none;
    border-top: 1px solid var(--rc-border);
    background: var(--rc-accent-pale);
    padding: 10px 14px;
    font-size: .84rem;
}
@media (max-width: 991.98px) {
    /* Bottom-docked like every other sheet, and clear of the tab bar by the
       variable rather than a number — see the rule in .ai/rules/css.md.

       Two extra terms the other sheets do not need. --rc-kb-inset clears the
       keyboard and --rc-mdbar-h stacks the panel on top of the pinned bar it
       belongs to rather than over it. Both are published by recall.js and are
       0 when there is nothing to clear.

       Opening this now blurs the field (recall.js), so on a phone both terms
       settle at 0 and the panel takes the whole 66vh — the keyboard and the
       bar together had been leaving it under half the screen. They are kept
       because they are not 0 straight away: a keyboard takes a moment to
       retract, and the panel is on screen for all of it. */
    .rc-editor-help-panel {
        left: 0; right: 0; top: auto; transform: none;
        width: auto;
        bottom: calc(var(--rc-tabbar-h) + var(--rc-kb-inset, 0px) + var(--rc-mdbar-h, 0px));
        /* Capped by the room that is actually left above the bottom offset,
           not by the screen. 66vh alone was measured against the whole
           screen while the keyboard and the bar had already taken half of
           it, and the head — with the only close button — went off the top.
           The 16px is a margin so the head never touches the status bar. */
        max-height: min(66vh, calc(100vh - var(--rc-tabbar-h) - var(--rc-kb-inset, 0px) - var(--rc-mdbar-h, 0px) - 16px - env(safe-area-inset-top, 0px)));
        border-radius: 18px 18px 0 0;
        border-left: 0; border-right: 0; border-bottom: 0;
    }

    /* With the keyboard up the tab bar is behind it, so counting its height
       as well would float the panel on a gap. Same branch the toolbar takes. */
    body.rc-kb-open .rc-editor-help-panel {
        bottom: calc(var(--rc-kb-inset, 0px) + var(--rc-mdbar-h, 0px));
        max-height: min(66vh, calc(100vh - var(--rc-kb-inset, 0px) - var(--rc-mdbar-h, 0px) - 16px - env(safe-area-inset-top, 0px)));
    }
}

/* ---- One toolbar, pinned above the keyboard -----------------------------

   A card form includes this partial three times — question, answer,
   explanation. At 375px each copy wraps to two rows and stands 90px tall, so
   270px of an 812px screen was toolbar before the keyboard claimed another
   40%. Worse, each one sat ABOVE its field: by the time you were typing into
   that field its buttons had scrolled off the top, which is the back and forth
   that made formatting on a phone not worth doing. The buttons were never too
   small — 40px already — the problem was the distance to them.

   Below lg the toolbar leaves the flow entirely. It takes no space at all
   until a Markdown field has focus, and then it is fixed just above the
   keyboard, showing that field's own buttons. public/js/recall.js adds
   .is-pinned on focus and measures the keyboard through the VisualViewport
   API into --rc-kb-inset.

   One row that scrolls sideways, not two that wrap: a second row costs another
   45px of the little left above a keyboard, and dropping buttons to fit would
   be guessing which ones somebody uses.

   Touch is in the gate as well as width, for the reason given at the foot of
   this file: a narrow desktop window still has a mouse and no keyboard to dock
   above, and a toolbar that detaches itself from the field there would be
   strange rather than helpful. */
@media (max-width: 991.98px) and ((pointer: coarse) or (any-pointer: coarse)) {
    .rc-md-toolbar {
        position: fixed; left: 0; right: 0; z-index: 1035;
        bottom: calc(var(--rc-tabbar-h) + var(--rc-kb-inset, 0px));
        display: none;
        flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
        border-radius: 0; border-left: 0; border-right: 0;
        border-bottom: 1px solid var(--rc-border);
        box-shadow: 0 -6px 18px rgba(22, 48, 43, .12);
    }

    /* With the keyboard up the tab bar is behind it, so adding its height as
       well would float this bar on a gap. Neither branch adds the safe-area
       inset: --rc-tabbar-h already carries it, and --rc-kb-inset is measured
       against the real viewport. */
    body.rc-kb-open .rc-md-toolbar { bottom: var(--rc-kb-inset, 0px); }

    .rc-md-toolbar.is-pinned { display: flex; }
    .rc-md-toolbar .rc-md-btn { min-width: 44px; min-height: 44px; }
    .rc-md-toolbar .rc-md-sep { display: block; }

    /* Nothing sits on the field's top edge any more, so it gets its corners
       back — 12px is --bs-border-radius as .form-control sets it. */
    .rc-md-toolbar + textarea.form-control { border-top-left-radius: 12px; border-top-right-radius: 12px; }

    /* Keep the caret clear of the bar when the browser scrolls a field in. */
    [data-rc-md] { scroll-margin-bottom: 64px; }

    /* The bar is fixed, so it covers whatever the page ends with — and on a
       card form what the page ends with is Save card and Save & add another,
       which is exactly what somebody reaches for the moment they stop typing.
       Scrolling did not help: the page had no more room to give, so the last
       50px of it were simply behind the bar.

       The page gets the bar's own height back as padding for as long as it is
       up. --rc-mdbar-h is measured and published by recall.js when it pins,
       and zeroed when it lets go, so this follows a bar of two rows or of
       taller buttons without a number here needing to be found and changed.

       Both insets are added because both can be under the bar at once: with
       the keyboard down it sits above the tab bar, and with the keyboard up
       --rc-kb-inset is what holds it clear. Every screen this bar appears on
       is a focused one, where --rc-tabbar-h is already the safe-area inset
       alone, so the sum is never a gap worth seeing. */
    body:has(.rc-md-toolbar.is-pinned) .rc-main {
        padding-bottom: calc(var(--rc-tabbar-h) + var(--rc-kb-inset, 0px) + var(--rc-mdbar-h, 0px) + 18px);
    }
}

/* Preview swaps with the editor rather than splitting the screen with it.

   The two only sit side by side from 992px up; below that the pane stacked
   under an editor already held open at min-height: 46vh. That is 373px of
   editor plus 325px of preview plus a 90px toolbar on an 812px phone — it
   overflowed with the keyboard DOWN, and neither half was usable, so the
   preview was off by default and nobody ever checked their work.

   Width alone gates this one: a narrow window cannot show two columns whatever
   is pointing at it, and the note editor now says Write / Preview rather than
   toggling an eye, so swapping is what the control promises. */
@media (max-width: 991.98px) {
    .rc-editor-grid.with-preview > .rc-editor-col { display: none; }
    .rc-editor-grid.with-preview .rc-preview { max-height: none; min-height: 62vh; }
}
textarea.rc-editor { min-height: 46vh; flex: 1 1 auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 15px; line-height: 1.55; resize: vertical; }
.rc-autocomplete {
    position: absolute; z-index: 20; left: 0; right: 0; margin-top: 4px;
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 12px; box-shadow: 0 8px 24px rgba(22, 48, 43, .12);
    overflow: hidden;
}
.rc-autocomplete button { display: block; width: 100%; text-align: left; padding: 10px 14px; border: 0; background: none; color: var(--rc-text); }
.rc-autocomplete button:hover, .rc-autocomplete button.active { background: var(--rc-accent-pale); }

/* Study screen */
.rc-study-wrap { max-width: 640px; margin: 0 auto; }

/* Memory Match's board sits in the middle of the height left under the
   header, rather than pinned beneath it with the screen empty below. Ten tiles
   are about 600px of an 812px phone, so top-aligning left an obvious gap.

   Only the board and its Give up move: the back link, the progress bar and the
   "memorise the board" line are orientation and keep their place at the top.

   Scoped by :has(.rc-game-stage) so it reaches this one game without a marker
   class. Not the other four games, and not the review session — that is
   .rc-focused, and its card is a fixed 400px precisely so the answer box has
   one steady place to sit.

   `margin: auto 0` rather than justify-content, because a board taller than
   the screen would have its top cut off and become unreachable under centring;
   auto margins simply stop giving space they do not have. */
body.rc-immersive .rc-main:has(.rc-game-stage) { display: flex; flex-direction: column; min-height: calc(100vh - 45px); }
@supports (height: 100dvh) {
    body.rc-immersive .rc-main:has(.rc-game-stage) { min-height: calc(100dvh - 45px); }
}
body.rc-immersive .rc-main:has(.rc-game-stage) .rc-study-wrap {
    flex: 1 1 auto; display: flex; flex-direction: column; width: 100%;
}
body.rc-immersive .rc-main:has(.rc-game-stage) .rc-game-stage { margin: auto 0; }
.rc-study-progress { height: 6px; }
.rc-study-card {
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 20px;
    /* A flat 400px, chosen deliberately over anything viewport-relative: the
       answer box needs one steady place to sit or it jumps between cards.
       The known cost is a viewport shorter than about 500px — a phone held
       sideways — where the card fills the screen and the answer box has to be
       scrolled to. Kept flat on purpose; do not quietly reintroduce a vh cap. */
    padding: 24px 18px; min-height: 400px; display: flex; flex-direction: column; justify-content: center;
    box-shadow: 0 2px 10px rgba(22, 48, 43, .06);
}

/* The card advancing, and the answer arriving.

   The view already keys the card on `card-{id}-{position}`, so every advance
   mounts a fresh node and this runs again — the same mechanism Survival's
   flame uses. Revealing an answer re-renders the SAME key, so the card is
   morphed rather than replaced and does not re-animate: only a genuine
   advance moves.

   This is doing more than decoration. Two consecutive cards can look alike,
   and with an instant swap there is real doubt about whether the deck moved
   at all — the motion is what answers that without reading the card.

   Excluded where a card is furniture rather than the thing being studied: the
   builder's live preview re-renders while you type, and the landing hero's
   mock is a picture. */
@media (prefers-reduced-motion: no-preference) {
    /* Distance and easing were raised to make this register; the DURATION was
       deliberately left at .2s. It is the only lever that delays when the
       question becomes readable, and this runs forty-odd times in a session —
       the one animation in the app where being slightly too quiet is a much
       smaller mistake than being slightly too slow.

       The curve overshoots a little, which reads as motion far more strongly
       than a longer linear settle and costs no extra time. Milder than the
       flame's .34/1.56 spring: that is a 20px icon, this is a 400px card, and
       the same bounce on something this size reads as wobble. */
    .rc-study-card { animation: rc-card-in .2s cubic-bezier(.2, 1.15, .3, 1); }
    @keyframes rc-card-in {
        from { opacity: 0; transform: translateY(20px); }
        to   { opacity: 1; transform: none; }
    }

    .rc-card-preview .rc-study-card,
    .rc-land-screen .rc-study-card { animation: none; }
}
.rc-study-card.is-compact { min-height: 0; padding: 18px 16px; }
.rc-study-q { font-size: 1.3rem; font-weight: 700; line-height: 1.35; text-align: center; overflow-wrap: anywhere; }

/* A question being asked cannot be selectable on a phone.
   Long-pressing a question in Chrome on Android selects it and raises Touch
   to Search — a bar at the foot of the screen offering the answer, one tap
   away, while the clock is still running. "Which Philippine volcano is
   famous for its near-perfect cone" is a card and also a search that returns
   Mayon. The selection toolbar lands over the question and the timer as
   well, and it is reached by a press only slightly too long.

   Suppressing selection removes the trigger, because the trigger IS the
   selection. This is not an anti-cheat measure and must not be sold as one:
   anyone can switch apps and search. It stops the answer being one
   ACCIDENTAL tap from somebody who meant to study.

   Question and options only. The explanation (.rc-explain) stays selectable
   — by then the card is graded and the text is material worth copying — and
   so does .rc-card-preview, which is the author looking at their own card.

   Coarse pointers only. Touch to Search is a phone behaviour; a mouse user
   loses a real capability for nothing, and here `any-pointer` would be the
   wrong gate — unlike a touch target, this TAKES something away, so it goes
   on the primary pointer alone and a touch laptop with a mouse keeps it. */
@media (pointer: coarse) {
    .rc-study-q,
    .rc-choice .btn,
    .rc-tile-text {
        -webkit-user-select: none; user-select: none;
        -webkit-touch-callout: none;
    }

    .rc-card-preview .rc-study-q { -webkit-user-select: text; user-select: text; }
}
/* Rendered Markdown in a card: paragraphs stay tight, lists stay readable, nothing shouts. */
.rc-md p { margin: 0 0 .5em; }
.rc-md p:last-child { margin-bottom: 0; }
.rc-md ul, .rc-md ol { display: inline-block; text-align: left; margin: .25em 0 0; padding-left: 1.2em; }
.rc-md code { background: var(--rc-accent-pale); padding: 1px 5px; border-radius: 5px; font-size: .9em; }
.rc-md pre { background: var(--rc-accent-pale); padding: 10px; border-radius: 10px; text-align: left; overflow-x: auto; font-size: .85em; }
.rc-md a { font-weight: 600; }
.rc-md img { max-width: 100%; }
.rc-study-a .rc-md { white-space: normal; font-weight: 400; }
.rc-study-a { font-size: 1.15rem; text-align: center; margin-top: 18px; padding-top: 18px; border-top: 1px dashed var(--rc-border); overflow-wrap: anywhere; }
/* Rendered only once revealed, so it is a fresh node and this runs on the
   reveal. Shorter and smaller than the card's own entrance: the answer is
   appearing inside a card that is already sitting still, and anything bigger
   reads as the whole card moving again. */
@media (prefers-reduced-motion: no-preference) {
    .rc-study-a { animation: rc-answer-in .16s ease-out; }
    @keyframes rc-answer-in {
        from { opacity: 0; transform: translateY(-4px); }
        to   { opacity: 1; transform: none; }
    }
    .rc-card-preview .rc-study-a,
    .rc-land-screen .rc-study-a { animation: none; }
}
.rc-study-a-text { white-space: pre-wrap; display: inline-block; max-width: 100%; text-align: center; }
/* A fill-in-the-blank blank: one ruled line, never a row of underscores.
   Empty and inline-block, so its bottom border sits exactly on the baseline. */
.rc-blank {
    display: inline-block;
    width: 5.5em;
    max-width: 46%;
    border-bottom: 2px solid currentColor;
    opacity: .45;
    margin: 0 .12em;
}
/* A preference switch: the label carries a title and a line of explanation,
   so the control needs to sit against the top of it rather than the middle. */
.rc-switch { display: flex; gap: 10px; padding-left: 0; }
.rc-switch .form-check-input { flex: 0 0 auto; margin: 2px 0 0; float: none; width: 2.4em; height: 1.3em; }
.rc-switch .form-check-input:checked { background-color: var(--rc-accent); border-color: var(--rc-accent); }
.rc-switch .form-check-input:focus { border-color: var(--rc-accent); box-shadow: 0 0 0 .2rem rgba(var(--rc-accent-rgb), .25); }
.rc-switch .form-check-label { cursor: pointer; }

/* ---- admin: the library shelf ------------------------------------------
   A row carries three different things — what the set is, how it is doing,
   and what it offers people — so each gets its own band rather than one long
   line that wraps differently on every row. The offers sit on a wash so they
   read as settings rather than as more content, which is what the plain
   border-top made them look like. */
/* Admin Library: a list with columns, not a stack of cards.

   Every set used to take three stacked bands and about 165px — the name and
   six equal buttons, a line of statistics, then a tinted strip carrying two
   switches — whether the set was thriving, hidden or reaching nobody. Under
   five sets fitted a laptop screen, and the switch labels ("Add to their
   sets", "They can take a copy of their own", "Play in games") were repeated
   word for word on every row. In columns the labels are written once, in the
   head, and the state is the only thing that varies down the page. */
.rc-lib-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
/* One row, never two. Ten subjects wrapping onto a second row gave every
   subject the same shout and pushed the sets themselves below the fold.
   Scrolled sideways they stay glanceable and cost one row whatever the shelf
   grows to. The fade on the right is the only thing that says there is more,
   so it is not decoration. */
.rc-lib-filters {
    display: flex; gap: 6px; flex-wrap: nowrap;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -ms-overflow-style: none;
    padding-bottom: 2px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
.rc-lib-filters::-webkit-scrollbar { display: none; }
.rc-lib-chip { flex: none; }
.rc-lib-search { flex: 1 1 14rem; min-width: 0; margin-left: auto; }

/* ---- Those same rows, given a mouse -------------------------------------

   One row, never two — the note above stands, and it stands on a desktop as
   well: a wrapped run gives every subject the same shout and pushes the sets
   below the fold. What does not survive the move to a mouse is the way you
   reach the rest of it. A finger drags the row and the fade says there is
   more; a mouse has neither, and both rows hide their scrollbar, so the last
   subject sat clipped mid-word with nothing to grab. Shift+wheel works and
   nobody knows it.

   So the row stays one row and the scrollbar comes back — slim, and only for
   a pointer that can use it. The native bar is the one affordance every
   desktop user already reads, costs no markup, and keeps the keyboard and
   the wheel working for free.

   Room is made for it rather than letting it sit on the chips: a 8px bar
   under a 38px chip otherwise overlaps the row it belongs to. */
@media (pointer: fine) {
    .rc-filters,
    .rc-lib-filters,
    .rc-offline-sets {
        scrollbar-width: thin;
        scrollbar-color: var(--rc-border) transparent;
        -ms-overflow-style: auto;
        padding-bottom: 8px;
    }

    .rc-filters::-webkit-scrollbar,
    .rc-lib-filters::-webkit-scrollbar,
    .rc-offline-sets::-webkit-scrollbar { display: block; height: 8px; }

    .rc-filters::-webkit-scrollbar-track,
    .rc-lib-filters::-webkit-scrollbar-track,
    .rc-offline-sets::-webkit-scrollbar-track { background: transparent; }

    .rc-filters::-webkit-scrollbar-thumb,
    .rc-lib-filters::-webkit-scrollbar-thumb,
    .rc-offline-sets::-webkit-scrollbar-thumb {
        background: var(--rc-border); border-radius: 99px;
    }

    .rc-filters:hover::-webkit-scrollbar-thumb,
    .rc-lib-filters:hover::-webkit-scrollbar-thumb,
    .rc-offline-sets:hover::-webkit-scrollbar-thumb { background: var(--rc-muted); }

    /* The fade would sit over the bar and dim its right end. */
    .rc-lib-filters {
        -webkit-mask-image: none;
        mask-image: none;
    }
}
.rc-lib-chip {
    border: 1px solid var(--rc-border); background: var(--rc-card); color: var(--rc-muted);
    border-radius: 99px; padding: 4px 12px; font-size: .82rem; line-height: 1.3;
}
.rc-lib-chip b { color: var(--rc-text); font-weight: 700; }
.rc-lib-chip.is-on { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-lib-chip.is-on b { color: var(--rc-on-accent); }
/* Outlined rather than another fill: this chip sits beside the accent one and
   warn-against-accent is the pair that collapses under a red-green deficiency
   — see the colour-blindness rule at the top of this file. Shape carries it. */
.rc-lib-chip.is-warn { border-color: var(--rc-warn); color: var(--rc-warn); background: var(--rc-warn-pale); }
.rc-lib-chip.is-warn.is-on { background: var(--rc-warn); border-color: var(--rc-warn); color: #fff; }
.rc-lib-chip.is-warn.is-on b { color: #fff; }

.rc-lib-list {
    background: var(--rc-card); border: 1px solid var(--rc-border);
    border-radius: 14px; overflow: hidden;
}
.rc-lib-row {
    display: grid;
    /* Every track a fixed size except the name. Each row is its OWN grid, so
       an `auto` column sizes to that row's content and nothing lines up: the
       head's "ACTIONS" measured 53px against a row's 104px of buttons, and
       the 1fr name column swallowed the 51px difference — which pushed every
       column head half an inch right of the numbers under it. */
    /* Eight tracks is a lot for a laptop. Measured at 1280 with the sidebar
       the list gets 886px, and the first draft left the set's own name 134px
       of it — narrower than the subject beside it. Everything fixed was
       shaved to give the name back about 90px. */
    grid-template-columns: 22px minmax(0, 1fr) 6rem 3.5rem 3.5rem 4rem 10rem 7.5rem;
    /* 14px, not 10: right-aligned PLAYED ends hard against the left-aligned
       OFFERS beside it, and at 10px the two words touched. */
    column-gap: 12px; align-items: center;
    padding: 8px 14px; border-bottom: 1px solid var(--rc-border);
}
.rc-lib-row:last-child { border-bottom: 0; }
.rc-lib-head {
    background: var(--rc-bg); padding-top: 6px; padding-bottom: 6px;
    font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--rc-muted);
}
.rc-lib-name { min-width: 0; }
.rc-lib-name b { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-weight: 700; }
.rc-lib-name a { font-size: .95rem; text-decoration: none; }
.rc-lib-from { display: block; font-size: .72rem; color: var(--rc-muted); }
.rc-lib-hit { color: var(--rc-accent-dark); font-weight: 600; }
.rc-lib-hit .bi { font-size: .68rem; margin-right: 2px; }
.rc-lib-found { margin: 8px 2px 0; font-size: .8rem; color: var(--rc-muted); }
.rc-lib-flag {
    flex: none; font-size: .64rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    padding: 1px 6px; border-radius: 5px; background: var(--rc-bg); color: var(--rc-muted);
}
.rc-lib-flag.is-warn { background: var(--rc-warn-pale); color: var(--rc-warn); }
/* Tabular figures so a column of counts lines up on its digits — the whole
   reason for putting them in a column. */
.rc-lib-num { text-align: right; font-size: .88rem; font-variant-numeric: tabular-nums; }
.rc-lib-num.is-zero { color: var(--rc-muted); opacity: .65; }

/* The subject picker. A select rather than free text on every row: two
   spellings of one subject look like two subjects and nothing on the screen
   would say otherwise. */
.rc-lib-subject .form-select { font-size: .78rem; padding: 2px 22px 2px 8px; }
/* Spans every track so the box has room to be typed in. Inside the 6rem
   subject column the input collapsed to a sliver and Save/Cancel overlapped
   the counts; the eight columns above keep their tracks either way, so the
   rows still line up with the head. */
.rc-lib-newsubject {
    grid-column: 1 / -1;
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    padding: 8px 10px; margin-top: 2px;
    background: var(--rc-bg); border-radius: 8px;
}
.rc-lib-newsubject label { font-size: .78rem; font-weight: 600; color: var(--rc-muted); }
.rc-lib-newsubject .form-control { font-size: .82rem; flex: 1 1 14rem; min-width: 0; max-width: 22rem; }
.rc-lib-newsubject-err { flex: 1 1 100%; font-size: .78rem; color: var(--rc-danger); }
/* Wraps onto its own line rather than squeezing the state chips beside it. */
.rc-lib-subjects { flex: 1 1 100%; }

/* The rename/ungroup bar for the filtered subject: its own full-width line
   under the chips, so acting on a subject is visibly separate from choosing
   which one to look at. */
.rc-lib-subject-manage {
    flex: 1 1 100%;
    display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
    margin-top: 2px;
}
.rc-lib-subject-manage-label { font-size: .82rem; color: var(--rc-muted); margin-right: 2px; }
.rc-lib-subject-manage-label strong { color: var(--rc-text); }

.rc-lib-offers { display: grid; gap: 3px; }
.rc-lib-offers .rc-switch { gap: 7px; min-height: 0; margin: 0; }
.rc-lib-offers .form-check-label { font-size: .78rem; line-height: 1.25; }
.rc-lib-offers .form-check-input { margin-top: 1px; }
/* A hidden set offers nothing either way, and the pair reads off and locked
   rather than claiming otherwise — what it WILL offer is remembered. */
/* Saving. A switch answers on a round trip, so without this the admin flips
   it and watches nothing happen — then flips it again, which undoes the save
   that was already on its way. The switch is disabled for the same reason. */
.rc-switch-busy {
    flex: none; align-self: center;
    width: .8rem; height: .8rem; border-width: .12em;
    color: var(--rc-muted);
}
.rc-lib-offers .form-check-input:disabled { opacity: .4; }
.rc-lib-offers .form-check-input:disabled + .form-check-label { opacity: .55; }
.rc-lib-acts { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }

/* The handle keeps its column when it cannot be used, so the rows still line
   up under the head — searched or filtered, row two is not position two. */
.rc-lib-row .rc-grip.is-off { opacity: .25; cursor: default; }
.rc-lib-row .rc-grip.is-off:hover { background: none; color: inherit; }

/* Both ways to add a set, side by side in one panel. */
.rc-lib-add { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: 18px; }
.rc-lib-add-head { font-size: .95rem; margin-bottom: 8px; }
.rc-lib-add .form-control, .rc-lib-add .form-select { flex: 1 1 12rem; min-width: 0; }

/* Below lg the columns become a card: the name and its actions on one line,
   the counts on the next carrying the label the column head used to give
   them, and the switches on their own. Gated on width alone — this is about
   how little room there is, not about what is pointing at it. lg rather than
   md because the sidebar arrives at the same breakpoint, so a 900px window
   has about the width a 1280px one has with the sidebar taking its share. */
@media (max-width: 991.98px) {
    /* Two classes, because the head is also a .rc-lib-row: at equal
       specificity the display:flex below would win on source order and the
       column head would come back as a row of stray words. */
    .rc-lib-row.rc-lib-head { display: none; }
    .rc-lib-row {
        display: flex; flex-wrap: wrap; align-items: center;
        column-gap: 10px; row-gap: 8px; padding: 10px 12px;
    }
    .rc-lib-row .rc-grip { order: 0; flex: none; }
    .rc-lib-name { order: 1; flex: 1 1 8rem; }
    .rc-lib-acts { order: 2; flex: none; margin-left: auto; }
    .rc-lib-num {
        order: 3; flex: none; text-align: left;
        font-size: .78rem; color: var(--rc-muted); font-weight: 600;
    }
    .rc-lib-num::before { content: attr(data-label) " "; font-weight: 400; opacity: .8; }
    .rc-lib-subject { order: 3; flex: 1 1 100%; }
    .rc-lib-offers { order: 4; flex: 1 1 100%; gap: 6px; }
    /* The row is flex here, so the grid-column that gives this a full line on
       desktop means nothing and it has to be said again. */
    .rc-lib-newsubject { order: 5; flex: 1 1 100%; }
    .rc-lib-search { flex: 1 1 100%; margin-left: 0; }
}

/* The clock centred above the type label, so it reads as part of the card
   rather than something pinned to its edge. */
.rc-study-head { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 12px; }
.rc-study-timer {
    position: relative; width: 40px; height: 40px;
    display: grid; place-items: center; color: var(--rc-muted);
}
.rc-study-timer svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.rc-study-timer circle { fill: none; stroke-width: 3; }
.rc-timer-track { stroke: var(--rc-border); }
.rc-timer-fill { stroke: var(--rc-accent); stroke-dasharray: 94.25; stroke-linecap: round; transition: stroke-dashoffset .12s linear; }
.rc-study-timer span { font-size: .95rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
/* Held, not broken: the bet is still open and the choices are not on screen
   yet, so the number sits at full and does not move. */
.rc-study-timer.is-held { opacity: .45; }
.rc-study-timer.is-warn { color: var(--rc-warn); }
.rc-study-timer.is-warn .rc-timer-fill { stroke: var(--rc-warn); }
.rc-study-timer.is-late { color: var(--rc-danger); }
.rc-study-timer.is-late .rc-timer-fill { stroke: var(--rc-danger); }
.rc-study-type { font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--rc-muted); text-align: center; margin-bottom: 12px; }
.rc-answer-bar { position: sticky; bottom: calc(var(--rc-tabbar-h) + 8px); margin-top: 14px; }
@media (min-width: 992px) { .rc-answer-bar { bottom: 12px; } }
/* Matching and multiple answer are taller than the space a sticky bar has,
   so they ride with the page rather than being pinned over the question. */
.rc-answer-bar.is-tall { position: static; }
/* Answered: the explanation above is what there is to read, and a stuck bar
   sits on top of it the moment the page outgrows the viewport. */
.rc-answer-bar.is-done { position: static; }
.rc-grade { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.rc-grade .btn { min-height: 56px; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.1; }
.rc-grade .btn small { font-weight: 500; opacity: .8; font-size: .72rem; margin-top: 2px; }
.btn-grade-no { --bs-btn-bg: var(--rc-danger-pale); --bs-btn-border-color: var(--rc-danger-pale); --bs-btn-color: var(--rc-danger); --bs-btn-hover-bg: var(--rc-danger-fill); --bs-btn-hover-color: var(--rc-on-accent); --bs-btn-hover-border-color: var(--rc-danger-fill); }
.btn-grade-hard { --bs-btn-bg: var(--rc-warn-pale); --bs-btn-border-color: var(--rc-warn-pale); --bs-btn-color: var(--rc-warn); --bs-btn-hover-bg: var(--rc-warn); --bs-btn-hover-color: var(--rc-on-accent); --bs-btn-hover-border-color: var(--rc-warn); }
.rc-choice { display: grid; gap: 8px; }

/* ---- Confirm dialog -----------------------------------------------------
   Replaces the browser's own confirm() for wire:confirm. The native one is a
   grey system slab with "OK" on it, announced as "recall.fsulit.net says" —
   it belongs to the browser, not to this app, and it arrives at exactly the
   moment somebody is deciding whether to destroy something.

   Above Bootstrap's dialog layer (1055) on purpose: a delete can be asked for
   from inside a modal, and the question has to sit over what it is asking
   about. */
.rc-confirm-scrim {
    position: fixed; inset: 0; z-index: 1100;
    background: rgba(12, 26, 24, .5);
    display: flex; align-items: center; justify-content: center;
    padding: 20px;
}
.rc-confirm {
    width: min(420px, 100%);
    background: var(--rc-card); color: var(--rc-text);
    border: 1px solid var(--rc-border); border-radius: 18px;
    box-shadow: 0 18px 48px rgba(12, 26, 24, .3);
    padding: 20px;
}
.rc-confirm-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 6px; }
.rc-confirm-body { color: var(--rc-muted); margin: 0; font-size: .92rem; line-height: 1.55; }
.rc-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
/* Cancel is the wider target and takes focus, because the question is usually
   asked about something that cannot be undone. */
.rc-confirm-actions .btn { min-width: 96px; }

@media (prefers-reduced-motion: no-preference) {
    .rc-confirm { animation: rc-confirm-in .16s ease-out; }
    @keyframes rc-confirm-in {
        from { opacity: 0; transform: translateY(8px) scale(.98); }
        to   { opacity: 1; transform: none; }
    }
}

/* ---- Survival's run banner ---------------------------------------------
   The tension in this game is one wrong answer ending a streak, and none of
   that was visible: the score was a grey word in the page header and the rule
   was never stated. This says both, above the question, where the decision is
   being made.

   The flame is the only thing on the screen that changes with how the run is
   going — it warms at five and blazes at ten. Everything it signals is also in
   the number beside it, so nothing depends on noticing a colour. */
.rc-survive {
    display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
    padding: 12px 16px; margin-bottom: 12px;
    border: 1px solid var(--rc-border); border-radius: 16px; background: var(--rc-card);
}
.rc-survive-run { display: flex; align-items: baseline; gap: 8px; }
/* Cold only before the run starts. From the first right answer it is lit, and
   it warms again at five and ten — so the flame reads as the run itself rather
   than as decoration that switches on late. */
.rc-survive-run .bi-fire { color: var(--rc-muted); font-size: 1.3rem; align-self: center; display: inline-block; transform-origin: 50% 65%; }
.rc-survive.is-lit .bi-fire { color: var(--rc-flame); }

/* Every right answer flares it. The element is keyed to the score in the view,
   so each increment mounts a fresh node and the animation runs again. */
@keyframes rc-flare {
    0%   { transform: scale(1); }
    30%  { transform: scale(1.5) rotate(-4deg); }
    55%  { transform: scale(1.15) rotate(3deg); }
    100% { transform: scale(1); }
}
.rc-survive.is-lit .bi-fire,
.rc-survive.is-hot .bi-fire,
.rc-survive.is-blazing .bi-fire { animation: rc-flare .5s cubic-bezier(.34, 1.56, .64, 1); }
@media (prefers-reduced-motion: reduce) {
    .rc-survive .bi-fire { animation: none; }
}
.rc-survive-n { font-size: 2rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.rc-survive-word { font-size: .82rem; font-weight: 600; color: var(--rc-muted); }
.rc-survive-side { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; text-align: right; }
/* The rule of the game, said once and quietly — it is a constraint, not an alarm. */
.rc-survive-life { font-size: .78rem; font-weight: 700; color: var(--rc-muted); }
.rc-survive-life .bi { color: var(--rc-danger); }
.rc-survive-best { font-size: .78rem; font-weight: 600; color: var(--rc-muted); }
.rc-survive-best.is-record { color: var(--rc-accent-dark); font-weight: 700; }

/* A run worth caring about. Border and flame together, so the state is not
   carried by colour alone. */
.rc-survive.is-hot { border-color: var(--rc-flame); }
.rc-survive.is-hot .bi-fire { color: var(--rc-flame); }
.rc-survive.is-blazing { border-color: var(--rc-flame); box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .10); }
.rc-survive.is-blazing .bi-fire { color: var(--rc-flame); font-size: 1.6rem; }
.rc-survive.is-blazing .rc-survive-n { color: var(--rc-flame); }

/* ---- Stakes -------------------------------------------------------------
   A bet on your own certainty, so the three levels have to read apart at a
   glance and while a clock is running.

   They escalate by fill weight and by a count of filled pips, never by hue.
   That is this file's standing rule — anything carrying meaning has to survive
   protanopia and deuteranopia — and it is the right answer here anyway: 1x, 2x
   and 3x are an ordered scale, and lightness is the only channel that reads as
   ordered. Both cues survive greyscale. */
/* Flames rather than plain dots: the streak chip already burns for a run kept
   up, so a stake reads as the same currency. One lit flame per level, the rest
   left cold — a count, not a colour, so the scale still reads in greyscale and
   under a red-green deficiency. Decorative: the multiplier and the word beside
   them carry the meaning, which is why they are aria-hidden. */
.rc-flames { display: inline-flex; gap: 1px; flex: none; }
.rc-flames .bi { font-size: .95rem; line-height: 1; opacity: .22; }
.rc-flames .bi.on { opacity: 1; color: var(--rc-flame); }
/* The placed chip still fills by weight — there is only one of it, so there is
   nothing for it to look chosen against. On its solid rung the ground is the
   accent, and the theme's flame is tuned against a card rather than that, so
   the flames borrow the chip's own text colour. */
.rc-staked.is-3 .rc-flames .bi.on { color: var(--rc-on-accent); }

.rc-stakes { display: grid; gap: 8px; }
.rc-stake {
    display: flex; align-items: center; gap: 12px;
    min-height: 56px; padding: 0 16px;
    border: 1px solid var(--rc-border); border-radius: 14px;
    background: var(--rc-card); color: var(--rc-text);
    font-weight: 600; text-align: left;
}
.rc-stake:hover { border-color: var(--rc-accent); }
.rc-stake:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .3); }
.rc-stake-x { font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; min-width: 36px; }
.rc-stake-label { font-weight: 600; opacity: .75; }
/* All three sit on the same ground on purpose. Filling them by weight read as
   "3x is already chosen", because a solid accent means selected everywhere else
   in this app — the answer choices, the layout switch, the active nav tab. The
   scale is carried by the flames and the multiplier, which say how much without
   claiming anything has been picked. */

/* The bet once it is placed. Same scale, shrunk to a chip. */
.rc-staked {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 14px; border-radius: 99px;
    background: var(--rc-card); border: 1px solid var(--rc-border); color: var(--rc-muted);
    font-size: .82rem; font-weight: 700;
}
.rc-staked-x { font-size: 1rem; font-variant-numeric: tabular-nums; }
.rc-staked.is-2 { background: var(--rc-accent-pale); border-color: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-staked.is-3 { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-choice .btn { min-height: 52px; text-align: left; display: flex; align-items: center; gap: 10px; white-space: normal; }
.rc-choice .rc-key { flex: none; width: 26px; height: 26px; border-radius: 8px; background: rgba(var(--rc-accent-rgb), .15); display: inline-flex; align-items: center; justify-content: center; font-size: .8rem; font-weight: 700; }
/* The beat before a new question's choices accept a tap. Held at full
   opacity: a choice that fades in reads as loading, and this is a fresh
   question that is entirely ready to be read. */
.rc-choice .btn.is-settling { opacity: 1; cursor: default; }
/* Multiple answer: a chosen option stays chosen until Check, so it has to
   read as held down rather than as merely hovered. */
.rc-choice .btn.rc-picked { background: var(--rc-accent-pale); border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-choice .btn.rc-picked .rc-key { background: var(--rc-accent-fill); color: var(--rc-on-accent); }

/* Matching: two columns of tap targets. Narrow phones get small type rather
   than a second row, because a pair only reads as a pair side by side. */
/* The gutter is where the wires are drawn, so it is sized for them rather
   than for the 8px it took to keep two columns apart. Under about 22px a
   curve has nowhere to bend and four of them crossing read as a smudge; the
   width comes off the items, which is why the type steps down sooner below.
   Row gap stays where it was — .rc-pairs-item carries its own margin. */
.rc-pairs { display: grid; grid-template-columns: 1fr 1fr; column-gap: 26px; row-gap: 8px; margin-bottom: 4px; position: relative; }

/* Wires under the columns, never over them: .rc-pairs is positioned and comes
   second in the wrap, so it paints on top. A curve can never cross a word,
   and each end tucks under the item it lands on rather than stopping short
   of it — which is what makes it read as plugged in. */
.rc-pairs-wrap { position: relative; }
.rc-pairs-wires { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.rc-wire { fill: none; stroke: var(--rc-accent); stroke-width: 2.5; stroke-linecap: round; }
.rc-pairs-head { font-size: .7rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--rc-muted); margin-bottom: 6px; }
.rc-pairs-item { width: 100%; min-height: 52px; margin-bottom: 8px; text-align: left; display: flex; align-items: center; gap: 8px; white-space: normal; font-size: .95rem; line-height: 1.25; }
.rc-pairs-item .rc-key { flex: none; width: 26px; height: 26px; border-radius: 8px; background: rgba(var(--rc-accent-rgb), .15); display: inline-flex; align-items: center; justify-content: center; font-size: .8rem; font-weight: 700; }
.rc-pairs-item.is-picked { background: var(--rc-accent-pale); border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-pairs-item.is-picked .rc-key { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-pairs-item.is-active { border-color: var(--rc-accent); box-shadow: 0 0 0 2px rgba(var(--rc-accent-rgb), .35); }
@media (max-width: 380px) { .rc-pairs-item { font-size: .85rem; padding-left: 8px; padding-right: 8px; } }

.rc-hint { font-size: .82rem; color: var(--rc-muted); margin: 6px 2px 8px; min-height: 1.2em; }
.rc-hint.is-error { color: var(--rc-danger); font-weight: 600; }

/* The answer side of a multi-part card: one row per choice or pair, each
   marked, so a near miss shows which part was the miss. */
.rc-review { display: grid; gap: 6px; text-align: left; font-size: .95rem; }
.rc-review-row { display: flex; align-items: baseline; gap: 8px; padding: 7px 10px; border-radius: 10px; }
.rc-review-row.ok { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-review-row.no { background: var(--rc-danger-pale); color: var(--rc-danger); }
/* A wrong option rightly left alone: shown, but not congratulated. */
.rc-review-row.idle { background: transparent; color: var(--rc-muted); }
.rc-review-row i { font-size: .85rem; }
.rc-review-note { margin-left: auto; font-size: .75rem; opacity: .8; white-space: nowrap; }

.rc-result { border-radius: 14px; padding: 12px 14px; font-weight: 600; display: flex; align-items: center; gap: 10px; }
.rc-result.ok { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-result.no { background: var(--rc-danger-pale); color: var(--rc-danger); }
/* The Settings link in the time's-up line takes the banner's colour: the
   default link teal on the danger wash is the one pairing this file's rules
   warn about, and an underline is enough to say it is a link. */
.rc-result-link { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.rc-explain { background: var(--rc-accent-pale); border-radius: 14px; padding: 12px 14px; margin-top: 12px; text-align: left; font-size: .95rem; color: var(--rc-accent-dark); }
.rc-explain-label { display: block; font-size: .7rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; opacity: .75; margin-bottom: 4px; }

/* A card that was missed promotes its "why" to the main event: the answer
   line above it is already known by then, and this is the part worth the
   screen. Marked with the accent rather than the danger colour — being
   wrong is the reason it is here, not the message. */
.rc-explain.is-missed { border-left: 4px solid var(--rc-accent); padding: 16px 18px; margin-top: 14px; font-size: 1rem; box-shadow: 0 1px 3px rgba(0, 0, 0, .06); }
.rc-explain.is-missed .rc-explain-label { opacity: 1; }
/* The opening line carries the weight, because it is the line that will
   actually be read: everything under it is one tap away and, for a learner
   with a button waiting, usually stays there. */
.rc-explain.is-missed .rc-explain-body > :first-child { font-weight: 600; }

/* Clipped to its opening, with the last line fading out so it reads as cut
   rather than as ending there. --rc-clip is read back by rcExplain(), which
   decides from it whether the button below is needed at all. */
.rc-explain-body { --rc-clip: 52px; }
.rc-explain.is-missed .rc-explain-body { --rc-clip: 64px; }
.rc-explain-body.is-clipped { max-height: var(--rc-clip); overflow: hidden; -webkit-mask-image: linear-gradient(180deg, #000 55%, transparent 100%); mask-image: linear-gradient(180deg, #000 55%, transparent 100%); }
.rc-explain-more { background: none; border: 0; padding: 2px 0 0; margin-top: 4px; font-size: .8rem; font-weight: 700; color: inherit; opacity: .8; display: inline-flex; align-items: center; gap: 5px; }
.rc-explain-more:hover { opacity: 1; text-decoration: underline; }

/* The beat before Continue answers on a missed card. Held at full opacity
   for the same reason a settling choice is: the button is not loading and
   not broken, it is a moment early, and a faded one reads as both. The fill
   is the only thing that says so, so it has to be visible on the primary. */
.btn.rc-gate { position: relative; overflow: hidden; }
.btn.rc-gate:disabled { opacity: 1; cursor: default; }
.rc-gate-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 100%; transform-origin: right center; background: rgba(255, 255, 255, .28); transition: transform .07s linear; pointer-events: none; }
.rc-gate-text { position: relative; }
@media (prefers-reduced-motion: reduce) { .rc-gate-fill { transition: none; } }
/* The policy pages. Long-form reading rather than an interface: a wider
   measure than a form, and headings that give the eye somewhere to land when
   somebody is scanning for the one paragraph they came for. */
.rc-legal h1 { font-size: 1.6rem; margin-bottom: 4px; }
.rc-legal h2 { font-size: 1.05rem; font-weight: 700; margin-top: 28px; margin-bottom: 8px; color: var(--rc-accent-dark); }
.rc-legal h3 { font-size: .95rem; font-weight: 700; margin-top: 18px; margin-bottom: 4px; }
.rc-legal p, .rc-legal li { line-height: 1.65; }
.rc-legal ul { padding-left: 20px; }
.rc-legal li { margin-bottom: 6px; }
/* A value the document needs and has not been given. Loud on purpose — it is
   there to be noticed in review, not to be lived with. */
.rc-legal-pending { background: var(--rc-warn-pale); color: var(--rc-warn); font-weight: 700; padding: 0 4px; border-radius: 4px; }
.rc-guest-legal { text-align: center; margin-top: 16px; font-size: .82rem; }
.rc-guest-legal a { color: var(--rc-muted); text-decoration: none; }
.rc-guest-legal a:hover { text-decoration: underline; }
/* Quieter than the links beside it and on its own line: it is a thing to
   quote when reporting something, not a thing to read on the way in. */
.rc-guest-version {
    display: block; margin-top: 6px;
    font-family: ui-monospace, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums;
    font-size: .72rem; color: var(--rc-muted); opacity: .75;
}

.rc-kbd { display: inline-block; padding: 1px 6px; border: 1px solid var(--rc-border); border-bottom-width: 2px; border-radius: 6px; font-size: .72rem; font-family: ui-monospace, monospace; color: var(--rc-muted); background: var(--rc-card); }
.rc-recap-n { font-size: 2.4rem; font-weight: 800; line-height: 1; }
/* The streak's flame sits inside the number, and at this size a glyph butted
   straight against a digit reads as one smudged character ("🔥2"). */
.rc-recap-n .bi { margin-right: 5px; }

/* Games */
.rc-word { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 18px 0; }
.rc-slot { width: 34px; height: 44px; border-bottom: 3px solid var(--rc-accent); display: flex; align-items: flex-end; justify-content: center; font-size: 1.5rem; font-weight: 800; padding-bottom: 2px; }
.rc-slot.space { border-bottom-color: transparent; width: 16px; }
.rc-slot.hint { color: var(--rc-muted); }
.rc-keys { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
@media (min-width: 576px) { .rc-keys { grid-template-columns: repeat(9, 1fr); } }
.rc-keys .btn { min-height: 46px; padding: 0; font-weight: 700; }
/* A guessed key is DISABLED, and a disabled .btn takes its colours from
   --bs-btn-disabled-*, never from --bs-btn-bg. These two rules only set the
   latter, so every letter anybody pressed went on rendering as a plain pale
   key: hit and miss have never actually been drawn. The disabled variables
   are the ones that reach the screen.

   --bs-btn-disabled-opacity: 1 with them. Bootstrap fades a disabled control
   to say "not for you", but here being disabled IS the information — a key
   is disabled BECAUSE it has been used — and fading it to 65% (or to 46%,
   which is what the .7 below compounded to) throws away the one thing the
   board is being read for.

   Miss loses its fill rather than taking a red one: the available keys are
   already a pale fill, so a second pale fill differs from them by hue alone,
   and this file's rule on neighbouring colours says hue is the channel that
   does not survive. An empty key beside a filled one survives anything. */
.rc-keys .btn.hit {
    --bs-btn-disabled-bg: var(--rc-accent-fill);
    --bs-btn-disabled-color: var(--rc-on-accent);
    --bs-btn-disabled-border-color: var(--rc-accent-fill);
    --bs-btn-disabled-opacity: 1;
}
.rc-keys .btn.miss {
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-color: var(--rc-muted);
    --bs-btn-disabled-border-color: var(--rc-border);
    --bs-btn-disabled-opacity: 1;
    text-decoration: line-through;
    text-decoration-thickness: 2px;
}
.rc-lives { display: flex; justify-content: center; gap: 4px; }
.rc-lives .bi { font-size: 1.2rem; color: var(--rc-danger); }
.rc-lives .bi.lost { color: var(--rc-border); }
/* Give up is a real control, but a quiet one: it must never compete with
   the guess it is offering to abandon. */
.rc-giveup {
    display: inline-flex; align-items: center; gap: 7px;
    min-height: 38px; padding: 0 16px;
    border: 1px solid var(--rc-border); border-radius: 99px;
    background: transparent; color: var(--rc-muted);
    font-size: .85rem; font-weight: 600; line-height: 1;
}
.rc-giveup:hover { border-color: var(--rc-danger); background: var(--rc-danger-pale); color: var(--rc-danger); }
.rc-giveup:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-danger-rgb), .25); }
/* The Play hub.

   Four tiles two-up with Survival lifted out above them into a panel of its
   own. Five tiles in two columns left an empty half-row beside the fifth,
   and the run-to-beat bar that used to sit above the grid (.rc-playbar, now
   retired) carried the same number and the same link as the tile it sat
   above. The panel is both, so neither problem is left.

   A tile that cannot be played is rendered as a div rather than an anchor —
   see resources/views/components/game-tile.blade.php. Do not go back to
   dimming a live link: pointer-events stops a mouse and nothing else, so the
   tile kept its place in the tab order and Enter still opened it. */
/* The tiles, two-up, with an odd last one taking the width instead of leaving
   the hole beside it that the note above describes. A grid rather than
   Bootstrap columns because that hole came back the moment the games stopped
   being an even number, and `:last-child:nth-child(odd)` closes it for any
   number of tiles rather than for five.

   The wrapper divs went with the columns, so the tiles are the grid's children
   directly — stretching to a common height per row for free, which the columns
   only did because every tile carries a min-height. */
.rc-game-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }
@media (min-width: 576px) {
    .rc-game-grid { grid-template-columns: 1fr 1fr; }
    .rc-game-grid > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

.rc-game-tile { display: flex; flex-direction: column; gap: 6px; min-height: 172px; padding: 18px; border-radius: 18px; background: var(--rc-card); border: 1px solid var(--rc-border); color: var(--rc-text); text-decoration: none; }
a.rc-game-tile:hover { border-color: var(--rc-accent); }
.rc-game-tile.is-off, .rc-game-panel.is-off { opacity: .62; }

.rc-game-orb { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 40px; height: 40px; border-radius: 50%; background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-game-orb .bi { font-size: 1.15rem; }
.rc-game-orb.is-lg { width: 52px; height: 52px; }
.rc-game-orb.is-lg .bi { font-size: 1.5rem; }
.rc-game-orb.is-flame { background: var(--rc-warn-pale); color: var(--rc-flame); }
.rc-game-orb.is-trophy { background: var(--rc-warn-pale); color: var(--rc-warn); }
.rc-game-tile.is-off .rc-game-orb { background: var(--rc-border); color: var(--rc-muted); }

/* Pushed to the bottom so the stat lines across a row sit level, whatever
   length of blurb is above them. */
.rc-game-foot, .rc-game-why { margin-top: auto; padding-top: 10px; }
.rc-game-why { display: flex; align-items: center; gap: 6px; font-size: .78rem; font-weight: 600; color: var(--rc-muted); }
.rc-game-meter { height: 5px; border-radius: 99px; background: var(--rc-border); overflow: hidden; }
.rc-game-meter span { display: block; height: 100%; background: var(--rc-accent); }

.rc-game-panel { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 18px; border-radius: 18px; background: var(--rc-card); border: 1px solid var(--rc-border); }
.rc-game-panel-body { flex: 1 1 200px; min-width: 0; }
.rc-game-panel .rc-game-why { margin-top: 4px; padding-top: 0; }
.rc-game-best { font-size: 1.55rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }

/* Outlined where the chips beside the picker are filled, and outlined again
   against the panel's filled Start button. Two solid warm things side by
   side are exactly the pair that collapses under a red-green deficiency, and
   a shape difference carries in every theme without per-theme tuning. */
.rc-lead-pill { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 99px; border: 1px solid var(--rc-spark); color: var(--rc-spark); font-size: .72rem; font-weight: 700; }

.rc-src-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* min-width: 0 is the load-bearing part. A flex item will not shrink below
   its min-content width, and a <select>'s min-content width is its LONGEST
   option — "Food and Beverage Service and Housekeeping" is wider than a
   phone. Without the floor the picker refused to shrink, the row grew to
   fit it, and the whole Play page scrolled sideways with its left edge cut
   off. With it the picker takes the row and the long name truncates inside
   the control, which is what a select does everywhere else. */
.rc-src-row .form-select { flex: 1 1 240px; width: auto; min-width: 0; max-width: 100%; }
.rc-src-chip { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; border-radius: 99px; background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-size: .78rem; font-weight: 700; white-space: nowrap; }

/* The source picker: a search box with its list hung under it. Every option
   is in the DOM and the search only hides them, so the list is tall and
   capped rather than paged. */
.rc-picker { position: relative; flex: 1 1 260px; min-width: 0; max-width: 100%; }
.rc-picker-input { padding-right: 38px; }
.rc-picker-caret {
    position: absolute; top: 0; right: 0; width: 38px; height: 100%;
    border: 0; background: transparent; color: var(--rc-muted);
    display: inline-flex; align-items: center; justify-content: center;
}
.rc-picker-menu {
    position: absolute; z-index: 1030; top: calc(100% + 4px); left: 0; right: 0;
    max-height: 320px; overflow-y: auto; padding: 6px;
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 12px;
    box-shadow: 0 10px 30px rgba(22, 48, 43, .16);
}
.rc-picker-group {
    padding: 8px 10px 4px; font-size: .72rem; font-weight: 800; letter-spacing: .04em;
    text-transform: uppercase; color: var(--rc-muted);
}
.rc-picker-opt {
    display: block; width: 100%; text-align: left; border: 0; background: transparent;
    color: var(--rc-text); padding: 8px 10px; border-radius: 8px; font-size: .92rem;
}
.rc-picker-opt:hover, .rc-picker-opt.is-active { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-picker-chips {
    position: sticky; top: -6px; z-index: 1; display: flex; gap: 6px; flex-wrap: wrap;
    padding: 6px 4px 8px; margin: -6px -6px 0; background: var(--rc-card);
    border-bottom: 1px solid var(--rc-border);
}
/* Sized like .rc-filter, because that is what these are: the row narrows the
   list under it, so it is a control and not a caption. At 4px of padding and
   .78rem they stood 26px tall — smaller than anything else on the screen,
   and the first thing a learner has to hit after opening the picker. */
.rc-picker-chip {
    display: inline-flex; align-items: center;
    min-height: 38px; padding: 0 14px;
    border: 1px solid var(--rc-border); background: transparent; color: var(--rc-muted);
    border-radius: 99px; font-size: .875rem; font-weight: 700; line-height: 1;
}
.rc-picker-chip.is-on { background: var(--rc-accent-fill); border-color: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-picker-empty { margin: 0; padding: 10px; color: var(--rc-muted); font-size: .9rem; }
.rc-picker-widen { display: block; margin-top: 6px; border: 0; background: transparent; padding: 0; font-weight: 700; color: var(--rc-accent-dark); text-decoration: underline; }

/* Both are given `hidden` by the filter, and both set `display` — which beats
   the browser's own [hidden] rule. Same trap as the install tips. */
.rc-picker-opt[hidden], .rc-picker-sect[hidden] { display: none; }

/* Theme picker */
.rc-theme-opt { display: flex; align-items: center; gap: 12px; width: 100%; padding: 12px 14px; border: 2px solid var(--rc-border); border-radius: 14px; background: var(--rc-card); color: var(--rc-text); text-align: left; }
.rc-theme-opt.active { border-color: var(--rc-accent); }
/* Scoped to the theme button: bare, these two reached the colour swatches in
   every ⋯ menu and shrank each colour to an 18px chip adrift in its square. */
.rc-theme-opt .rc-swatches { display: flex; gap: 4px; }
.rc-theme-opt .rc-swatches span { width: 18px; height: 18px; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .08); }

/* Feedback */
.rc-fab {
    position: fixed; right: 14px; bottom: calc(var(--rc-tabbar-h) + 14px); z-index: 1025;
    width: 48px; height: 48px; border-radius: 50%; border: 0;
    background: var(--rc-card); color: var(--rc-accent-dark);
    box-shadow: 0 4px 16px rgba(22, 48, 43, .18); font-size: 1.25rem;
    transition: opacity .16s ease, visibility 0s;
}
/* The corner this sits in is not empty. On a phone every card's action row
   ends flush against the right edge, and a sweep of six screens at 360px
   found the button on top of a library tile's Challenge icon (67% of it),
   a note's ⋯ menu (84%) and Settings' Show password (70%) — each of them
   the only way to reach what it does, so the tap opened feedback instead.

   recall.js takes the button away when the spot it occupies is somebody
   else's. `visibility` is what makes that a fix rather than a fade: a
   transparent button still swallows the tap, and still answers the Tab
   key. It is held back until the fade finishes so the button does not just
   vanish, and dropped at once on the way in.

   A fade and nothing else. Sliding it out would read a little better and
   would cost the fix: recall.js has to measure where the button rests to
   decide any of this, and a transitioning `transform` means the box it
   measures is wherever the animation had got to — 14px low and a sixth too
   small, which is a box the icon underneath no longer overlaps. It decided
   it was in the clear every time, which is how it stayed on top of the one
   control this is all about. Opacity moves nothing. */
.rc-fab.is-away { opacity: 0; visibility: hidden; transition-delay: 0s, .16s; }
@media (prefers-reduced-motion: reduce) {
    .rc-fab { transition-duration: 0s; }
    .rc-fab.is-away { transition-delay: 0s; }
}

/* A page head that stays put, for a shelf that runs to tens of thousands of
   pixels. Sticky under the top bar on the same reasoning as
   .rc-whatsnew-bar, carrying the page background so the rows do not scroll
   through it, and below the bar's z-index for the same reason.

   The Back to top button lives in here rather than floating in a corner,
   and that was the second attempt. A fixed circle was tried first and put
   on the admin shelf it covered a row's "Add to their sets" toggle -- the
   same trap .rc-fab documents above, on the opposite side. These rows leave
   no corner free: a drag handle at the left edge, Cards and the menu at the
   right, two toggles along the bottom. recall.js's dodging is not the answer
   either, because its own comment records that on a grid of tiles it is away
   at almost every scroll stop, and a way out that is usually missing is not
   a way out. */
.rc-page-head-sticky {
    position: sticky; top: var(--rc-topbar-h); z-index: 1010;
    margin: -16px -14px 12px; padding: 12px 14px;
    background: var(--rc-bg); border-bottom: 1px solid var(--rc-border);
}
/* Kept in the layout while hidden, so the head does not jump the moment it
   appears -- and hidden by visibility, not opacity alone, so it is not a tab
   stop while it would do nothing. */
.rc-totop { visibility: hidden; opacity: 0; transition: opacity .16s ease, visibility 0s .16s; }
.rc-totop.is-shown { visibility: visible; opacity: 1; transition-delay: 0s, 0s; }
@media (prefers-reduced-motion: reduce) {
    .rc-totop { transition-duration: 0s; }
    .rc-totop.is-shown { transition-delay: 0s; }
}

/* A list of answers takes the corner outright, without asking recall.js.
   Options are full-width buttons in a column, so one of them is always at
   the right edge wherever the button rests, and on a phone the button was
   photographed sitting on top of "C · Taal" with a timer running. The
   measuring in recall.js should have caught that and evidently did not in
   the moment it mattered; a tapped answer is not something to leave to a
   heuristic that re-runs on a settle timer.

   Every screen that asks a question this way is covered by the one hook:
   review, a challenge, Speed Quiz and Survival all render .rc-choice. The
   other games' tiles and letters are buttons too, so the measuring still
   looks after those; this only removes the case it was demonstrably
   losing. Feedback comes back the moment the options do not exist — after
   grading, and on every other screen — and Settings carries a Send feedback
   card besides.

   Matching .is-away rather than `display: none`: recall.js reads the
   button's resting box even while it is hidden, and a display-none button
   has no box to read.

   Below lg, which is the same width recall.js measures at and for its
   reason: the desktop layout is a centred column with empty gutters, so the
   corner is nobody else's there and taking feedback away would cost
   something for nothing. */
@media (max-width: 991.98px) {
    body:has(.rc-choice) .rc-fab { opacity: 0; visibility: hidden; pointer-events: none; }
}
/* There was a rule here lifting the button above the review screen's answer
   bar. The button is no longer drawn on a focused screen at all — the review
   is one, and so are both editors and the reader — so the answer bar has the
   bottom edge to itself and the rule had nothing left to move. The dodging
   above still earns its keep on every screen you browse from. */
@media (min-width: 992px) { .rc-fab { bottom: 24px; right: 24px; } }
.rc-stars { display: flex; gap: 6px; justify-content: center; }
/* Empty stars must sit on --rc-muted, not --rc-border: the border colour is
   ~1.1:1 against the modal background, so the rating control looked empty. */
.rc-stars button { border: 0; background: none; font-size: 2rem; color: var(--rc-muted); padding: 0 2px; line-height: 1; }
.rc-stars button:hover, .rc-stars button:focus-visible { color: var(--rc-spark); }
.rc-stars button.on { color: var(--rc-spark); }
/* Inline variant for the feedback form, where 2rem stars overpower the field. */
.rc-stars-sm { gap: 2px; }
.rc-stars-sm button { font-size: 1.4rem; }
/* Screenshot thumbnail on a bug report, in the form and in the admin list. */
.rc-fb-shot { max-height: 72px; max-width: 120px; border-radius: 8px; border: 1px solid var(--rc-border); object-fit: cover; }

/* Toasts */
.rc-toasts { position: fixed; left: 50%; transform: translateX(-50%); bottom: calc(var(--rc-tabbar-h) + 20px); z-index: 1090; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; }
/* From 992px up the toast moves to the TOP of the screen, under the bar.

   A confirmation belongs near the control that caused it. On a desktop
   settings page the switch is at the top and the toast was landing roughly
   630px below it — outside the eye's focus while you look at the thing you
   just changed, and a neutral dark pill is badly caught in peripheral
   vision anyway. On a phone the bottom is still right: that is where the
   thumb is, and the screen is short enough that all of it is in view.

   bottom: auto is required, not tidiness — leaving it set would pin the
   element top AND bottom and stretch it the height of the screen. */
@media (min-width: 992px) {
    .rc-toasts { top: calc(var(--rc-topbar-h) + 24px); bottom: auto; }
}
/* Bigger than it was (10px/.92rem): this is read at a glance from across
   the page, in the moment after a click, and then it is gone. */
.rc-toast { background: var(--rc-text); color: var(--rc-bg); padding: 12px 20px; border-radius: 99px; font-weight: 600; font-size: 1rem; box-shadow: 0 8px 24px rgba(0, 0, 0, .24); display: flex; align-items: center; gap: 8px; }
.rc-toast.xp { background: var(--rc-accent); }
.rc-toast.xp .bi { color: var(--rc-spark); }
/* In from below and out the same way. These used to appear and vanish with
   nothing in between — the fade came from an Alpine transition that could
   only ever run on the way in, because the element it was bound to was
   never hidden, only removed. A confirmation that blinks out of existence
   reads as a glitch rather than as an answer.

   The exit is driven by an is-leaving class rather than by Alpine, so the
   node is still in the DOM while it plays; rcToasts() sets it, waits, and
   then drops the item. */
@media (prefers-reduced-motion: no-preference) {
    /* Raised well past the study card's settings on purpose. A toast is seen
       a handful of times in a session rather than forty, so it carries none of
       the repetition cost that keeps the card restrained — and it is the one
       most likely to be missed entirely, being small and at the edge of the
       screen. The exit duration is coupled to the 240ms wait in rcToasts():
       change one and the node is dropped mid-animation or sits done and
       waiting. */
    .rc-toast { animation: rc-toast-in .3s cubic-bezier(.2, .8, .3, 1); }
    .rc-toast.is-leaving { animation: rc-toast-out .24s ease-in forwards; }
    @keyframes rc-toast-in {
        from { opacity: 0; transform: translateY(24px) scale(.92); }
        to   { opacity: 1; transform: none; }
    }
    @keyframes rc-toast-out {
        to { opacity: 0; transform: translateY(14px) scale(.94); }
    }
}
/* Without motion the exit class still has to hide it, or a toast whose item
   is removed 180ms later would sit at full opacity until it went. */
@media (prefers-reduced-motion: reduce) {
    .rc-toast.is-leaving { opacity: 0; }
}

/* Docked at the top, it has to arrive from above — the default keyframes
   rise from below, which reads as the toast falling upwards into place.
   After the reduced-motion blocks on purpose: those set `animation` in full
   at equal specificity, so anything earlier is overwritten. */
@media (min-width: 992px) and (prefers-reduced-motion: no-preference) {
    .rc-toast { animation-name: rc-toast-in-top; }
    .rc-toast.is-leaving { animation-name: rc-toast-out-top; }
    @keyframes rc-toast-in-top {
        from { opacity: 0; transform: translateY(-24px) scale(.92); }
        to   { opacity: 1; transform: none; }
    }
    @keyframes rc-toast-out-top {
        to { opacity: 0; transform: translateY(-14px) scale(.94); }
    }
}

/* Admin */
.rc-bars { display: flex; align-items: flex-end; gap: 3px; height: 90px; }
.rc-bar { flex: 1; background: var(--rc-accent); border-radius: 4px 4px 0 0; min-height: 2px; position: relative; }
.rc-bar[data-v]:hover::after { content: attr(data-v); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); font-size: .7rem; background: var(--rc-text); color: var(--rc-bg); padding: 2px 6px; border-radius: 6px; white-space: nowrap; }
.rc-bar-labels { display: flex; justify-content: space-between; font-size: .7rem; color: var(--rc-muted); margin-top: 4px; }
.table { --bs-table-border-color: var(--rc-border); --bs-table-bg: transparent; }

/* Guest */
/* "safe" is load-bearing, not decoration. A centred flex column whose content
   is taller than the viewport overflows at BOTH ends, and the half above the
   top edge cannot be scrolled to — on a laptop the Create account form is
   858px against a 720px viewport, and the logo was simply cut off. "safe"
   drops to flex-start rather than centring once it would overflow; a browser
   too old to know the keyword ignores the declaration and starts at the top,
   which is the same outcome. */
.rc-guest-main { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: safe center; padding: 28px 16px; }
.rc-guest-brand { text-align: center; margin-bottom: 20px; }
.rc-guest-brand h1 { font-size: 1.8rem; font-weight: 800; margin: 10px 0 2px; letter-spacing: -.02em; }
.rc-guest-brand p { color: var(--rc-muted); margin: 0; }
.rc-guest-card { width: 100%; max-width: 420px; background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 20px; padding: 24px 20px; box-shadow: 0 8px 30px rgba(22, 48, 43, .08); }
.rc-guest-card h2 { margin-bottom: 16px; }
/* line-height, not a margin, because the two lines are separated by a <br>
   inside one paragraph and a margin cannot reach between them. At the
   inherited 1.5 the baselines came out 22px apart, which read as one block of
   underlined text rather than two things you could choose between. */
.rc-guest-foot { text-align: center; margin: 22px 0 0; color: var(--rc-muted); font-size: .92rem; line-height: 2; }

/* A six-digit code, typed once and never read back as prose. Tabular figures
   so the digits sit in a fixed grid and a mistyped one is obvious, and enough
   letter-spacing that "11" cannot be read as "H". Centred because there is
   nothing else in the box to line up against. */
.rc-otp {
    text-align: center; letter-spacing: .38em; text-indent: .38em;
    font-variant-numeric: tabular-nums; font-weight: 700;
}
/* The placeholder is six zeroes, which at the tracking above reads as a
   filled-in answer rather than as a hint. */
.rc-otp::placeholder { letter-spacing: .38em; opacity: .35; font-weight: 400; }

/* Set below the footer and smaller than it, for the way back in that almost
   nobody needs. Given equal billing beside "Forgot your password?" the two
   competed, and the common one got harder to find.

   The rule is doing the demoting that size alone could not: three centred,
   underlined links in a stack are a wall whatever their point size, and a
   hairline turns the last one into a footnote under the other two rather than
   a third thing of equal weight. */
.rc-guest-aside {
    text-align: center; font-size: .84rem;
    margin: 18px 0 0; padding-top: 16px;
    border-top: 1px solid var(--rc-border);
}
.rc-guest-aside a { color: var(--rc-muted); }

/* The column that was the main element's own contents until the pitch panel
   needed something to sit beside. On its own it lays out exactly as before. */
.rc-guest-col { width: 100%; display: flex; flex-direction: column; align-items: center; }

/* Hidden here and shown at 992px, not the reverse: it exists to fill a half
   that only the desktop layout has. A phone has no empty half — the form is
   the whole screen — so the markup ships and simply never draws.

   The mock is gated a second time, on the viewport's HEIGHT, and starts off
   for the same reason. */
.rc-guest-pitch,
.rc-guest-pitch-mock { display: none; }

@media (min-width: 992px) {
    /* Row, and the vertical guard moves with it. In a column `justify-content:
       safe center` was what kept a form taller than the viewport from
       overflowing past the top edge where it cannot be scrolled to; across a
       row that job belongs to align-items, so the keyword goes there too. A
       browser that does not know `safe` ignores the declaration and starts at
       the top, which is the same outcome. */
    .rc-guest-main.has-pitch {
        flex-direction: row;
        align-items: safe center;
        justify-content: safe center;
        gap: 56px;
        /* The vertical padding stays at the base layout's 28px. Widening it to
           40px here cost 24px that a 675px-tall window did not have, and put a
           scrollbar on a sign-in page that had fit on one screen before the
           panel arrived. Only the horizontal padding grows. */
        padding: 28px 32px;
    }
    .rc-guest-main.has-pitch .rc-guest-col { width: auto; flex: 0 0 420px; }

    /* The tagline is the pitch panel's headline from here up, and printing it
       again under the mark six inches away reads as a mistake. The <h1> it
       sits under stays, so the heading outline is the same at every width. */
    .rc-guest-main.has-pitch .rc-guest-brand p { display: none; }

    /* And the mark itself goes for the same reason: the pitch panel already
       draws one, so this layout was showing the brand twice, side by side, a
       few inches apart. The "Recall" wordmark stays above the card — losing
       the picture, not the name, and with it the <h1>. */
    .rc-guest-main.has-pitch .rc-guest-brand .rc-logo { display: none; }

    .rc-guest-pitch { display: block; flex: 0 1 480px; }
    .rc-guest-pitch-title {
        font-size: clamp(1.9rem, 2.6vw, 2.5rem); font-weight: 800;
        line-height: 1.08; letter-spacing: -.03em;
        margin: 10px 0 14px; text-wrap: balance;
    }
    .rc-guest-pitch-title span { color: var(--rc-accent); }
    .rc-guest-pitch-lead { color: var(--rc-muted); font-size: 1.02rem; max-width: 30em; margin-bottom: 26px; }
    /* Smaller than the landing hero's, which has a full page to sit in. */
    .rc-guest-pitch-mock .rc-land-screen { max-width: 320px; margin: 0; }
}

/* The mock costs 410px of height, and a wide window is not necessarily a tall
   one: at 1390x675 — an ordinary laptop with browser chrome — the panel came
   to 668px, hung 33px past the fold and grew a scrollbar on a page that is
   meant to be one screen. This is the same trap the `safe` keyword on
   .rc-guest-main guards against, arriving from the other side: `safe` keeps
   the TOP reachable, and nothing was keeping the bottom on screen.

   Height, not width, because height is what it runs out of, and a second
   media query rather than a shorter mock because there is no size at which a
   phone drawn 200px tall is worth looking at. The words carry the panel on
   their own at ~250px, so a short window keeps the pitch and loses only the
   picture. */
@media (min-width: 992px) and (min-height: 800px) {
    .rc-guest-pitch-mock { display: block; }
}

/* The other side of that same threshold. Below it the mock is gone, but the
   column beside it still has to fit, and it very nearly does not: at 675px
   the stack came to 720px and scrolled. Rather than keep shaving individual
   numbers every time the card grows a line, the whole rhythm tightens once
   here — which is exactly the situation the room is short. */
@media (min-width: 992px) and (max-height: 799.98px) {
    .rc-guest-main.has-pitch { padding-top: 20px; padding-bottom: 20px; }
    .rc-guest-main.has-pitch .rc-guest-brand { margin-bottom: 12px; }
}

/* ---- Challenges -------------------------------------------------------- */

/* The line that replaced the standing explanation. What is on this player
   leads it, in the accent; the rest is a tally. */
.rc-ch-summary { color: var(--rc-muted); font-size: .9rem; margin: -6px 0 16px; }
.rc-ch-onyou { color: var(--rc-accent-dark); }
.rc-ch-qs { font-size: .8rem; color: var(--rc-muted); }

/* A duel, led by its scoreboard. The one waiting on this player is ringed:
   the state chip says so in words, and the ring says it from across the room
   — two channels, because a chip alone is easy to read past. */
.rc-duel {
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 16px;
    padding: 12px 14px; margin-bottom: 10px;
}
.rc-duel.is-yours { border-color: var(--rc-accent); box-shadow: 0 0 0 2px rgba(var(--rc-accent-rgb), .12); }
.rc-duel-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rc-duel-body { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin: 10px 0 12px; }
.rc-duel-say { font-size: .92rem; }
.rc-duel-say b { display: block; }
.rc-duel-series { font-size: .82rem; color: var(--rc-muted); }
.rc-duel-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.rc-score { display: flex; align-items: center; gap: 16px; }
.rc-score-side { text-align: center; min-width: 58px; }
.rc-score-n { font-size: 1.8rem; font-weight: 800; line-height: 1; }
/* A run that has not happened is a dash, not a zero: zero is a score. */
.rc-score-n.is-none { color: var(--rc-muted); }
.rc-score-who { font-size: .76rem; color: var(--rc-muted); font-weight: 600; margin-top: 3px; }
.rc-score-v { font-size: .8rem; color: var(--rc-muted); font-weight: 700; }

/* The four states a duel can be in. They differ by lightness and by shape,
   never by hue alone: "your turn" is a solid fill, "waiting" is outlined in
   warn, "unclaimed" is a dashed outline, "decided" is the pale fill. That
   survives both kinds of red-green deficiency without per-theme tuning —
   the same reasoning as the exam pill on a card set's tile. */
.rc-state {
    display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border-radius: 99px;
    font-size: .75rem; font-weight: 700;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
}
.rc-state.is-yours { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-state.is-waiting { background: var(--rc-warn-pale); color: var(--rc-warn); border: 1px solid var(--rc-warn); padding: 1px 8px; }
.rc-state.is-unclaimed { background: transparent; color: var(--rc-muted); border: 1px dashed var(--rc-border); padding: 1px 8px; }

/* A shared link: what it is on one line, what you can do with it on the next.
   The URL is not one of them until it is asked for — copying is the thing
   people came to do, and a read-only field the width of the row to support
   the rare case was the loudest element on the screen.

   Named rc-link-*, not rc-ch-row: that one is already the leaderboard's row,
   on the challenge board and in Survival, and a second meaning for it here
   restyled both of those from underneath. */
.rc-link-row {
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 16px;
    padding: 12px 14px; margin-bottom: 10px;
}
.rc-link-row.is-closed { opacity: .75; }
.rc-link-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rc-link-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.rc-link-scores { font-size: .82rem; color: var(--rc-muted); margin-left: auto; }
.rc-ch-reveal { --bs-btn-color: var(--rc-muted); --bs-btn-hover-color: var(--rc-accent-dark); font-size: .82rem; text-decoration: underline dotted; }

/* The same offer the intro card makes, once there is anything on the screen
   for it to sit under. */
.rc-ch-prompt {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    border: 1px dashed var(--rc-accent); border-radius: 14px; padding: 10px 14px;
    color: var(--rc-accent-dark); font-size: .92rem;
}

.rc-ch-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rc-border); }
.rc-ch-line.is-past { font-size: .88rem; opacity: .8; }
.rc-ch-line.is-past a { color: var(--rc-muted); }
.rc-ch-scores { font-size: .8rem; color: var(--rc-muted); }
.rc-ch-history { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--rc-border); }
.rc-ch-start { margin: 4px 0 8px; }
.rc-ch-note { color: var(--rc-muted); font-size: .9rem; }
/* The ask made to a guest holding a score. In the accent wash so it reads as
   part of the result rather than as an advert under it, and the button on
   its own row below 480px so neither the sentence nor the button squeezes. */
.rc-ch-keep {
    display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
    margin: 16px 0 4px; padding: 14px 16px;
    background: var(--rc-accent-pale); border: 1px solid var(--rc-border); border-radius: 14px;
}
.rc-ch-keep > div { flex: 1 1 16rem; min-width: 0; }
.rc-ch-keep b { display: block; margin-bottom: 2px; }
.rc-ch-head { display: flex; justify-content: space-between; align-items: center; min-height: 52px; font-size: .82rem; color: var(--rc-muted); margin-bottom: 8px; }
/* Larger than the quiz dial and in the accent rather than muted grey: this
   one sits in a header row rather than centred on the card, where a 40px
   ring in --rc-muted was the least noticeable thing on screen. */
.rc-ch-timer { width: 52px; height: 52px; color: var(--rc-accent-dark); }
.rc-ch-timer span { font-size: 1.15rem; }
/* The board: rank, name, then the two numbers it is sorted by. */
.rc-ch-row { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-bottom: 1px solid var(--rc-border); font-size: .92rem; }
.rc-ch-row.is-you { background: var(--rc-accent-pale); border-radius: 10px; border-bottom-color: transparent; font-weight: 600; }
.rc-ch-rank { flex: none; width: 22px; color: var(--rc-muted); font-variant-numeric: tabular-nums; }
.rc-ch-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.rc-ch-score { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; }
.rc-ch-time { flex: none; width: 56px; text-align: right; color: var(--rc-muted); font-variant-numeric: tabular-nums; }
/* A duel's third column carries a status ("waiting", "hidden") as often as
   a time, and the board's fixed 56px shreds a word into three lines. Only
   two rows are ever shown here, so the column can size to its content. */
.rc-ch-row.is-duel .rc-ch-time { width: auto; white-space: nowrap; }
.rc-ch-row.is-duel .rc-ch-name { display: flex; align-items: center; gap: 6px; }
.rc-ch-won { color: var(--rc-accent); font-size: .82rem; }

/* A shared note is a document, not a form: it starts at the top of the page
   rather than being centred in it, and it gets room for a reading measure.
   The colour the owner gave it comes along as the card's top edge. */
.rc-guest-main.is-reading { justify-content: flex-start; padding: 24px 16px 40px; }
.rc-guest-card.is-reading {
    max-width: 760px; padding: 26px 22px 22px;
    border-top: 3px solid var(--rc-nc, var(--rc-accent));
    border-color: var(--rc-nc-edge, var(--rc-border));
    border-top-color: var(--rc-nc, var(--rc-accent));
}
@media (min-width: 768px) { .rc-guest-card.is-reading { padding: 34px 40px 26px; } }
/* The measure is the point; the card is already narrow enough not to fight it. */
.rc-guest-card.is-reading .rc-read-body { max-width: 66ch; }
.rc-guest-card.is-reading .rc-read-title { font-size: 1.9rem; }

/* A shared card set. The questions are the document, so they get hairline
   rows and reading-sized type rather than a bordered list-group. */
.rc-share-lead { font-size: 1.05rem; line-height: 1.6; color: var(--rc-muted); max-width: 62ch; margin: 0 0 18px; }
/* The one thing on this page there is to do, rather than to read. */
.rc-share-cta {
    display: flex; align-items: center; gap: 14px; margin: 0 0 20px;
    padding: 14px 16px; border-radius: 14px; text-decoration: none;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border: 1px solid transparent;
}
.rc-share-cta:hover { border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-share-cta-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rc-share-cta-title { font-weight: 700; }
.rc-share-cta-sub { font-size: .85rem; opacity: .85; }
.rc-share-cta .bi-arrow-right { margin-left: auto; font-size: 1.1rem; }

.rc-share-cards { border-top: 1px solid var(--rc-border); }
.rc-share-card { border-bottom: 1px solid var(--rc-border); }
.rc-share-q {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
    width: 100%; padding: 14px 2px; border: 0; background: transparent;
    color: var(--rc-text); font-size: 1rem; font-weight: 600; line-height: 1.45; text-align: left;
}
.rc-share-q:hover { color: var(--rc-accent-dark); }
.rc-share-q .bi { color: var(--rc-muted); font-size: .9rem; margin-top: 3px; }
.rc-share-a { padding: 0 2px 16px; color: var(--rc-muted); font-size: .95rem; line-height: 1.6; }
/* The answer reads as the answer, not as one more choice in the list. */
.rc-share-answer { margin-top: 8px; color: var(--rc-accent-dark); font-weight: 600; white-space: pre-wrap; }
.rc-share-answer.rc-md { font-weight: 400; color: var(--rc-text); }
.rc-share-list { margin: 8px 0 0; padding-left: 1.25em; }
.rc-share-list li { margin-bottom: .25em; }
/* The answers, once shown, read as answers rather than as more choices. */
.rc-share-list.is-answer { color: var(--rc-accent-dark); font-weight: 600; }
.rc-share-a .rc-explain { margin-top: 10px; }

/* Utilities */
.rc-hide-scroll::-webkit-scrollbar { display: none; }
.rc-scroll-x { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; -webkit-overflow-scrolling: touch; }
.rc-scroll-x > * { flex: none; }
.rc-truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.rc-mono-sm { font-family: ui-monospace, monospace; font-size: .82rem; }
.rc-outdated { background: var(--rc-warn-pale); color: var(--rc-warn); }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }

/* ==========================================================================
   Landing page (guests only). Same tokens as the app; larger type, more air.
   ========================================================================== */
.rc-landing { background: var(--rc-bg); }
.rc-land-nav {
    display: flex; align-items: center; justify-content: space-between;
    max-width: 1100px; margin: 0 auto; padding: 14px 18px;
}
.rc-land-hero {
    max-width: 1100px; margin: 0 auto; padding: 28px 18px 40px;
    display: grid; gap: 32px; align-items: center;
}
.rc-land-eyebrow { font-size: .75rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--rc-accent-dark); margin-bottom: 10px; }
.rc-land-hero h1 { font-size: clamp(2rem, 8vw, 3.6rem); font-weight: 800; line-height: 1.05; letter-spacing: -.03em; margin: 0 0 16px; text-wrap: balance; }
.rc-land-hero h1 span { color: var(--rc-accent); }
.rc-land-lead { font-size: 1.1rem; color: var(--rc-muted); max-width: 32em; margin-bottom: 20px; }
.rc-land-cta { display: flex; flex-wrap: wrap; gap: 10px; }
.rc-land-cta .btn { flex: 1 1 auto; }
/* Free, no ads, no AI — directly under the buttons, where the decision is
   being made. Ticks rather than a sentence: three separate promises read as
   three, and as a grey line of prose they read as one aside nobody finishes. */
.rc-land-promise {
    list-style: none; padding: 0; margin: 18px 0 0;
    display: flex; flex-wrap: wrap; gap: 8px 18px;
    font-size: .88rem; font-weight: 600; color: var(--rc-text);
}
.rc-land-promise li { display: inline-flex; align-items: center; gap: 6px; }
.rc-land-promise .bi { color: var(--rc-accent); font-size: 1rem; }
.rc-land-fine { font-size: .82rem; color: var(--rc-muted); margin: 12px 0 0; }

/* A study card drawn with the app's own components, framed as a phone. */
.rc-land-phone { display: flex; justify-content: center; }
.rc-land-screen {
    width: 100%; max-width: 360px;
    background: var(--rc-bg); border: 1px solid var(--rc-border); border-radius: 28px;
    padding: 18px 16px 20px;
    box-shadow: 0 30px 60px -30px rgba(22, 48, 43, .35), 0 0 0 6px var(--rc-card), 0 0 0 7px var(--rc-border);
}
.rc-land-bar { display: flex; justify-content: space-between; font-size: .8rem; color: var(--rc-muted); margin-bottom: 8px; }
.rc-land-keys { display: flex; justify-content: center; gap: 8px; margin-top: 14px; }

/* Sections alternate plain / tinted down the page.

   The two grounds are only ~2.4 dL* apart, which is this file's rule for a
   surface text sits on for an hour and exactly right inside the app. Across
   3,600px of landing page the job is not reading, it is knowing where you
   are — and only one section was tinted, so the whole page scrolled as one
   undifferentiated sheet. Alternating is what makes that difference do the
   work, rather than reaching for a stronger colour and breaking the range.

   --rc-land-inset is the reason a section can be flipped by adding or
   removing one class: the cards inside it read this rather than naming a
   ground of their own, so they invert to stay visible against whichever
   band they landed on. Never hard-code a background on one of them. */
.rc-land-section { --rc-land-inset: var(--rc-card); max-width: 1100px; margin: 0 auto; padding: 52px 18px; }
.rc-land-section h2 { font-size: clamp(1.5rem, 4.5vw, 2.1rem); font-weight: 800; letter-spacing: -.02em; margin-bottom: 8px; text-wrap: balance; }
.rc-land-sub { color: var(--rc-muted); font-size: 1.05rem; max-width: 40em; margin-bottom: 24px; }
.rc-land-alt {
    --rc-land-inset: var(--rc-bg);
    max-width: none; background: var(--rc-card);
    border-top: 1px solid var(--rc-border); border-bottom: 1px solid var(--rc-border);
}
/* Doubled up on purpose: `.rc-land-section.rc-land-alt` outweighs a plain
   single-class rule further down the file, and one of those is a list reset.
   `.rc-land-loop { margin: 0 }` sits thirteen lines below this and, at equal
   specificity, was winning — so the heading and the subtitle centred in the
   1100px column while the cards sat hard left with the whole 460px of slack
   on the right. The section still looked deliberate, which is why it went
   unnoticed: nothing is missing, one block is just 219px out of line. */
.rc-land-section.rc-land-alt > * { max-width: 1100px; margin-left: auto; margin-right: auto; }

/* Sign in is a link on a phone and a button from 576px up. Three buttons and
   the wordmark do not fit at 320px, and the one that had to give way was
   Create account — the only one a first-time visitor can use. Full row height
   whatever it looks like, so it is still a thumb's target. */
.rc-land-signin {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0 8px;
    color: var(--rc-accent-dark); font-size: .875rem; font-weight: 700; text-decoration: none;
    border-radius: 8px;
}
.rc-land-signin:hover { color: var(--rc-accent-dark); background: var(--rc-accent-pale); }

.rc-land-loop { list-style: none; padding: 0; margin: 0; display: grid; gap: 14px; counter-reset: step; }
.rc-land-loop li { position: relative; background: var(--rc-land-inset); border: 1px solid var(--rc-border); border-radius: 18px; padding: 20px 18px 18px 18px; counter-increment: step; }
.rc-land-loop li::before { content: counter(step); position: absolute; top: 16px; right: 16px; font-size: .75rem; font-weight: 800; color: var(--rc-accent-dark); background: var(--rc-accent-pale); width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.rc-land-loop .bi { font-size: 1.6rem; color: var(--rc-accent); }
.rc-land-loop h3 { font-size: 1.05rem; margin: 8px 0 4px; }
.rc-land-loop p { margin: 0; color: var(--rc-muted); font-size: .95rem; }
.rc-land-loop code { background: var(--rc-accent-pale); padding: 1px 5px; border-radius: 5px; }

/* ---- Three shown, the rest listed ---------------------------------------

   Twelve cards of identical weight was a wall: nothing to enter it by, and
   the one thing no other app does looked exactly like the export format.
   Three now take a row of their own with a picture beside them, and the
   picture changes sides each time so three in a row read as three things
   rather than as a list that happens to have illustrations. */
.rc-land-show { display: grid; gap: 22px; align-items: center; margin-bottom: 40px; }
.rc-land-show-copy .bi { font-size: 1.7rem; color: var(--rc-accent); }
.rc-land-show-copy h3 { font-size: 1.3rem; font-weight: 800; letter-spacing: -.01em; margin: 10px 0 6px; }
.rc-land-show-copy p { margin: 0; color: var(--rc-muted); font-size: 1rem; line-height: 1.6; }

/* The frame the mock partials sit in. It takes the band's own inset ground,
   and the app components inside it invert so they read against it — the same
   trick the bands themselves use, one level further in. Without that, cards
   drawn on --rc-card sitting on a panel of --rc-card lose their edges. */
.rc-land-panel {
    background: var(--rc-land-inset); border: 1px solid var(--rc-border); border-radius: 18px;
    padding: 14px; box-shadow: 0 24px 44px -32px rgba(22, 48, 43, .4);
}
.rc-land-panel .rc-cand,
.rc-land-panel .rc-duel,
.rc-land-panel .rc-brick { background: var(--rc-bg); }
.rc-land-panel .rc-land-bar { margin-bottom: 12px; }
.rc-land-panel-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.rc-land-panel-note { font-size: .8rem; color: var(--rc-muted); margin: 0; }
.rc-land-tick { color: var(--rc-accent-dark); font-size: .95rem; }
.rc-land-cand-q { font-size: .9rem; font-weight: 600; line-height: 1.4; }
.rc-land-cand-a { font-size: .85rem; color: var(--rc-muted); margin-top: 3px; }

/* The notes wall: the folder tree beside a handful of coloured cards. */
.rc-land-notes { display: grid; gap: 12px; grid-template-columns: minmax(0, 1fr); }
.rc-land-tree { display: flex; flex-direction: column; gap: 2px; font-size: .82rem; }
.rc-land-tree-row { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: 9px; color: var(--rc-muted); }
.rc-land-tree-row b { margin-left: auto; font-weight: 700; }
.rc-land-tree-row.is-on { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-land-tree-row.is-child { padding-left: 24px; }
.rc-land-wall { display: grid; gap: 8px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rc-land-wall .rc-brick { padding: 9px 11px; gap: 4px; border-radius: 12px; }
.rc-land-wall .rc-brick-title { font-size: .84rem; padding-right: 0; }
.rc-land-wall .rc-brick-excerpt { font-size: .74rem; -webkit-line-clamp: 2; }
.rc-land-wall .rc-tag { font-size: .66rem; }

/* The other nine. One line each is all any of them needed. */
.rc-land-more-head { font-size: 1.05rem; font-weight: 800; margin: 0 0 4px; }
.rc-land-more { list-style: none; padding: 0; margin: 0; display: grid; gap: 0 30px; }
.rc-land-more li { display: flex; gap: 12px; padding: 14px 0; border-top: 1px solid var(--rc-border); }
.rc-land-more .bi { font-size: 1.15rem; color: var(--rc-accent); margin-top: 1px; }
.rc-land-more b { display: block; font-size: .95rem; margin-bottom: 2px; }
.rc-land-more span { color: var(--rc-muted); font-size: .9rem; line-height: 1.55; }

.rc-land-two { display: grid; gap: 28px; align-items: start; }
.rc-land-two p { color: var(--rc-muted); font-size: 1.02rem; }
.rc-land-timeline { background: var(--rc-land-inset); border: 1px solid var(--rc-border); border-radius: 18px; padding: 18px; display: grid; gap: 10px; }
.rc-land-timeline > div:not(.rc-land-timeline-note) { display: flex; align-items: center; gap: 12px; }
.rc-land-timeline span { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .78rem; color: var(--rc-accent-dark); background: var(--rc-accent-pale); padding: 2px 8px; border-radius: 99px; min-width: 62px; text-align: center; }
.rc-land-timeline b { font-weight: 600; }
.rc-land-timeline-note { font-size: .85rem; color: var(--rc-muted); border-top: 1px dashed var(--rc-border); padding-top: 10px; }

.rc-land-final {
    max-width: 1100px; margin: 20px auto 0; padding: 40px 22px; text-align: center;
    background: var(--rc-accent-dark); color: var(--rc-on-accent); border-radius: 24px;
    background-image: radial-gradient(circle at 100% 0, rgba(255, 255, 255, .14), transparent 55%);
}
.rc-land-final h2 { font-size: clamp(1.6rem, 5vw, 2.3rem); font-weight: 800; letter-spacing: -.02em; }
.rc-land-final p { opacity: .85; max-width: 34em; margin: 0 auto 20px; }
.rc-land-final .btn-light { --bs-btn-color: var(--rc-accent-dark); --bs-btn-font-weight: 700; }
.rc-land-foot { max-width: 1100px; margin: 0 auto; padding: 28px 18px 40px; display: flex; flex-wrap: wrap; gap: 8px 20px; justify-content: space-between; font-size: .85rem; color: var(--rc-muted); }
.rc-land-by { color: var(--rc-accent-dark); text-decoration: none; font-weight: 600; }

/* The back-to-top button is fixed to the bottom-right of the VIEWPORT, and
   at the end of the page that corner is the footer's. Below 1224px the
   centring gutter is too narrow to keep them apart (1100px of content plus
   the button's 62px each side), so the footer gives up the corner itself. */
@media (max-width: 1223.98px) {
    .rc-land-foot { padding-right: 88px; }
}
.rc-land-by:hover { text-decoration: underline; }
/* The landing page's back-to-top. Floats, because this page has no sticky
   bar to seat it in the way the shelves seat .rc-totop; it is the size of
   the app's FAB and sits where the FAB would, so a visitor who signs up
   finds the same corner occupied by the same kind of thing. Hidden by the
   attribute until the page has scrolled past the hero — see the script at
   the foot of landing.blade.php. No tab bar on this page, so no
   --rc-tabbar-h in the offset. */
/* Shared with the guide, which is eleven screens for the same reason the
   landing page is seven. */
.rc-land-totop, .rc-guide-totop {
    position: fixed; right: 14px; bottom: 14px; z-index: 1025;
    width: 48px; height: 48px; border-radius: 50%; border: 1px solid var(--rc-border);
    background: var(--rc-card); color: var(--rc-accent-dark);
    box-shadow: 0 4px 16px rgba(22, 48, 43, .18); font-size: 1.25rem;
    display: inline-flex; align-items: center; justify-content: center;
}
.rc-land-totop:hover, .rc-guide-totop:hover { background: var(--rc-accent-pale); }
.rc-land-totop:focus-visible, .rc-guide-totop:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .3); }

@media (min-width: 768px) {
    .rc-land-loop { grid-template-columns: repeat(2, 1fr); }
    .rc-land-more { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .rc-land-notes { grid-template-columns: 150px minmax(0, 1fr); }
    .rc-land-cta .btn { flex: 0 0 auto; }
}
@media (min-width: 900px) {
    /* Copy and picture side by side, and the picture swaps sides on the
       middle one. Below this the picture always follows its copy, because a
       stacked column that alternated would read as an ordering mistake. */
    .rc-land-show { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; margin-bottom: 56px; }
    .rc-land-show.is-flipped .rc-land-show-art { order: -1; }
}
@media (min-width: 992px) {
    .rc-land-hero { grid-template-columns: 1.1fr .9fr; padding: 56px 24px 64px; gap: 48px; }
    .rc-land-loop { grid-template-columns: repeat(4, 1fr); }
    .rc-land-two { grid-template-columns: 1.1fr .9fr; gap: 48px; }
    .rc-land-section { padding: 64px 24px; }
    .rc-land-final { margin-left: 24px; margin-right: 24px; padding: 56px 24px; }
}
@media (min-width: 1148px) { .rc-land-final { margin-left: auto; margin-right: auto; } }

/* A game card sizes to its content.
   The study card reserves 42vh so a flashcard does not jump as it flips, but
   a game has no flip and its controls sit directly beneath: on a two-line
   quiz question that reservation is half a screen of nothing. The floor here
   is only enough to stop the card twitching between questions. */
.rc-study-card.rc-game-card { min-height: 190px; padding: 20px 16px; }
@media (min-width: 992px) { .rc-study-card.rc-game-card { min-height: 220px; } }

/* Memory Match. Three columns even on a phone: a board you have to scroll
   is a board you cannot remember, so the whole thing has to fit on screen. */
.rc-memory { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
@media (min-width: 576px) { .rc-memory { grid-template-columns: repeat(4, 1fr); gap: 10px; } }

/* The tile itself is only a stage — no border, no colour of its own — so the
   two faces can turn inside it. Both faces are in the DOM at once, which is
   what makes a real flip possible rather than a swap of contents. */
.rc-tile {
    aspect-ratio: 1 / 1; padding: 0; border: 0; background: none;
    perspective: 800px; -webkit-tap-highlight-color: transparent;
    transition: transform .1s ease;
}
.rc-tile:not([disabled]):active { transform: scale(.96); }
.rc-tile[disabled] { cursor: default; }

.rc-tile-inner {
    position: relative; display: block; width: 100%; height: 100%;
    transform-style: preserve-3d;
    transition: transform .45s cubic-bezier(.2, .7, .3, 1);
}
.rc-tile.is-up .rc-tile-inner { transform: rotateY(180deg); }

.rc-tile-face {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 6px; border: 1px solid var(--rc-border); border-radius: 12px;
    backface-visibility: hidden; -webkit-backface-visibility: hidden;
    overflow: hidden; text-align: center; font-size: .7rem; line-height: 1.2;
}
@media (min-width: 576px) { .rc-tile-face { font-size: .82rem; padding: 10px; border-radius: 14px; } }

.rc-tile-cover { background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-size: 1.2rem; }
.rc-tile-cover i { opacity: .5; }
/* The far side, pre-rotated so it lands face on when the tile turns. */
.rc-tile-value {
    transform: rotateY(180deg);
    background: var(--rc-card); border-color: var(--rc-accent); color: var(--rc-text);
}
.rc-tile-value.is-answer { font-weight: 700; color: var(--rc-accent-dark); }
.rc-tile-text { display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }

/* A found pair settles: dashed and dimmed, so the board reads as progress
   rather than as tiles still in play. */
.rc-tile.is-matched .rc-tile-value { border-style: dashed; border-color: var(--rc-border); color: var(--rc-muted); opacity: .5; }
.rc-tile.is-matched { animation: rc-tile-found .45s ease; }
@keyframes rc-tile-found {
    0% { transform: none; }
    45% { transform: scale(1.07); }
    100% { transform: none; }
}

/* The opening look at the board: nothing to tap, so nothing invites a tap. */
.rc-memory.is-preview { pointer-events: none; }
.rc-memory.is-preview .rc-tile-value { border-color: var(--rc-accent); }

@media (prefers-reduced-motion: reduce) {
    .rc-tile, .rc-tile-inner { transition: none; }
    .rc-tile.is-matched { animation: none; }
}

/* Pair Up: two columns to join, side by side even on a phone.

   Side by side is the game. Stacked, the right-hand column starts below the
   fold on any card with more than three pairs, so the thing being joined and
   the thing it joins to are never on screen together — and the whole question
   is which of these goes with which of those. The type shrinks instead, and
   the eligibility rule caps either half at 60 characters so there is a floor
   under how much has to fit (GameContentAdapter::MAX_PAIR_LENGTH).

   `align-items: start` so a short column does not stretch its rows to match
   the tall one: a row's height is a hint that it holds more words, and
   stretching throws that away.

   The column gap is sized for the WIRES rather than for keeping two columns
   apart — the same 26px the study screen's matching card uses, because under
   about 22px a curve has nowhere to bend and several of them crossing read as
   a smudge. The width comes off the items, which is why the type steps down
   sooner on a narrow phone. */
.rc-pair { display: grid; grid-template-columns: 1fr 1fr; column-gap: 26px; row-gap: 6px; align-items: start; position: relative; }

.rc-pair-col { display: flex; flex-direction: column; gap: 6px; }
@media (max-width: 380px) { .rc-pair-row, .rc-pair-opt { font-size: .8rem; padding-left: 8px; padding-right: 8px; } }

/* The badge that carries the pairing when the wire cannot: a joined row and
   the item it reached show the same number. It holds its space while empty so
   nothing shifts sideways at the moment of joining — which is exactly when the
   eye is on the wire and a jump would be read as part of it. */
.rc-pair-line { display: flex; align-items: center; gap: 7px; }
.rc-pair-key {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; border-radius: 6px;
    font-size: .72rem; font-weight: 700; line-height: 1;
    background: transparent; color: transparent;
}
.rc-pair-row.is-joined .rc-pair-key,
.rc-pair-opt.is-taken .rc-pair-key { background: var(--rc-accent-fill); color: var(--rc-on-accent); }

/* Both sides share their shape, so a row and an option read as two halves of
   one thing rather than as a list beside a menu. */
.rc-pair-row, .rc-pair-opt {
    display: flex; flex-direction: column; gap: 3px; width: 100%;
    padding: 10px 12px; border-radius: 12px; text-align: left;
    background: var(--rc-card); border: 1px solid var(--rc-border); color: var(--rc-text);
    font-size: .85rem; line-height: 1.3;
    transition: border-color .12s ease, background .12s ease;
}
.rc-pair-text { overflow-wrap: anywhere; }

/* Held: the one row waiting for a partner. Filled rather than merely outlined,
   because it has to be findable at a glance while the eye is in the other
   column — and it is the only thing on screen in this state. */
.rc-pair-row.is-held { border-color: var(--rc-accent); background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-weight: 600; }

/* Joined, and out of play — but NOT faded. These two are the ends of a wire,
   and a wire anchored to something half transparent reads as a mistake rather
   than as a finished join. Dashed in the accent, with the text muted, does the
   saying instead. (This was opacity .6 before the wires existed.) */
.rc-pair-row.is-joined, .rc-pair-opt.is-taken {
    border-style: dashed; border-color: var(--rc-accent); color: var(--rc-muted); background: none;
}
.rc-pair-opt:disabled { cursor: default; }

/* What a joined row turned out to be. Small, under the term, so the finished
   board reads as a list of pairs — which is also where an unjoined row's
   answer goes once the round is lost. */
.rc-pair-got { display: flex; align-items: baseline; gap: 3px; font-size: .78rem; font-weight: 600; color: var(--rc-accent-dark); opacity: 1; padding-left: 27px; }
.rc-pair-got.is-missed { color: var(--rc-danger); }

/* The wire that has just been joined, drawn in by the board rather than by a
   keyframe: :d changing on a path does not restart an animation, so the stroke
   offset is set and released in JavaScript (see rcPairBoard). The dash is
   longer than any wire on this board, so the path is fully hidden at the start
   whatever its length. */
.rc-wire.is-fresh { stroke-dasharray: 400; }

/* Half a wire out of the held row: dashed and thinner, so it reads as reaching
   rather than as a join already made. */
.rc-wire.is-reaching { stroke-dasharray: 4 5; stroke-width: 2; opacity: .75; }

/* A wrong join, on both ends at once. Shifting rather than flashing a colour:
   the danger colour is doing the saying, and the movement is what makes it
   noticeable on a board where nothing else moves. */
.rc-pair-row.is-wrong, .rc-pair-opt.is-wrong {
    border-color: var(--rc-danger); background: var(--rc-danger-pale); color: var(--rc-danger);
    animation: rc-pair-no .42s ease;
}
@keyframes rc-pair-no {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-4px); }
    45% { transform: translateX(4px); }
    70% { transform: translateX(-2px); }
}

@media (prefers-reduced-motion: reduce) {
    .rc-pair-row.is-wrong, .rc-pair-opt.is-wrong { animation: none; }
    .rc-wire.is-fresh { stroke-dasharray: none; }
}

/* All That Apply: the study screen's answer list, plus a held state.

   Built on .rc-choice so a question reads the same here as it does in study.
   What this game adds is that several choices are held at once before anything
   is submitted, so "picked" has to be visible without being mistaken for
   "correct" — the tick inside the box is the learner's own mark, and the tick
   at the end of the row (added only once the round is over) is the app's. */
.rc-apply-opt { text-align: left; }
.rc-apply-box {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 6px;
    border: 2px solid var(--rc-border); background: var(--rc-card);
    font-size: .8rem; line-height: 1;
}
.rc-apply-opt.is-picked { border-color: var(--rc-accent); background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-weight: 600; }
.rc-apply-opt.is-picked .rc-apply-box { border-color: var(--rc-accent-fill); background: var(--rc-accent-fill); color: #fff; }

/* Marked up after the submission, and the same language Speed Quiz speaks:
   every right answer outlined in the accent, and anything picked that does not
   belong in danger. A right answer the learner never picked is still shown —
   which of them was missed is the whole lesson of this card type. */
.rc-apply-opt.is-answer { opacity: 1 !important; border-color: var(--rc-accent); background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-weight: 700; }
.rc-apply-opt.is-wrong { opacity: 1 !important; border-color: var(--rc-danger); background: var(--rc-danger-pale); color: var(--rc-danger); }
.rc-apply-opt.is-wrong .rc-apply-box { border-color: var(--rc-danger); background: var(--rc-danger); color: #fff; }

/* Speed Quiz: the choices stay on screen once answered, marked. */
.rc-quiz-opt { opacity: 1 !important; }
.rc-quiz-opt.is-answer { border-color: var(--rc-accent); background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-weight: 700; }
.rc-quiz-opt.is-wrong { border-color: var(--rc-danger); background: var(--rc-danger-pale); color: var(--rc-danger); }

/* Survival: the run itself is the score, so it is the biggest thing on the
   end screen — the number people come back to beat. */
.rc-survival-score { font-size: 3rem; font-weight: 700; line-height: 1.1; color: var(--rc-accent-dark); }
/* The confident misses on a staked recap: a study list, so it reads as a
   list rather than as one more explanation panel. */
.rc-explain-danger { border-color: var(--rc-danger); }
.rc-explain-danger .rc-explain-label { color: var(--rc-danger); }
.rc-miss { padding: 8px 0; border-top: 1px solid var(--rc-border); }
.rc-miss:first-of-type { border-top: 0; }

/* Import bar above the editor. Deliberately quiet: it is a way in, not the
   point of the screen, so it sits above the toolbar without competing. */
.rc-import { margin-bottom: 10px; }
.rc-import-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rc-import-actions .btn { cursor: pointer; }
.rc-import-actions .btn.disabled { pointer-events: none; opacity: .65; }
.rc-import-hint { flex: 1 1 260px; min-width: 0; font-size: .78rem; line-height: 1.35; color: var(--rc-muted); }
/* Emphasis here lifts to the body colour rather than adding a hue: this line sits
   inside a tinted panel, and a warning colour on .78rem text is the pair this file
   asks to be measured against its neighbours. Weight and lightness carry it. */
.rc-import-hint strong { font-weight: 600; color: var(--rc-text); }
.rc-import-progress { height: 6px; margin-top: 8px; }
.rc-import .rc-result { font-size: .85rem; }


/* ---- Folders -------------------------------------------------------------

   The tree sits beside the notes on a wide screen and above them on a phone,
   where it is capped and scrolls: a deep tree must never push the notes it is
   there to reach off the bottom of the screen. Indentation is one variable,
   --rc-depth, so every row that stands in for a folder — the folder itself,
   the rename box, the move list, the new-folder box — lines up with it. */
[x-cloak] { display: none !important; }
.rc-filed { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
@media (min-width: 992px) { .rc-filed { grid-template-columns: 244px minmax(0, 1fr); gap: 24px; } }
.rc-filed-main { min-width: 0; }

.rc-folders { display: flex; flex-direction: column; gap: 2px; }
.rc-folders-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.rc-folders-tree { display: flex; flex-direction: column; gap: 2px; }

/* On a phone the tree is a disclosure, not a pane: one line saying where you
   are, opened only when you want to go somewhere else and closed again the
   moment you pick. Nothing here clips its own children — the ... actions are
   an in-flow row rather than a popover for exactly that reason. */
.rc-folders-toggle {
    display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0;
    min-height: 38px; padding: 0 12px;
    border: 1px solid var(--rc-border); border-radius: 99px;
    background: var(--rc-card); color: var(--rc-text);
    font-size: .875rem; font-weight: 600; line-height: 1; text-align: left;
}
.rc-folders-toggle:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); }
.rc-folders-caret { flex: none; font-size: .7rem; opacity: .6; transition: transform .15s ease; }
.rc-folders.is-open .rc-folders-caret { transform: rotate(180deg); }
.rc-folders .rc-folders-tree { display: none; }
.rc-folders.is-open .rc-folders-tree { display: flex; margin-top: 6px; }

@media (min-width: 992px) {
    .rc-folders { position: sticky; top: 80px; max-height: calc(100vh - 120px); overflow-y: auto; }
    .rc-folders-head { margin-bottom: 6px; }
    .rc-folders .rc-folders-tree { display: flex; }
}
@media (prefers-reduced-motion: reduce) { .rc-folders-caret { transition: none; } }

.rc-folder-row { display: flex; align-items: center; gap: 2px; }
.rc-folder {
    display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0;
    padding: 7px 10px 7px calc(10px + var(--rc-depth, 0) * 14px);
    border: 0; border-radius: 10px; background: transparent; color: var(--rc-text);
    font-size: .875rem; font-weight: 600; text-align: left; line-height: 1.2;
}
.rc-folder:hover { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-folder:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); }
.rc-folder.active { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-folder.active .rc-filter-count { background: rgba(var(--rc-accent-rgb), .22); color: var(--rc-on-accent); }
.rc-folder .bi { flex: none; opacity: .8; }
.rc-folder-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-folder-menu { flex: none; padding: 2px 6px; opacity: .55; }
.rc-folder-row:hover .rc-folder-menu, .rc-folder-menu:focus-visible, .rc-folder-menu.is-open { opacity: 1; }

/* A folder's actions, in flow beneath it. Wraps rather than scrolls, so the
   fourth action is reachable on the narrowest screen. */
.rc-folder-actions {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin: 2px 0 6px calc(var(--rc-depth, 0) * 14px);
    padding: 6px; border: 1px solid var(--rc-border); border-radius: 12px; background: var(--rc-card);
}
.rc-folder-actions button {
    display: inline-flex; align-items: center; gap: 5px;
    min-height: 32px; padding: 0 10px;
    border: 1px solid var(--rc-border); border-radius: 99px;
    background: transparent; color: var(--rc-text); font-size: .78rem; font-weight: 600; line-height: 1;
}
.rc-folder-actions button:hover { border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-folder-actions button:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); }
.rc-folder-actions .rc-folder-danger { color: var(--rc-danger); }
.rc-folder-actions .rc-folder-danger:hover { border-color: var(--rc-danger); color: var(--rc-danger); }
.rc-folder-empty { font-size: .8rem; color: var(--rc-muted); margin: 8px 2px 0; }

.rc-folder-form {
    display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
    padding: 2px 0 2px calc(var(--rc-depth, 0) * 14px);
}
.rc-folder-form .form-control { flex: 1 1 100px; min-width: 0; }
.rc-folder-error { flex: 1 0 100%; font-size: .78rem; color: var(--rc-danger); }

/* One move sheet for the whole tree, floating clear of it: the rail scrolls
   and collapses, so a panel drawn inside it would be cut off exactly when the
   folder being moved is near the bottom. */
.rc-move-sheet {
    position: fixed; z-index: 1040; left: 50%; transform: translateX(-50%);
    bottom: calc(var(--rc-tabbar-h) + 14px);
    width: min(360px, calc(100vw - 24px)); max-height: 60vh; overflow-y: auto;
    padding: 12px; border: 1px solid var(--rc-border); border-radius: 16px;
    background: var(--rc-card); box-shadow: 0 12px 32px rgba(22, 48, 43, .18);
}
@media (min-width: 992px) { .rc-move-sheet { bottom: auto; top: 96px; } }
.rc-folder-move-head { display: flex; align-items: center; gap: 6px; font-size: .78rem; color: var(--rc-muted); margin-bottom: 4px; }
.rc-folder-target {
    display: flex; align-items: center; gap: 6px; width: 100%;
    padding: 5px 8px 5px calc(8px + var(--rc-depth, 0) * 12px);
    border: 0; border-radius: 8px; background: transparent; color: var(--rc-text);
    font-size: .82rem; text-align: left;
}
.rc-folder-target:hover:not(:disabled) { background: var(--rc-accent-pale); color: var(--rc-accent-dark); }
.rc-folder-target:disabled { opacity: .45; }

/* Breadcrumbs: every step back out is one click, however deep the folder. */
.rc-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 12px; font-size: .85rem; }
.rc-crumbs button { border: 0; background: transparent; padding: 0; color: var(--rc-accent-dark); font-weight: 600; }
.rc-crumbs button:hover { text-decoration: underline; }
.rc-crumbs .bi { font-size: .7rem; opacity: .5; }
.rc-crumbs [aria-current] { font-weight: 700; }
.rc-crumbs-note { margin-left: auto; font-size: .78rem; color: var(--rc-muted); }

.rc-item-folder { margin-left: 8px; }
.rc-item-folder .bi { margin-right: 3px; opacity: .7; }

/* The ⋯ menu shared by a note and a card set. A note's carries the whole
   folder list and a set's carries every colour, so it is capped and scrolls
   rather than running off the bottom of a phone. */
.rc-sheet-menu { max-height: 70vh; overflow-y: auto; }

/* Each tile's ⋯ is absolute at z-index 1, which makes it a stacking context:
   an open menu was trapped at level 1 with the tiles after it, and their own
   ⋯ buttons showed through it. The open one is lifted clear of its siblings. */
.rc-brick .dropdown:has(> .dropdown-menu.show) { z-index: 1000; }

.rc-menu-head { display: none; }

/* On a phone these menus are a bottom sheet, not a dropdown. As a dropdown one
   was pinned to a 30px button in a 360px column: a narrow tower that ran under
   the tab bar, over the tiles beside it, and cut the colour row in half.

   Popper still positions the menu, so the placement it writes inline is
   overridden with !important. The slide uses `translate` rather than
   `transform` because an !important declaration beats an animation — it
   would have pinned the keyframes too. Same z-order as the More sheet:
   scrim and sheet under the tab bar (1030), everything under a dialog. The
   scrim is the wrapper's own ::before, so a tap on it lands on the wrapper —
   outside the menu, which Bootstrap closes — and never on the note beneath. */
@media (max-width: 575.98px) {
    .dropdown:has(> .rc-sheet-menu.show) { z-index: 1029; }
    .dropdown:has(> .rc-sheet-menu.show)::before {
        content: ''; position: fixed; inset: 0;
        background: rgba(12, 26, 24, .45);
        animation: rc-scrim-in .18s ease;
    }
    .rc-sheet-menu.show {
        position: fixed !important; inset: auto 0 var(--rc-tabbar-h) 0 !important;
        transform: none !important; margin: 0 !important;
        max-height: calc(100dvh - var(--rc-tabbar-h) - 56px);
        overscroll-behavior: contain;
        padding: 0 12px 12px;
        border-width: 1px 0 0; border-radius: 18px 18px 0 0;
        box-shadow: 0 -10px 30px rgba(22, 48, 43, .18);
        animation: rc-sheet-in .22s ease;
    }

    .rc-sheet-menu .rc-menu-head {
        display: flex; align-items: center; gap: 12px;
        position: sticky; top: 0; z-index: 1;
        padding: 20px 4px 12px; background: inherit;
    }
    /* The grip, as on the More sheet: says "this came up from below". */
    .rc-sheet-menu .rc-menu-head::before {
        content: ''; position: absolute; top: 8px; left: 50%; translate: -50% 0;
        width: 40px; height: 4px; border-radius: 99px; background: var(--rc-border);
    }
    .rc-menu-title {
        flex: 1 1 auto; min-width: 0;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        font-weight: 700; font-size: 1.05rem; color: var(--rc-text);
    }
    .rc-menu-head .btn-close { flex: none; width: 44px; height: 44px; padding: 0; background-size: 14px; }

    .rc-sheet-menu .rc-menu-quick { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 2px 0 10px; }
    .rc-menu-quick .dropdown-item {
        display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
        min-height: 76px; padding: 12px 6px; border-radius: 14px;
        background: var(--rc-accent-pale); color: var(--rc-accent-dark);
        font-weight: 600; font-size: .85rem; text-align: center; white-space: normal;
    }
    .rc-menu-quick .dropdown-item .bi { font-size: 1.35rem; }

    .rc-sheet-menu .dropdown-item { border-radius: 10px; }
    .rc-sheet-menu .dropdown-divider { margin: 6px 0; }
    .rc-sheet-menu .dropdown-header {
        padding: 10px 4px 6px; font-size: .72rem; font-weight: 700;
        letter-spacing: .06em; text-transform: uppercase; color: var(--rc-muted);
    }
    /* The sheet is the width of the phone, so the rows stop wrapping at the
       width a dropdown had. Target sizes are left to the touch block at the
       end of this file; only the colour inside grows, so a swatch reads as a
       colour rather than a dot adrift in its 44px square. */
    .rc-sheet-menu .rc-swatches, .rc-sheet-menu .rc-icons { max-width: none; gap: 8px; }
    .rc-sheet-menu .rc-swatch span { inset: 4px; border-radius: 8px; }
}
@keyframes rc-sheet-in { from { translate: 0 100%; } }
@keyframes rc-scrim-in { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
    .rc-sheet-menu.show, .dropdown:has(> .rc-sheet-menu.show)::before { animation: none; }
}

/* The move list inside a note's ⋯ menu, indented the same way the tree is. */
.rc-move-item { padding-left: calc(1rem + var(--rc-depth, 0) * 12px); }
.rc-move-item.active { background: var(--rc-accent-pale); color: var(--rc-accent-dark); font-weight: 600; }
.rc-read-eyebrow .rc-crumb-sep { opacity: .5; }

/* Picking several notes at once. The box sits in the corner the ⋯ menu does
   not use, and the title steps aside to make room for it.

   Two things the bare checkbox got wrong. It was 16px of target, which no
   thumb hits reliably; and the card's body link covers the whole card, so
   every miss landed on that link and opened the note — the one outcome that
   undoes what you were in the middle of doing. The label fixes both: 40px of
   target here, 44 on touch (the block at the end of this file), all of it
   stacked above the link.

   z-index 2, over the ⋯ menu's 1, because the two corners are close enough
   on a narrow card that the order has to be deliberate. */
.rc-brick-pick {
    position: absolute; top: 2px; left: 2px; z-index: 2;
    width: 40px; height: 40px; margin: 0;
}
.rc-brick.is-selecting .rc-brick-title { padding-left: 32px; }
.rc-brick.is-selected { border-color: var(--rc-accent); box-shadow: 0 0 0 2px rgba(var(--rc-accent-rgb), .18); }

/* Previous / next, at the end of the note where the reader actually is when
   they finish it. Two halves that stay side by side on a phone, because a
   stepper that stacks reads as two unrelated links. */
.rc-stepper { display: flex; gap: 10px; margin-top: 28px; padding-top: 16px; border-top: 1px solid var(--rc-border); }
.rc-step {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 3px;
    padding: 10px 12px; border: 1px solid var(--rc-border); border-radius: 12px;
    color: var(--rc-text); text-decoration: none;
}
.rc-step:hover { border-color: var(--rc-accent); color: var(--rc-accent-dark); }
.rc-step-next { text-align: right; }
.rc-step-dir { font-size: .72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--rc-muted); }
.rc-step-title { font-weight: 600; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-step.is-empty { color: var(--rc-muted); font-size: .8rem; align-items: flex-start; justify-content: center; border-style: dashed; }
.rc-step-next.is-empty { align-items: flex-end; }

/* Two candidates side by side. Choosing between them is the whole task, so
   they get equal room rather than a winner and a runner-up. */
.rc-dupe-pair { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 768px) { .rc-dupe-pair { grid-template-columns: 1fr 1fr; } }
.rc-dupe { display: flex; flex-direction: column; padding: 12px; border: 1px solid var(--rc-border); border-radius: 12px; }
.rc-dupe-title { font-weight: 700; color: var(--rc-text); text-decoration: none; overflow-wrap: anywhere; }
.rc-dupe-title:hover { color: var(--rc-accent-dark); }
.rc-dupe-excerpt { font-size: .85rem; color: var(--rc-muted); margin: 6px 0 4px; line-height: 1.5; }
.rc-dupe .btn { align-self: flex-start; margin-top: auto; }

/* What the search did, stated where the results begin. */
.rc-searchline { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin-bottom: 12px; font-size: .85rem; }
.rc-searchline .btn { margin-left: auto; }

/* ---- The folder tree as nested lists --------------------------------------

   Nesting does the indenting now, which is what makes a branch foldable: hide
   one list and everything under it goes with it. */
.rc-branch { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.rc-branch-item { min-width: 0; }
/* A rail down each level. Depth was carried by a 14px indent alone, which
   asks you to compare two rows to see that one is inside the other; a line
   says it without the comparison, and keeps saying it as the tree deepens. */
.rc-branch-nest { position: relative; padding-left: 14px; }
.rc-branch-nest::before {
    content: ''; position: absolute; left: 7px; top: 0; bottom: 10px;
    width: 2px; border-radius: 2px; background: var(--rc-border);
}

/* The twisty. Leaves keep the same slot empty so that names stay in one
   column whether or not a folder has anything under it. */
.rc-twisty {
    flex: none; width: 18px; height: 24px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; background: transparent; color: var(--rc-muted);
    font-size: .7rem; transition: transform .15s ease;
}
.rc-twisty:hover { color: var(--rc-accent-dark); }
.rc-twisty:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); border-radius: 6px; }
.rc-twisty.is-open { transform: rotate(90deg); }
.rc-twisty.is-leaf { pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .rc-twisty { transition: none; } }

/* The one place a drag may start. Quiet until wanted on a mouse, always
   there on a touch screen, where there is no hover to reveal it. */
.rc-grip {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 24px; border-radius: 7px;
    color: var(--rc-muted); background: var(--rc-accent-pale);
    cursor: grab; font-size: .9rem; line-height: 1;
    /* Without this a touch drag scrolls the page instead of moving the row —
       the handle looks draggable and simply is not. */
    touch-action: none; -webkit-user-select: none; user-select: none;
}
.rc-grip:hover { color: var(--rc-accent-dark); background: var(--rc-accent-fill); }
.rc-grip:hover .bi { color: var(--rc-on-accent); }
.rc-grip:active { cursor: grabbing; }

.rc-brick-grip { position: absolute; top: 10px; left: 10px; z-index: 1; }
.rc-brick.is-sortable .rc-brick-title { padding-left: 22px; }
.rc-brick.is-selecting .rc-brick-title { padding-left: 32px; }
/* Both at once, and they compete for the same corner. The box's target
   narrows to make room rather than covering the handle: it is on top (z-index
   2 against the grip's 1), so a label left at full width would sit over the
   one place a drag is allowed to start and the card could not be moved at
   all. 30px is still five times the area the bare box had. */
.rc-brick.is-sortable.is-selecting .rc-brick-pick { width: 30px; }
.rc-brick.is-sortable.is-selecting .rc-brick-grip { left: 32px; }
.rc-brick.is-sortable.is-selecting .rc-brick-title { padding-left: 58px; }

/* Livewire marks what is being dragged and where it would land. */
[wire\:sort\:item].sorting-item, .sortable-ghost, .sortable-drag { opacity: .6; }
[wire\:sort\:item].sortable-chosen { box-shadow: 0 6px 20px rgba(22, 48, 43, .16); }

.rc-orderline { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; font-size: .8rem; color: var(--rc-muted); }
.rc-orderline .bi { opacity: .6; }

/* ---- Notes grid: one pass over hierarchy ---------------------------------

   Scoped to the notes grid on purpose. The same .rc-brick draws the Study and
   Library shelves, and this is a decision about the notes screen only. */
.rc-notes-grid .rc-brick {
    gap: 6px; padding: 10px 12px 8px;
    border: 1px solid var(--rc-border);
}
/* The coloured edge appears only on a note that has a colour. When every card
   wore one, the colour you chose said nothing. */
.rc-notes-grid .rc-brick.has-color { border-left: 3px solid var(--rc-nc); }
.rc-notes-grid .rc-brick:hover { border-color: var(--rc-nc, var(--rc-accent)); }
.rc-notes-grid .rc-brick-title { line-height: 1.25; }
.rc-notes-grid .rc-brick-excerpt { -webkit-line-clamp: 2; margin-top: 2px; }
.rc-notes-grid .rc-brick-foot { padding-top: 7px; gap: 6px; }
.rc-note-sep { opacity: .45; }

/* Present in the layout at all times so nothing shifts, and legible only on
   the card under the pointer — or under the keyboard, which is why this is
   opacity and not display. */
.rc-note-nudge { color: var(--rc-muted); text-decoration: none; font-weight: 600; opacity: 0; transition: opacity .12s; }
.rc-brick:hover .rc-note-nudge, .rc-brick:focus-within .rc-note-nudge { opacity: 1; color: var(--rc-accent-dark); }
.rc-note-nudge:focus-visible { opacity: 1; outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25); border-radius: 6px; }
@media (hover: none) { .rc-note-nudge { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .rc-note-nudge { transition: none; } }

/* The active tag turns itself off, so the row needs no "everything" chip. */
.rc-filter-x { font-size: .65rem; margin-left: 2px; opacity: .8; }

/* ---- The rail gives its width back to the names -------------------------

   At rest a row is twisty, name, count. Under the pointer the grip appears
   and the ⋯ takes the count's place — it is positioned over it, so the name
   does not reflow twice. */
.rc-folder-row { position: relative; }
.rc-folder-row .rc-grip { width: 0; opacity: 0; overflow: hidden; transition: width .12s, opacity .12s; }
.rc-folder-row:hover .rc-grip, .rc-folder-row:focus-within .rc-grip { width: 22px; opacity: 1; }
.rc-folder-row .rc-folder-menu {
    position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.rc-folder-row:hover .rc-folder-menu,
.rc-folder-row:focus-within .rc-folder-menu,
.rc-folder-menu.is-open { opacity: 1; pointer-events: auto; }
.rc-folder-row:hover .rc-folder .rc-filter-count,
.rc-folder-row:focus-within .rc-folder .rc-filter-count { opacity: 0; }
.rc-folder .rc-filter-count { transition: opacity .12s; }
@media (hover: none) {
    .rc-folder-row .rc-grip { width: 22px; opacity: 1; }
    /* The swap above needs a hover to swap on. Without one the ⋯ is simply
       always there — and, positioned over the count, it hid every folder's
       count on every phone: the badge sat at x 321-349 under a button
       covering 314-357, at full opacity, permanently invisible. All notes
       and Unfiled kept theirs only because neither carries a ⋯.

       In flow the two are siblings and nothing overlaps. The name pays for
       it, 205px to 160px at 375px, which is the right way round: a name
       truncates with an ellipsis and tells you it did, while a hidden count
       tells you nothing. */
    .rc-folder-row .rc-folder-menu {
        position: static; transform: none; opacity: 1; pointer-events: auto;
    }
    /* And the count no longer makes way for it. The fade above still fired
       on touch — a tap leaves :hover stuck and focus inside the row — so
       opening a folder or its ⋯ blanked that folder's count until you
       touched somewhere else. Same selectors, so this wins by order. */
    .rc-folder-row:hover .rc-folder .rc-filter-count,
    .rc-folder-row:focus-within .rc-folder .rc-filter-count { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .rc-folder-row .rc-grip, .rc-folder-row .rc-folder-menu, .rc-folder .rc-filter-count { transition: none; }
}

/* ---- Make cards from a note ------------------------------------------- */
/* A checklist of proposals. Each row is a card the reader can edit before it
   becomes one, so the fields are real inputs rather than a preview. */
.rc-cand-list { display: flex; flex-direction: column; gap: 10px; }
.rc-cand {
    border: 1px solid var(--rc-border); border-radius: 12px;
    padding: 10px 12px; background: var(--rc-card);
}
/* Unticked rather than removed: it stays readable so a change of mind costs
   one click, but it stops competing with the rows being kept. */
.rc-cand.is-off { opacity: .5; }
.rc-cand-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.rc-cand-tag {
    font-size: .68rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
    padding: 2px 8px; border-radius: 99px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
}
.rc-cand-type { font-size: .72rem; color: var(--rc-muted); font-weight: 600; }
.rc-cand-label {
    display: block; font-size: .68rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .02em; color: var(--rc-muted); margin: 8px 0 3px;
}
.rc-cand-pairs {
    margin: 0; padding: 8px 12px; list-style: none;
    border: 1px dashed var(--rc-border); border-radius: 10px;
    font-size: .82rem; font-variant-numeric: tabular-nums;
}
.rc-cand-pairs li + li { margin-top: 3px; }

/* ---- Read / unread notes ---------------------------------------------- */
/* Only the minority state is drawn. A library where most notes are read
   gains nothing from a badge on every one of them, so silence means "read
   and current" and the eye goes straight to what is left.

   Filled versus hollow, not green versus amber: about one man in twelve
   cannot separate those two hues, and the difference has to survive that. */
.rc-read-dot {
    display: inline-block; width: 8px; height: 8px; border-radius: 99px;
    margin-right: 7px; vertical-align: 1px; flex: none;
}
.rc-read-dot.is-unread { background: var(--rc-accent); }
.rc-read-dot.is-changed { background: none; border: 1.5px solid var(--rc-warn); }

/* A note still to be opened earns weight; one already read gives it back. */
.rc-brick-title:has(.rc-read-dot.is-unread),
.rc-item-title:has(.rc-read-dot.is-unread) { font-weight: 700; }

.rc-read-state { font-weight: 600; }
.rc-read-state.is-unread { color: var(--rc-accent-dark); }
.rc-read-state.is-changed { color: var(--rc-warn); }
/* The dot on the Unread chip, at the size the note cards use it. */
.rc-filter-dot { font-size: .5rem; vertical-align: 1px; color: var(--rc-accent); }
.rc-filter.active .rc-filter-dot { color: inherit; }

/* ---- The way in from a shared page ------------------------------------ */
/* Modest on purpose. The page belongs to whoever wrote the note, and a
   banner shouting over their work is a worse advert than a quiet one — but
   this is the only screen Recall shows to somebody with no account, so it
   has to be a button rather than small print under the fold. */
.rc-join {
    /* width alongside max-width for the same reason .rc-guest-card carries
       both: this is a flex child under align-items:center, so without it the
       box shrink-wraps its text into a column three words wide. */
    width: 100%; max-width: 420px; margin: 22px auto 0; padding: 18px 20px;
    text-align: center;
    background: var(--rc-accent-pale); border: 1px solid rgba(var(--rc-accent-rgb), .22);
    border-radius: 16px;
}
.rc-join-line {
    margin: 0 0 14px; font-size: .95rem; line-height: 1.5;
    color: var(--rc-accent-dark); font-weight: 600;
}
/* Full width below 380px so the tap target is never a thin pill in the
   middle of a phone screen. */
.rc-join-btn { min-width: 220px; }
.rc-join-sub { margin: 12px 0 0; font-size: .84rem; color: var(--rc-muted); }
.rc-join-sub a { color: var(--rc-accent-dark); font-weight: 600; }

@media (max-width: 420px) {
    .rc-join { margin-left: 4px; margin-right: 4px; padding: 16px; }
    .rc-join-btn { width: 100%; min-width: 0; }
}

/* ---- The guide -------------------------------------------------------- */
/* Contents at the top rather than a sidebar: the page is read on a phone as
   often as not, and a rail that becomes a stack of links above the text is
   just a worse version of this. */
/* Pills rather than a paragraph of links: nine destinations reading as one
   run of text is a wall, and each one is a tap target on a phone — where a
   line of underlined words sets them a few pixels apart. */
/* The guide's contents.

   Measured before it was changed: 8,832px, about eleven screens, eleven
   sections, and the only way back to the list of them was scrolling to the
   top. Notes and Writing were 3,295px of that between them while The Library
   was 215px, and a flat row of eleven equal pills said none of it.

   Two controls now, one per width, because the page is read on both and a
   sticky rail has nowhere to go at 375px:

   - .rc-guide-rail, from 992px up. Sticky, so the contents stop being
     somewhere you scroll back to.
   - .rc-guide-jump, below that. A <details> pinned to the top of the screen.
     A <details> rather than a menu because signed-out pages carry no Alpine
     and this has to work with no JavaScript at all. */
/* The reading card is 760px, which is the right measure for one column of
   prose and the wrong one once a 232px rail stands beside it: the text column
   fell to 415px and the page came out 11,232px — TALLER than the 8,832 it
   started at, because a narrow column wraps into more lines. Widened only
   where the rail exists, so install, privacy and the rest keep their measure.
   Browsers without :has() keep 760px and simply get the cramped version. */
@media (min-width: 992px) {
    .rc-guest-card.is-reading:has(.rc-guide-shell) { max-width: 1040px; }
}
.rc-guide-shell { display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; }
.rc-guide-group {
    font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--rc-muted); margin: 14px 0 6px 12px;
}
.rc-guide-group:first-child { margin-top: 0; }

.rc-guide-rail { display: none; }
@media (min-width: 992px) {
    .rc-guide-shell { grid-template-columns: 232px minmax(0, 1fr); gap: 32px; }
    /* align-self: start, or the rail stretches the full grid row and sticky
       has nothing left to stick within. */
    .rc-guide-rail {
        display: block; position: sticky; top: 24px; align-self: start;
        max-height: calc(100vh - 48px); overflow-y: auto;
    }
    .rc-guide-jump { display: none; }
}
.rc-guide-rail a, .rc-guide-jump-list a {
    display: flex; align-items: center; gap: 9px;
    min-height: 36px; padding: 0 12px; border-radius: 9px;
    color: var(--rc-text); text-decoration: none;
    font-size: .88rem; font-weight: 600;
    border-left: 2px solid transparent;
}
.rc-guide-rail a:hover, .rc-guide-jump-list a:hover { background: var(--rc-accent-pale); }
.rc-guide-rail a:focus-visible, .rc-guide-jump-list a:focus-visible {
    outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25);
}
/* Where you are. Set by the script at the foot of guide.blade.php; without
   JavaScript the rail is still a list of links, just without the marker. */
.rc-guide-rail a.is-here {
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    border-left-color: var(--rc-accent-dark);
}
.rc-guide-rail a .bi, .rc-guide-jump-list a .bi { flex: none; font-size: .95rem; opacity: .75; }

.rc-guide-jump {
    position: sticky; top: 0; z-index: 20;
    margin: 0 0 18px; border: 1px solid var(--rc-border); border-radius: 12px;
    background: var(--rc-card);
}
.rc-guide-jump > summary {
    display: flex; align-items: center; gap: 9px;
    min-height: 44px; padding: 0 14px; cursor: pointer; list-style: none;
    font-size: .9rem; font-weight: 700; color: var(--rc-accent-dark);
}
.rc-guide-jump > summary::-webkit-details-marker { display: none; }
.rc-guide-jump-caret { margin-left: auto; font-size: .75rem; transition: transform .15s ease; }
.rc-guide-jump[open] > summary .rc-guide-jump-caret { transform: rotate(180deg); }
.rc-guide-jump-list { padding: 4px 8px 10px; border-top: 1px solid var(--rc-border); }
@media (prefers-reduced-motion: reduce) { .rc-guide-jump-caret { transition: none; } }

/* The detail inside a section, folded away. Five of these carried about
   2,000px between them, all of it worth keeping and none of it worth
   reading on the way past. */
.rc-guide-more { margin: 14px 0; }
.rc-guide-more > summary {
    display: flex; align-items: center; gap: 10px;
    min-height: 44px; padding: 0 14px; cursor: pointer; list-style: none;
    border: 1px solid rgba(var(--rc-accent-rgb), .45); border-radius: 12px;
    background: var(--rc-accent-pale);
    font-size: .9rem; font-weight: 600; color: var(--rc-accent-dark);
}
.rc-guide-more > summary::-webkit-details-marker { display: none; }
.rc-guide-more > summary:focus-visible {
    outline: none; box-shadow: 0 0 0 3px rgba(var(--rc-accent-rgb), .25);
}
.rc-guide-more-mark { flex: none; font-size: .85rem; transition: transform .15s ease; }
.rc-guide-more[open] > summary .rc-guide-more-mark { transform: rotate(45deg); }
.rc-guide-more-body { padding: 4px 2px 0; }
@media (prefers-reduced-motion: reduce) { .rc-guide-more-mark { transition: none; } }
/* The section the reader jumped to. :target sits on the heading, so the pill
   cannot light itself up — this marks where they landed instead, which is the
   thing that was actually asked for. */
.rc-legal h2[id]:target { color: var(--rc-accent-dark); }
.rc-legal h2[id]:target::before {
    content: ""; display: inline-block;
    width: 4px; height: 1em; margin-right: 10px; vertical-align: -.1em;
    background: var(--rc-accent); border-radius: 2px;
}
/* Headings are link targets, so they must not land under a sticky bar. */
.rc-legal h2[id] { scroll-margin-top: 80px; }

/* ---- Section intros --------------------------------------------------- */
/* Shown only while a section is genuinely empty, so it can afford to be the
   loudest thing on the screen — nobody with content ever sees it. */
.rc-intro {
    padding: 22px 24px; margin-bottom: 16px;
    background: var(--rc-accent-pale);
    border: 1px solid rgba(var(--rc-accent-rgb), .22);
    border-radius: 18px;
}
.rc-intro-title { font-size: 1.15rem; font-weight: 800; margin: 0 0 6px; color: var(--rc-accent-dark); }
.rc-intro-body { margin: 0 0 16px; max-width: 56ch; line-height: 1.6; color: var(--rc-text); }
.rc-intro-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.rc-intro-more {
    display: inline-block; margin-top: 14px;
    font-size: .88rem; font-weight: 600; color: var(--rc-accent-dark); text-decoration: none;
}
.rc-intro-more:hover { text-decoration: underline; }
.rc-intro-more .bi { font-size: .8rem; }

@media (max-width: 480px) {
    .rc-intro { padding: 18px; }
    .rc-intro-actions .btn { width: 100%; }
}

/* ---- Getting started -------------------------------------------------- */
/* Above the hero on an empty account, and gone the moment the list is
   finished or put away — so it is never something an established user has
   learned to look past. */
.rc-start {
    padding: 18px 20px;
    background: var(--rc-card); border: 1px solid rgba(var(--rc-accent-rgb), .28);
    border-radius: 18px;
}
.rc-start-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.rc-start-title { font-size: 1.05rem; font-weight: 800; margin: 0; }
.rc-start-sub { margin: 2px 0 0; font-size: .82rem; color: var(--rc-muted); font-variant-numeric: tabular-nums; }
.rc-start-hide { color: var(--rc-muted); text-decoration: none; padding: 0; }

.rc-start-steps { list-style: none; margin: 14px 0 0; padding: 0; }
.rc-start-step {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 10px 0; border-top: 1px solid var(--rc-border);
}
.rc-start-step:first-child { border-top: 0; }
.rc-start-mark .bi { font-size: 1.1rem; color: var(--rc-muted); }
.rc-start-step.is-done .rc-start-mark .bi { color: var(--rc-accent); }
/* Done steps step back rather than disappear: the list is also a map of what
   the app is for, and removing a row would shorten it into nonsense. */
.rc-start-step.is-done .rc-start-step-title { color: var(--rc-muted); text-decoration: line-through; }
.rc-start-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.rc-start-step-title { font-weight: 700; }
.rc-start-step-body { font-size: .86rem; color: var(--rc-muted); line-height: 1.5; }
.rc-start-go { flex: none; align-self: center; }

/* The install step's confirmation, in place of the button it replaces.
   install.js hides it with the `hidden` attribute, so this rule deliberately
   declares no `display` — a class that sets one beats the browser's own
   [hidden] rule, which is the trap .rc-install-done and .rc-tip each needed a
   guard for further down this file. */
.rc-start-landed { flex: none; align-self: center; font-size: .84rem; font-weight: 600; color: var(--rc-accent-dark); }
.rc-start-landed .bi { margin-right: 4px; }

/* The install step's two controls sit inside [data-rc-until-installed], so the
   WRAPPER is the flex item and the button inside it is not — which is why the
   alignment and the full-width phone treatment have to be restated here. A
   flex item is blockified, so `width` on the span works despite it being a
   <span>; without it .rc-start-go's own `width: 100%` resolved against a
   content-sized parent and the button stayed 121px while every other step's
   went edge to edge. */
.rc-start-step > [data-rc-until-installed] { flex: none; align-self: center; }

.rc-start-more {
    display: inline-block; margin-top: 12px;
    font-size: .86rem; font-weight: 600; color: var(--rc-accent-dark); text-decoration: none;
}
.rc-start-more:hover { text-decoration: underline; }
.rc-start-more .bi { font-size: .78rem; }

@media (max-width: 560px) {
    .rc-start-step { flex-wrap: wrap; }
    .rc-start-go { width: 100%; margin-top: 8px; }
    .rc-start-landed { width: 100%; margin-top: 8px; }
    .rc-start-step > [data-rc-until-installed] { width: 100%; margin-top: 8px; }
    .rc-start-step > [data-rc-until-installed] .rc-start-go { margin-top: 0; }
}

/* ---- Contextual tips -------------------------------------------------- */
/* Quieter than a section intro. An intro fills an empty screen and is the
   only thing on it; a tip interrupts somebody already working, so it sits to
   one side and offers a door rather than blocking one. */
.rc-tip {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 12px 14px; margin-bottom: 12px;
    background: var(--rc-card);
    border: 1px solid var(--rc-border);
    border-left: 3px solid var(--rc-accent);
    /* Square, because a single-sided border with rounded corners reads as a
       rendering fault rather than as an accent. */
    border-radius: 0 12px 12px 0;
}
/* Inside the title now, not in a column beside the whole card. A column
   takes its width off EVERY line of the body; on the title it costs one. */
.rc-tip-icon { color: var(--rc-accent); font-size: 1.05rem; flex: none; margin-right: 7px; }
.rc-tip-text { flex: 1; min-width: 0; }
.rc-tip-title { margin: 0; font-weight: 700; font-size: .95rem; }
.rc-tip-body { margin: 3px 0 0; font-size: .87rem; color: var(--rc-muted); line-height: 1.5; }
.rc-tip-more { display: inline-block; margin-top: 6px; font-size: .84rem; font-weight: 600; color: var(--rc-accent-dark); text-decoration: none; }
/* Finishing the body rather than standing under it. The install prompt keeps
   the stacked form — there the link sits in a row beside an Install button —
   so this is scoped to the link that is actually inside a paragraph. */
.rc-tip-body .rc-tip-more { display: inline; margin: 0 0 0 5px; }
.rc-tip-more:hover { text-decoration: underline; }
.rc-tip-close {
    flex: none; border: 0; background: none; padding: 2px 4px;
    color: var(--rc-muted); font-size: .8rem; line-height: 1; cursor: pointer;
}
.rc-tip-close:hover { color: var(--rc-text); }

/* ---- Add to Home Screen (/install) --------------------------------------

   Every platform's steps are in the page at all times and public/js/install.js
   only marks which one looks like this device — so these rules make the match
   obvious without hiding the rest. A guessed platform costs a scroll, never
   the answer, and the page reads correctly with no JavaScript at all.

   The `display: none` line at the end is not redundant. Three of these are
   flex containers and are drawn with the `hidden` attribute set, and a
   `display` declaration beats the browser's own `[hidden]` rule — without it
   they would all be visible until the script decided otherwise. */
.rc-install-plats { display: flex; flex-direction: column; gap: 14px; margin: 22px 0; }
.rc-install-plat {
    padding: 4px 18px 14px;
    background: var(--rc-card);
    border: 1px solid var(--rc-border);
    border-radius: 12px;
}
/* Pulled to the front of the list rather than singled out by colour alone. */
.rc-install-plat.is-yours { order: -1; border-color: var(--rc-accent); }
.rc-install-plat h2 { font-size: 1.05rem; margin-top: 14px; }
.rc-install-badge {
    display: none;
    margin-left: 8px; padding: 2px 8px;
    border-radius: 999px;
    background: var(--rc-accent-pale); color: var(--rc-accent-dark);
    font-size: .72rem; font-weight: 700; vertical-align: middle;
}
.rc-install-plat.is-yours .rc-install-badge { display: inline-block; }
.rc-install-steps { margin: 0; padding-left: 22px; }
.rc-install-steps li { margin-bottom: 7px; line-height: 1.55; }

.rc-install-done, .rc-install-warn, .rc-install-note {
    display: flex; align-items: flex-start; gap: 11px;
    padding: 12px 14px; margin: 16px 0;
    border: 1px solid var(--rc-border);
    border-radius: 12px;
    font-size: .9rem; line-height: 1.55;
}
.rc-install-done { background: var(--rc-accent-pale); border-color: var(--rc-accent); }
.rc-install-done > i { color: var(--rc-accent-dark); }
.rc-install-warn { background: var(--rc-warn-pale); border-color: var(--rc-warn); }
.rc-install-warn > i { color: var(--rc-warn); }
.rc-install-note { background: var(--rc-accent-pale); border-color: var(--rc-accent); }
.rc-install-note > i { color: var(--rc-accent-dark); }

.rc-install-done[hidden], .rc-install-warn[hidden], .rc-install-note[hidden],
[data-rc-install-button][hidden], [data-rc-install-manual][hidden] { display: none; }

/* Same trap on the dashboard nudge, which borrows .rc-tip and is drawn
   hidden until the browser confirms this is a phone without the app. */
.rc-tip[hidden] { display: none; }

/* ---- The library shelf ---------------------------------------------------

   Modifiers only. `.rc-brick` is the tile shape for notes, card sets AND the
   shelf, so everything specific to the shelf is named rc-lib- and layered on
   top; touching .rc-brick itself would restyle two screens that never asked.

   The shelf differs from the other two in one way that drives the rest: the
   learner did not write any of it. There is no "opened 2 days ago" and no
   colour they chose, so the facts worth showing are what the set contains,
   and they belong at the top rather than whispered under the buttons. */
.rc-lib-meta {
    display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px;
    margin-top: 6px; font-size: .78rem; color: var(--rc-muted);
}
.rc-lib-count {
    padding: 2px 9px; border-radius: 99px; font-weight: 700;
    background: var(--rc-nc-pale, var(--rc-accent-pale));
    color: var(--rc-nc-ink, var(--rc-accent-dark));
}
.rc-lib-taken {
    display: inline-flex; align-items: center; gap: 4px;
    color: var(--rc-accent-dark); font-weight: 600; text-decoration: none;
}
.rc-lib-taken:hover { text-decoration: underline; }
/* Outlined rather than filled, and not the accent: it is a limitation, not
   an achievement, and must not read as the "In your sets" badge above. */
.rc-lib-ref {
    padding: 1px 8px; border-radius: 99px;
    border: 1px solid var(--rc-border); color: var(--rc-muted); font-weight: 600;
}
/* "Popular to play" / "Popular to take", beside the card count and the
   play-only chip.

   Deliberately NOT a pill. .rc-lib-count is a solid fill and .rc-lib-ref is
   outlined, so both shapes on this row are spoken for, and this file's rule
   on new chips says to reach for a different SHAPE rather than a new hue —
   a third colour here would have to clear 10 dE from both neighbours under
   protanopia and deuteranopia in ten themes, which no warm hue manages.
   Unenclosed text with an icon is that third shape and needs no tuning.

   --rc-accent-dark rather than the row's inherited --rc-muted: it is the
   same ink as the count chip, and the lift is in lightness, which is the
   channel that survives colour blindness. */
.rc-lib-pop {
    display: inline-flex; align-items: center; gap: 5px;
    color: var(--rc-accent-dark); font-weight: 600; white-space: nowrap;
}
.rc-lib-pop .bi { font-size: .9em; opacity: .85; }

/* The description's slot is reserved whether or not there is one, so every
   collapsed tile is the same height. The grid is top-aligned on purpose —
   an opened preview must not stretch its row-mates — which is exactly what
   left the shelf ragged once one set had a description and four did not. */
.rc-lib-body { min-height: 2.6rem; }
.rc-lib-excerpt { -webkit-line-clamp: 2; }
/* A question quoted from the set, where the set has no description. Labelled
   so it cannot be mistaken for one, and the label carries the weight so the
   question itself reads as the content it is. */
/* The subject on a learner's tile. Small and above the name: it is context
   for the name, not a second name — and it is the word that used to be the
   first thing in the title on twenty tiles in a row. */
.rc-lib-subject-tag {
    font-size: .64rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
    color: var(--rc-nc-ink, var(--rc-accent-dark)); opacity: .8; margin-bottom: 2px;
}

.rc-lib-taster-label {
    font-weight: 700; color: var(--rc-nc-ink, var(--rc-accent-dark));
    text-transform: uppercase; letter-spacing: .04em; font-size: .68rem; margin-right: 5px;
}

.rc-lib-foot {
    display: flex; align-items: center; gap: 8px;
    margin-top: auto; padding-top: 10px; border-top: 1px solid var(--rc-border);
}
/* The library as a list. One row per set, wrapping to a second line on a
   phone so the name is never squeezed to nothing by the controls beside it.
   The preview spans the whole row underneath, which is why the row is a wrap
   container rather than a plain flex line. */
/* Named rc-shelf-, not rc-lib-: .rc-lib-row is already the ADMIN library
   manager's row, and it is a grid of fixed columns. A flex rule of the same
   name further down this file would have quietly taken that screen apart. */
.rc-shelf-list { display: flex; flex-direction: column; gap: 6px; }
.rc-shelf-row {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    background: var(--rc-card); border: 1px solid var(--rc-border);
    border-radius: 12px; padding: 10px 12px;
}
.rc-shelf-row-main { flex: 1 1 120px; min-width: 0; }
.rc-shelf-row-name { font-weight: 700; }
.rc-shelf-row-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 3px; }
.rc-shelf-row-meta .rc-lib-subject-tag { margin: 0; }
.rc-shelf-row-peek { flex: 1 0 100%; margin: 4px 0 0; padding-left: 2.2em; color: var(--rc-muted); }
.rc-shelf-row-peek li { margin-bottom: 2px; }

/* The tick that replaced the "In your sets" badge. Beside the name, where
   it reads as a fact about the set rather than a third thing to parse. */
/* Filled, and no smaller than the name beside it. A hairline bi-check2 at
   .85em is a typographic mark rather than a status: at that weight it reads
   as punctuation after the title and people miss that they already hold the
   set. A solid disc carries at small sizes where a thin stroke does not. */
.rc-lib-tick { color: var(--rc-accent-dark); font-size: 1em; margin-left: 4px; }

/* What else a set can do, inside its preview: the two icons that used to sit
   on the face of every tile, with their names on. */
.rc-lib-more { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.rc-lib-kinds { font-size: .78rem; color: var(--rc-muted); }

.rc-lib-extras { display: flex; align-items: center; gap: 4px; margin-left: auto; }
/* Preview is a way of looking before deciding, not a decision. */
.rc-lib-quiet { border-color: transparent; background: none; color: var(--rc-muted); }
.rc-lib-quiet:hover { color: var(--rc-text); background: var(--rc-bg); }
/* Bordered and in the accent rather than grey and bare. Carrying no label,
   these have nothing but their own weight to say they can be pressed — at a
   muted 15px glyph in a borderless box they read as decoration sitting beside
   the buttons rather than as two more of them. */
.rc-lib-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0; border-radius: 9px;
    border: 1px solid var(--rc-border); background: none;
    color: var(--rc-accent-dark); font-size: 1.1rem; cursor: pointer;
    text-decoration: none;
}
.rc-lib-icon:hover { background: var(--rc-accent-pale); border-color: var(--rc-accent); }
.rc-lib-icon:focus-visible { outline: 2px solid var(--rc-accent); outline-offset: 1px; }

/* A tool, not a sixth set: no card ground, no shadow, and the left edge says
   it belongs to the whole shelf rather than to any tile below it. */
.rc-lib-strip {
    padding: 11px 14px;
    border: 1px solid var(--rc-border);
    border-left: 3px solid var(--rc-accent);
    border-radius: 0 12px 12px 0;
}
.rc-lib-strip-icon { color: var(--rc-accent); font-size: 1.1rem; }
/* Shrinkable, so a long sentence wraps within itself rather than pushing the
   button onto a line of its own. */
.rc-lib-strip-text { flex: 1 1 200px; min-width: 0; margin-right: auto; }

/* ---- Review with no signal ----------------------------------------------

   Its own screen (resources/views/study/offline.blade.php), and its own
   document: the app layout loads Livewire from the server, and a page whose
   scripts cannot load is a page that cannot open. So these rules cannot lean
   on anything the layout would have set up.

   .rc-focused on <body> zeroes --rc-tabbar-h, so nothing here needs to hold
   clear of a tab bar that is not drawn. */
/* Settings → Offline. The state is a tinted panel with a round badge, so the
   answer to "am I ready?" reads before any of the words; the actions follow
   as one column on a phone — the main one first and filled — instead of
   three buttons of three widths hanging off the left edge. From sm up they
   sit in a row. */
.rc-offline-status {
    display: flex; align-items: center; gap: 12px; margin: 0;
    padding: 12px 14px; border-radius: 14px;
    background: var(--rc-bg); border: 1px solid var(--rc-border);
}
.rc-offline-status.is-ready { background: var(--rc-accent-pale); border-color: transparent; }
.rc-offline-status.is-none { background: var(--rc-warn-pale); border-color: transparent; }
.rc-offline-badge {
    flex: none; width: 36px; height: 36px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--rc-card); color: var(--rc-muted); font-size: 1.15rem;
}
.rc-offline-status.is-ready .rc-offline-badge { background: var(--rc-accent-fill); color: var(--rc-on-accent); }
.rc-offline-status.is-none .rc-offline-badge { background: var(--rc-warn); color: var(--rc-card); }
.rc-offline-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.rc-offline-actions .btn { width: 100%; }
@media (min-width: 576px) {
    .rc-offline-actions { flex-direction: row; flex-wrap: wrap; }
    .rc-offline-actions .btn { width: auto; }
}

.rc-offline-wrap { max-width: 34rem; margin: 0 auto; padding: 16px 16px 40px; }
.rc-offline-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.rc-offline-state { margin-left: auto; display: inline-flex; align-items: center; gap: 7px; font-size: .82rem; color: var(--rc-muted); }
/* Hollow when there is no connection, filled when there is — a shape, not a
   shade. Grey against green is the pair this stylesheet already refuses to
   rely on elsewhere, and a muted grey-green dot beside the words "No
   connection" read as a green light saying the opposite.

   Hollow is also the right default: a dot that starts filled would be
   claiming a connection before anything had checked for one. */
.rc-offline-dot {
    width: 10px; height: 10px; border-radius: 99px; flex: none;
    border: 2px solid var(--rc-muted); background: transparent;
}
.rc-offline-dot.is-on { border-color: var(--rc-accent-fill); background: var(--rc-accent-fill); }

/* The set picker. A scrolling row rather than a wrapping one: ten chemistry
   sets would otherwise push the card itself below the fold, which on the one
   screen somebody opened to study is the wrong thing to spend the space on.
   
   The scrollbar is deliberately NOT hidden here, unlike .rc-filters above.
   That one hides it in its own rule and gets it back under
   @media (pointer: fine) — which works only because its base rule comes
   BEFORE that block. This one is defined after it, so the same two lines at
   equal specificity would win on source order and leave a mouse with a
   sideways row it cannot move: the last set unreachable, and shift+wheel is
   not an answer anybody knows.
   
   So nothing is hidden, and the fine-pointer block further up only styles
   the bar it now knows will be there. Touch gets its platform's own overlay
   scrollbar, which takes no space anyway. */
.rc-offline-sets {
    display: flex; gap: 8px; margin-bottom: 16px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
}
.rc-offline-set {
    display: inline-flex; align-items: center; gap: 7px; flex: none;
    min-height: 40px; padding: 0 14px; border-radius: 99px;
    border: 1px solid var(--rc-border, #D3E3DE);
    background: var(--rc-card, #F0F5F5); color: var(--rc-text, #1B3B35);
    font: inherit; font-size: .85rem; font-weight: 600; white-space: nowrap; cursor: pointer;
}
/* Filled, not merely tinted — the same reason the tab bar stopped relying on
   a change of colour alone. */
.rc-offline-set.is-on {
    background: var(--rc-accent-fill, #0F6E56);
    border-color: var(--rc-accent-fill, #0F6E56);
    color: var(--rc-on-accent, #fff);
}
.rc-offline-set b { font-weight: 700; opacity: .75; font-variant-numeric: tabular-nums; }

.rc-offline-card {
    display: flex; flex-direction: column; gap: 14px;
    background: var(--rc-card); border: 1px solid var(--rc-border);
    border-radius: 16px; padding: 18px 16px;
}
.rc-offline-count { font-size: .78rem; color: var(--rc-muted); }
.rc-offline-count b { color: var(--rc-text); }
.rc-offline-face { font-size: 1.1rem; font-weight: 600; line-height: 1.45; overflow-wrap: anywhere; white-space: pre-wrap; }
/* Shown, never clickable: the grading on this screen is the learner's own. */
.rc-offline-options { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.rc-offline-options li {
    border: 1px solid var(--rc-border); border-radius: 10px;
    padding: 8px 12px; font-size: .9rem; color: var(--rc-muted);
}
.rc-offline-back { border-top: 1px solid var(--rc-border); padding-top: 14px; }
.rc-offline-answer { font-size: 1.05rem; font-weight: 600; color: var(--rc-accent-dark); overflow-wrap: anywhere; white-space: pre-wrap; }
.rc-offline-why { margin-top: 8px; font-size: .88rem; color: var(--rc-muted); white-space: pre-wrap; }

/* The review screen's three grades in one row. "Incorrect", the longest
   label, fits a third of a 320px phone, so there is no wrapping to do — and a
   two-column grid would strand the third button alone on a row of its own. */
.rc-offline-grade { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
/* .rc-offline-btn, not .btn: Bootstrap is on a CDN and therefore just as
   unreachable as the server on this screen. */
.rc-offline-grade .rc-offline-btn { flex-direction: column; gap: 2px; padding: 8px 10px; }
.rc-offline-grade small { font-weight: 600; opacity: .75; font-size: .72rem; }

.rc-offline-empty {
    background: var(--rc-card); border: 1px solid var(--rc-border); border-radius: 16px;
    padding: 30px 20px; text-align: center; color: var(--rc-muted);
}
.rc-offline-empty .bi { font-size: 1.8rem; color: var(--rc-accent); }
.rc-offline-empty p { margin: 10px 0 0; }
.rc-offline-hint { font-size: .86rem; max-width: 40ch; margin-left: auto; margin-right: auto; }

.rc-offline-foot {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin-top: 14px; font-size: .84rem; color: var(--rc-muted);
}
.rc-offline-foot .btn { margin-left: auto; }

/* ---- The two screens that must render with NO network ------------------

   Bootstrap comes from a CDN (see components/layouts/app.blade.php), so it is
   exactly as unreachable as the server is. These two pages therefore cannot
   use a single Bootstrap class: no .btn, no .list-group, no icon font. They
   get everything from here instead, and this file is in the service worker's
   shell, so it is the one stylesheet guaranteed to be on the phone.

   That is also why there is a font stack below. Nothing else in the app needs
   one — Bootstrap sets it — and without it these pages render in the
   browser's serif default, which is how a working screen still manages to
   look broken. */
/* Every panel on these screens is shown and hidden with the `hidden`
   attribute, and six of them set `display` in their own class — which beats
   the browser's own `[hidden] { display: none }` outright. Without this the
   whole screen renders at once: the empty state, the card, the grade buttons
   and the outbox all stacked on top of each other.
   
   The trap .ai/rules/js.md already records for .rc-tip and .rc-install-*.
   One rule here rather than ten, because the next panel added to these
   screens would otherwise have to remember it. Two classes of specificity
   beats every single-class display rule below. */
.rc-offline-page [hidden] { display: none; }

.rc-offline-page {
    margin: 0;
    background: var(--rc-bg, #E8EEED);
    color: var(--rc-text, #1B3B35);
    font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}
.rc-offline-page h1 { font-size: 1.2rem; margin: .85rem 0 .35rem; }
.rc-offline-page p { margin: 0 0 12px; }

/* One button shape, in two weights. */
.rc-offline-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    min-height: 44px; padding: 0 18px; border-radius: 12px;
    border: 1px solid var(--rc-border, #D3E3DE);
    background: var(--rc-card, #F0F5F5); color: var(--rc-accent-dark, #0F6E56);
    font: inherit; font-size: .95rem; font-weight: 700; text-decoration: none; cursor: pointer;
}
.rc-offline-btn.is-strong {
    background: var(--rc-accent-fill, #0F6E56); border-color: var(--rc-accent-fill, #0F6E56);
    color: var(--rc-on-accent, #fff);
}
.rc-offline-btn:disabled { opacity: .5; cursor: default; }
/* The one answer that sends a card back to the start. */
.rc-offline-btn.is-again { color: var(--rc-danger, #7A2438); border-color: var(--rc-danger-pale, #F6DDDA); background: var(--rc-danger-pale, #F6DDDA); }

/* The way in, on the page you land on with no signal. Without it both offline
   features are unreachable exactly when they are wanted: the notes list and
   the dashboard are Livewire, so neither is cached. */
.rc-offline-door {
    display: flex; align-items: center; gap: 14px;
    background: var(--rc-accent-pale, #D7F4EB); color: var(--rc-accent-dark, #0F6E56);
    border: 1px solid var(--rc-border, #D3E3DE); border-radius: 16px;
    padding: 16px; text-decoration: none; margin-bottom: 26px;
}
.rc-offline-door span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.rc-offline-door b { font-size: 1rem; }
.rc-offline-door small { color: var(--rc-muted, #52716C); font-size: .83rem; line-height: 1.45; }
.rc-offline-saved-head {
    font-size: .75rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--rc-muted, #52716C); margin: 0 0 8px;
}
.rc-offline-saved { display: flex; flex-direction: column; gap: 8px; }
.rc-offline-saved a {
    display: block; padding: 13px 14px; min-height: 44px;
    background: var(--rc-card, #F0F5F5); border: 1px solid var(--rc-border, #D3E3DE);
    border-radius: 12px; color: var(--rc-text, #1B3B35); text-decoration: none;
    font-weight: 600; overflow-wrap: anywhere;
}
/* The way to the diagnostic. Quiet — it is for the day something is wrong,
   not for every day. */
/* The quiet line under the Try again button, on both offline screens.
   
   `p.` is load-bearing, not decoration. `.rc-offline-page p` above sets
   `margin: 0 0 12px` at specificity (0,1,1), which beats a bare
   `.rc-offline-help` (0,1,0) whatever the order — so the 18px top margin
   this rule asked for was never applied and the line sat flush against the
   button, touching it. Matching the element makes the two equal and the
   later rule wins. Check the computed value, not the declaration, if this
   ever looks wrong again.
   
   28px rather than the 18px it meant to have: this is a footnote under a
   button, and the gap has to say so. */
p.rc-offline-help { margin: 28px 0 0; text-align: center; font-size: .82rem; color: var(--rc-muted, #52716C); }
.rc-offline-help a { color: var(--rc-accent-dark, #0F6E56); font-weight: 700; }

.rc-offline-none { font-size: .88rem; text-align: center; max-width: 34ch; margin: 0 auto; color: var(--rc-muted, #52716C); }

/* The two marks on Settings → Offline. Existing tokens rather than new
   colours, and they are never on screen together — one state at a time — so
   this is not a pair the 10 dE rule has anything to say about. The words
   beside each ("Ready to study offline" / "Nothing saved yet") carry the
   meaning on their own; the colour is only agreeing with them. */
.rc-offline-yes { color: var(--rc-accent-dark); }
.rc-offline-not { color: var(--rc-warn); }

/* The banner on a page served from the cache./* The banner on a page served from the cache. Injected by recall.js rather
   than rendered, because the server was not reachable to render it. */
.rc-stale {
    position: fixed; left: 0; right: 0; top: 0; z-index: 1040;
    display: flex; align-items: center; justify-content: center;
    height: var(--rc-stale-h); padding: 0 14px;
    background: var(--rc-warn-pale); color: var(--rc-warn);
    font-size: .84rem; font-weight: 600; text-align: center;
}
.rc-stale .bi { margin-right: 6px; }

/*
 * The banner is fixed at the top and the app's own bar is sticky at the top,
 * so the banner sat ON the bar — covering the logo and the streak and level
 * pills, which reads as a broken header rather than a notice.
 *
 * Both offsets come from one declared height rather than from measuring the
 * banner's padding, for the same reason bottom-docked things derive theirs
 * from --rc-tabbar-h: a number repeated in three places is a number that
 * stops matching.
 */
body:has(.rc-stale) { padding-top: var(--rc-stale-h); }
body:has(.rc-stale) .rc-topbar { top: var(--rc-stale-h); }

/*
 * A note read with no signal is the note and nothing else.
 *
 * The page comes out of the cache exactly as the server rendered it, so it
 * arrives carrying the whole reading surround: Previous and Next, Create
 * card, Cards from this note, Links to, Linked from, Related by tag, Share,
 * and the ⋯ menu of edit, export, archive and delete. Every one of those
 * needs the server. Offline they are controls that look live, do nothing when
 * pressed, and sit between somebody and the one thing on the page that does
 * work — which is the note they opened it to read.
 *
 * Hidden here rather than left out of the markup because there is no server
 * to leave it out: `rc-saved-note` is put on <body> by recall.js when the
 * page is on screen with no connection, and taken off again when one returns.
 *
 * display: none rather than dimming, so the links leave the tab order and the
 * screen reader with them. A control that cannot work should not be reachable
 * by any route.
 */
body.rc-saved-note .rc-read { grid-template-columns: minmax(0, 1fr); }
/* The whole action group in the head — Make cards and the ⋯ menu — rather
   than each control, so a third button added there is covered without
   anybody remembering this rule. The back link is its sibling and stays:
   with no connection /notes falls through to the offline page, which lists
   the notes that can still be read. */
body.rc-saved-note .rc-stepper,
body.rc-saved-note .rc-read-bar,
body.rc-saved-note .rc-sel-cta,
body.rc-saved-note .rc-read-rail { display: none; }

/* This one needs !important and no amount of specificity would do instead:
   the action group is a Bootstrap `.d-flex`, and every Bootstrap utility
   declares `!important`. Without it the rule matches, loses, and the buttons
   stay on screen — which is exactly what the first attempt did. */
body.rc-saved-note .rc-page-head > .d-flex { display: none !important; }

/* ---- Tutorial videos ------------------------------------------------------

   The recorded walkthroughs (components/tutorial-video.blade.php). The video
   and its chapter list sit side by side from lg up and stack below it; the
   chapters are the table of contents, so they stay on screen while the video
   plays rather than hiding behind the player's own menu. */
.rc-tutorial { display: grid; gap: 16px; }
.rc-tutorial-video { display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 12px; background: #0E1A17; }
.rc-tutorial-chapters { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; align-content: start; }
.rc-tutorial-chapter {
    display: flex; gap: 12px; align-items: flex-start; width: 100%; min-height: 44px;
    padding: 8px 10px; border: 1px solid transparent; border-radius: 10px;
    background: none; color: var(--rc-text); text-align: left;
}
.rc-tutorial-chapter:hover { background: var(--rc-card); }
.rc-tutorial-chapter:focus-visible { outline: 2px solid var(--rc-accent); outline-offset: 1px; }
.rc-tutorial-chapter.is-current { background: var(--rc-accent-pale); border-color: var(--rc-border); }
.rc-tutorial-time { flex: none; min-width: 38px; padding-top: 1px; font-size: .8rem; font-variant-numeric: tabular-nums; color: var(--rc-muted); }
.rc-tutorial-chapter-text { display: flex; flex-direction: column; min-width: 0; }
.rc-tutorial-chapter-title { font-weight: 600; font-size: .92rem; }
.rc-tutorial-chapter-summary { font-size: .8rem; color: var(--rc-muted); }
.rc-tutorial-chapter.is-current .rc-tutorial-time,
.rc-tutorial-chapter.is-current .rc-tutorial-chapter-summary { color: var(--rc-accent-dark); }
@media (min-width: 992px) {
    .rc-tutorial { grid-template-columns: minmax(0, 1fr) 260px; }
}
.rc-tutorial-modal .modal-body { padding: 16px; }
/* The guide is a reading column: too narrow for the list beside the video. */
.rc-guide-video { margin: 12px 0 22px; }
.rc-guide-video .rc-tutorial { grid-template-columns: minmax(0, 1fr); }
.rc-guide-video .rc-tutorial-chapters li { margin: 0; line-height: 1.4; }
.rc-tutorial-intro { margin: 0 0 12px; color: var(--rc-muted); font-size: .9rem; }

/* ---- Touch targets on a phone -------------------------------------------

   44px is the floor, not something to get near. The .btn rules near the top of
   this file work through Bootstrap's --bs-btn-padding-* variables; everything
   below sets the `padding` shorthand, an explicit width/height, or its own
   min-height — all of which beat those variables outright. So a phone kept
   getting a 27px ⋯ menu and a 31px folder row no matter what the button rules
   said, and an audit of the notes screen found twelve such controls.

   Two things keep this working. It sits at the END of the stylesheet, because
   every selector here is also defined further up at equal specificity and
   source order is what decides. And it holds ONE rule per selector: a second
   one lower down silently wins, which is how .rc-folder-actions button was
   pinned to 42px while a 44px rule sat above it doing nothing. */
@media (pointer: coarse), (any-pointer: coarse) {
    /* Most buttons in the app are .btn-sm. */
    .btn-sm { --bs-btn-padding-y: .68rem; --bs-btn-padding-x: .95rem; }

    /* The ⋯ menus — a note card's and a folder row's. */
    .rc-brick .dropdown .btn,
    .rc-folder-menu { padding: 11px 14px; }
    /* Grown to 44px the ⋯ kept the mouse-sized button's top: 10px, which put
       its dots 11px below the title's line and onto the excerpt. Anchored to
       the tile's corner instead, the dots sit on the first line of the title
       however many lines it wraps to; the extra target is padding, off the
       text's side of the corner. */
    .rc-brick .dropdown { top: 0; right: 0; }

    /* Controls that set their own height and so never saw the .btn rules.
       .rc-folder is the row you press to open a folder at all: it was 31px. */
    .rc-folder,
    .rc-folders-toggle,
    .rc-filter { min-height: 44px; }
    /* .78rem is a caption size on a control that names an action, and Delete
       is one of them. Matched to .btn-sm's 14px: the target was fixed first
       and the label left behind, which reads as small either way. */
    .rc-folder-actions button { min-height: 44px; padding: 0 14px; font-size: .875rem; }
    .rc-seg button { min-height: 44px; min-width: 44px; padding: 0 12px; }

    /* Menu rows, through Bootstrap's own variable so a row grows by padding
       and its label stays centred. Real actions live behind a ⋯ now — Edit,
       and Delete under it — so a 32px row above a destructive one was a
       mis-tap waiting to happen. */
    .dropdown-menu { --bs-dropdown-item-padding-y: .625rem; }

    /* The drag handle and the disclosure arrow stay narrower than 44px, and
       that is a decision rather than an oversight: both sit inline with the
       folder name, and giving each a thumb's width would leave the name
       nothing. Neither is the only way to do its job — the ⋯ carries Move up
       and Move down, and a folder opens by its name — so they take the full
       row height, which is free, and keep their width. */
    .rc-grip { width: 30px; height: 44px; }
    .rc-folder-row:hover .rc-grip,
    .rc-folder-row:focus-within .rc-grip { width: 30px; }
    .rc-twisty { width: 30px; height: 44px; }
    @media (hover: none) {
        .rc-folder-row .rc-grip { width: 30px; }
    }

    /* The card's handle is absolutely positioned over the text, so height here
       costs the excerpt a line rather than the row a few pixels. */
    .rc-brick-grip { height: 26px; }
    .rc-brick.is-sortable .rc-brick-title { padding-left: 30px; }

    .rc-brick.is-sortable.is-selecting .rc-brick-grip { left: 36px; }
    .rc-brick.is-sortable.is-selecting .rc-brick-title { padding-left: 72px; }

    /* Clear of the bigger ⋯ riding above it. */
    .rc-brick-title { padding-right: 46px; }

    /* Play and Challenge on a shelf tile carry no label, so nothing else
       grows them to a thumb's width the way a button's text does. They are
       the only way to reach either action from here, which is what puts them
       over the 44px floor rather than at the grip's 30px exception. */
    /* The glyph grows with the box. Sizing only the target leaves a thumb's
       width of empty space around a 15px icon, which looks like a mistake
       rather than like something meant to be pressed. */
    .rc-lib-icon { width: 44px; height: 44px; font-size: 1.25rem; }

    /* The admin shelf's filter chips: a real control carrying a word each,
       so nothing else grows them to a thumb's width. */
    /* gap, not a space: inline-flex makes the number and the label
       separate flex items and the whitespace between them collapses —
       "12sets". */
    .rc-lib-chip { min-height: 44px; padding: 0 16px; display: inline-flex; align-items: center; gap: .3em; }

    /* The All / My cards / Library row inside the Play picker. Missed when
       the rest of this block was written, and it is the first thing a thumb
       reaches for once the picker opens. */
    .rc-picker-chip { min-height: 44px; padding: 0 16px; }

    /* Found by walking every screen at 375px with a coarse pointer and
       measuring each control (2026-09-15). Each was under 40px; several were
       the ONLY way to do their job — opening a card set is the title link.

       Two techniques. A control that can grow simply gets min-height. One
       that sits inside a line of text — the nudge under a note, the "start
       fresh" button in the hero sentence — takes padding and the same amount
       of negative margin, so its hit box is a thumb's height while the line
       it sits in does not move. */
    .rc-brick-body { padding: 11px 0; margin: -11px 0; }
    .rc-note-nudge { display: inline-block; padding: 12px 6px; margin: -12px -6px; }
    .rc-brick-nudge { min-height: 44px; padding: 0 12px; }
    .rc-hero-alt button { padding: 12px 0; margin: -12px 0; }
    .rc-device-remove { min-height: 44px; padding: 0 10px; margin-right: -10px; }
    .rc-pick { min-width: 44px; min-height: 44px; margin: -10px 0 -10px -10px; }
    .rc-pick .form-check-input { width: 22px; height: 22px; margin: 0; }
    /* A card's box is positioned over the corner rather than sitting in a
       row, so it has no line of text to avoid moving and must NOT take the
       negative margin above — measured, that hung 9px of a 44px target off
       the left edge of the card, where nothing can be tapped.

       Written here rather than with the other card rules because .rc-pick and
       .rc-brick-pick weigh exactly the same and the last one wins; up there
       these lost silently. min-width as well as width, for the same reason:
       .rc-pick's min-width held the narrowed one open at 44. */
    .rc-brick-pick { top: 0; left: 0; width: 44px; min-width: 44px; height: 44px; margin: 0; }
    .rc-brick.is-selecting .rc-brick-title { padding-left: 36px; }
    .rc-brick.is-sortable.is-selecting .rc-brick-pick { width: 36px; min-width: 36px; }
    /* The whole palette wraps to fit; the coloured dot inside stays its size. */
    .rc-swatches { max-width: none; }
    .rc-swatch { width: 44px; height: 44px; }
    .rc-swatch span { inset: 10px; }
    .rc-swatch-custom { width: 44px; height: 44px; }
    .rc-swatch-custom input { width: 44px; height: 44px; }
    /* The guide's contents on a phone: the rail is desktop-only, so these
       are the links a thumb actually meets. */
    .rc-guide-jump-list a { min-height: 44px; }
    .rc-guide-back a { min-height: 44px; padding: 0 8px; }
    .rc-land-final .text-white { display: inline-flex; align-items: center; min-height: 44px; }
    .rc-lib-name b a { display: inline-flex; align-items: center; min-height: 44px; }
    .rc-lib-subject .form-select-sm { min-height: 44px; }
    /* Same treatment for the admin list bar: both are form-*-sm, which is
       34px, and on the users screen the search is the only way to find
       anyone once the list is longer than a screen. */
    .rc-listbar-search, .rc-listbar-tools .form-select { min-height: 44px; }
    /* Pair Up's two columns. They set the padding shorthand, so a one-line row
       came out at 35px — and a mis-tap here is not a wasted press but a miss
       against a card that only allows three. `justify-content: center` so the
       height added is space around the term rather than above it. */
    .rc-pair-row, .rc-pair-opt { min-height: 44px; justify-content: center; }
}
