/* SM3 (Securemail v3) -- theme stylesheet.
 *
 * Loaded by the secure layout as /app/themes/sm3/theme.css, IN ADDITION to the
 * app's main.css. Everything here is plain CSS on purpose: it is served as a
 * static file and never passes through Tailwind, so a theme can be dropped in
 * without rebuilding the host. Do not use @apply or Tailwind utility classes in
 * this file -- they will not be compiled.
 *
 * This file is the whole design: tokens, fonts, nav, shell geometry and rail.
 * Nothing about SM3 remains in source.css. `zip -r sm3.zip .` from this folder
 * is a distributable theme. See .planning/design-theming/THEME-PACKAGING.md.
 */

/* ---------------------------------------------------------------------------
   DESIGN TOKENS.
   Hand-maintained plain CSS, copied verbatim from what daisyUI compiled for the
   `@plugin "daisyui/theme"` blocks that used to live in source.css. They are
   here so the folder is self-contained: a theme can be dropped in and take
   effect without rebuilding the host's Tailwind.

   The :root:has(input.theme-controller[value=...]:checked) half is daisyUI's
   own selector for its checkbox theme-controller, kept so the light/dark toggle
   behaves exactly as before. The app's real switch is server-side (?design= /
   the ctxc cookie) plus setThemeInDom(), which sets <html data-theme>.

   Palette source: ../securemail-ui v3.0.140 -- accent #f16d00 and its
   desaturated green-grey ramp. See THEME-PACKAGING.md.
   --------------------------------------------------------------------------- */

:root:has(input.theme-controller[value=sm3-v1]:checked),
[data-theme="sm3-v1"] {
	color-scheme:light;
	--color-base-100:#fff;
	--color-base-200:#f1f3f7;
	--color-base-300:#dee0e8;
	--color-base-content:#2a2f2e;
	--color-primary:#f16d00;
	/* WHITE on orange, matching securemail-ui: TheMainNav.vue pairs
	   hover:dark:bg-emerald-500 (their `emerald` is this orange) with
	   hover:dark:text-white, and the shipped UI shows #ffffff glyphs on #f16d00.
	   KNOWN TRADE-OFF: 3.03:1, which meets WCAG AA for large text only. It was
	   briefly changed to #262D2B (4.64:1) and reverted -- brand parity with
	   securemail was chosen over the contrast gain (Holden, 2026-07-30). If that
	   is revisited, #262D2B is the value; #2A2F2E misses at 4.49. */
	--color-primary-content:#fff;
	--color-secondary:#43454d;
	--color-secondary-content:#fff;
	/* Accent DELIBERATELY equals primary. It was orange-500 #f88748 on dark text,
	   which made "Add new instance" / "Add contact" a paler orange than the nav
	   and than the edit buttons sitting beside them -- two oranges doing one job.
	   --color-accent is consumed ONLY by .btn-accent (12 call sites), so aligning
	   the token is the whole fix, with no template changes. Cost: accent no longer
	   reads as a distinct role, so a genuinely secondary action later needs its
	   own token rather than reaching for btn-accent. */
	--color-accent:#f16d00;
	--color-accent-content:#fff;
	--color-neutral:#343938;
	--color-neutral-content:#f1f3f7;
	--color-info:#2475bf;
	--color-info-content:#fff;
	--color-success:#1ab883;
	--color-success-content:#fff;
	--color-warning:#f1ae00;
	--color-warning-content:#2a2f2e;
	--color-error:#ff6767;
	--color-error-content:#fff;
	--radius-selector:8px;
	--radius-field:8px;
	--radius-box:12px;
	--border:1px;
}

:root:has(input.theme-controller[value=sm3-v1-dark]:checked),
[data-theme="sm3-v1-dark"] {
	color-scheme:dark;
	--color-base-100:#404746;
	--color-base-200:#343938;
	--color-base-300:#2a2f2e;
	--color-base-content:#dee0e8;
	--color-primary:#f16d00;
	/* White on orange, as in light -- see the trade-off note in that block. */
	--color-primary-content:#fff;
	--color-secondary:#9597a1;
	--color-secondary-content:#2a2f2e;
	/* Matches primary, as in light -- see the note in that block. */
	--color-accent:#f16d00;
	--color-accent-content:#fff;
	--color-neutral:#262d2b;
	--color-neutral-content:#dee0e8;
	--color-info:#2475bf;
	--color-info-content:#fff;
	--color-success:#1ab883;
	--color-success-content:#2a2f2e;
	--color-warning:#f1ae00;
	--color-warning-content:#2a2f2e;
	--color-error:#ff6767;
	--color-error-content:#2a2f2e;
	--radius-selector:8px;
	--radius-field:8px;
	--radius-box:12px;
	--border:1px;
}


/* Chart text tracks base-content: gray-900 on light, gray-100 on dark. Dark
   carries MORE alpha than light: the label sits on the card rather than the page,
   and dark mode leaves less contrast between the two to spend. */
:root[data-theme="sm3-v1"] {
	--chart-text: rgba(42, 47, 46, 0.7);
	--chart-grid: rgba(42, 47, 46, 0.12);
	--map-selected-stroke: #424242;
}

:root[data-theme="sm3-v1-dark"] {
	--chart-text: rgba(222, 224, 232, 0.92);
	--chart-grid: rgba(222, 224, 232, 0.12);
	--map-selected-stroke: #ececec;
}

/* Fonts are a theme capability: a design overrides --font-display (consumed by
   .font-primary) and its body font-family in its own [data-theme] block. SM3 uses
   a clean system-ui stack, distinct from the brand Taruno Wide. Swap in a bespoke
   SM3 face: drop a woff2 in internal/static/fonts/, add an @font-face, and point
   --font-display + font-family below at it. See THEMES.md. */
/* Work Sans -- self-hosted variable font shared with securemail-ui. */
@font-face {
	font-family: "Work Sans";
	src: url("/app/themes/sm3/assets/fonts/WorkSans-VariableFont_wght.ttf") format("truetype");
	font-weight: 100 900;
	font-style: normal;
	font-display: swap;
}

/* :root[data-theme=..] (0,2,0) so these WIN over the :root defaults (0,1,0),
   which are declared later in the file. A plain [data-theme=..] (0,1,0) would lose
   the tie on source order and the base font/tokens would leak into sm3. */
:root[data-theme="sm3-v1"],
:root[data-theme="sm3-v1-dark"] {
	--font-display: "Work Sans", ui-sans-serif, system-ui, sans-serif;
	font-family: "Work Sans", ui-sans-serif, system-ui, sans-serif;
}

/* SM3 sidebar nav: flat items (kill daisyUI's separators/shadows), active = orange
   LEFT bar + tint. Every item reserves a transparent 3px left border so activating
   one does not shift the label. */
[data-theme="sm3-v1"] .menu :where(li > a, li > summary),
[data-theme="sm3-v1-dark"] .menu :where(li > a, li > summary) {
	border: 0;
	border-radius: var(--radius-field, 0.5rem);
	box-shadow: none;
}
/* Active = SOLID primary, matching securemail-ui (its active rail item is a filled
   orange square, not a tint). This replaced a 10%-opacity tint + orange text. */
[data-theme="sm3-v1"] .menu :where(li > *.menu-active),
[data-theme="sm3-v1-dark"] .menu :where(li > *.menu-active) {
	background-color: var(--color-primary);
	color: var(--color-primary-content);
	font-weight: 600;
}
[data-theme="sm3-v1"] .menu :where(li > *.menu-active) .icon,
[data-theme="sm3-v1-dark"] .menu :where(li > *.menu-active) .icon {
	color: var(--color-primary-content);
}

/* SM3: flat -- no panel, tighter gutter. :root[data-theme=..] (0,2,0) so it beats
   the :root token defaults (0,1,0) declared later; else sm3 pages keep the panel. */
:root[data-theme="sm3-v1"],
:root[data-theme="sm3-v1-dark"] {
	--page-gutter: 1rem;
	--surface-bg: transparent;
	--surface-pad: 0;
	--surface-radius: 0;

	/* SM3's primary is the ORANGE accent (#f16d00); white on it is ~3.0:1 and
	   fails WCAG AA for body text. The dark slab lives in neutral, so the auth
	   panel takes neutral here, not primary. */
	--auth-panel-bg: var(--color-neutral);
	--auth-panel-radius: 0.75rem; /* matches sm3's 12px card radius */
}

:root[data-theme="sm3-v1"] {
	--line-strong: #e2e5ea;
}
:root[data-theme="sm3-v1-dark"] {
	--line-strong: rgba(255, 255, 255, 0.14);
}

/* Sidebar rail surface. securemail-ui makes the rail DARKER than everything
   else in dark mode -- TheMainNav.vue is `dark:bg-gray-900 border-r
   dark:border-gray-1000`, and sampling the shipped UI agrees: rail #2a2f2e,
   content #343938, top bar #404746 (the lightest of the three).
   The shell puts the topbar and the aside both on bg-base-100, which is right
   for the topbar (#404746 matches) but leaves the rail three steps too light.
   Hence its own token rather than reusing a base-* step: in LIGHT the rail is
   white like the rest of the chrome (securemail sets no light bg on it), so the
   two modes genuinely differ and base-300 would have made the light rail grey. */
:root[data-theme="sm3-v1"] {
	--sm3-rail-bg: var(--color-base-100);
	--sm3-rail-border: var(--color-base-300);
}
:root[data-theme="sm3-v1-dark"] {
	--sm3-rail-bg: #2a2f2e;        /* gray-900 */
	--sm3-rail-border: #262d2b;    /* gray-1000 */
}

.sm3-sidebar {
	background-color: var(--sm3-rail-bg);
	border-right: 1px solid var(--sm3-rail-border);
}

/* Metric digits: SM3 uses its own numerals, not the base DSEG7 seven-segment
   LED. The tokens and their LED defaults are defined in source.css; this is the
   whole override.

   Work Sans with tabular figures, so fitMetricDigital() -- which measures the
   box once and scales the font to it -- keeps a stable width as the value
   animates. The K/M/B/T suffix drops DSEG14 for the same face at the same
   half-height, and loses the raise: DSEG14's suffix is lifted to sit against
   the top of the segment cells, which only reads correctly next to segment
   digits.

   The phantom-8s ghost goes to opacity 0. It is a segment-display conceit --
   un-lit cells showing through -- and behind proportional numerals it is just
   noise. Zero opacity, NOT display:none: the ghost is what sizes the
   fixed-width box the live value is right-aligned over, so removing it from
   layout would collapse the alignment. Front opacity goes to 1; the 0.8 default
   exists to soften lit LED segments, and there is nothing lit here to soften. */
/* Values taken from the artifact's .kpi .num / .kpi .num .u
   (inputs/sam-artifact/artifact-source.html:259-260):
     digits  82px / weight 700 / navy   / letter-spacing -2px / tabular-nums
     suffix  44px (= 0.54em)   / ORANGE / margin-left 2px
   The two-tone split is the point -- the K/M/B/T rides as an accent rather than
   being part of the number. Sizes are em-based here because the digits are
   fluid (fitMetricDigital scales them to the widget), so -2px at 82px becomes
   -0.024em and 44px becomes 0.54em. */
:root[data-theme="sm3-v1"],
:root[data-theme="sm3-v1-dark"] {
	--metric-digit-font: "Work Sans", ui-sans-serif, system-ui, sans-serif;
	--metric-digit-tracking: -0.024em;
	--metric-unit-font: "Work Sans", ui-sans-serif, system-ui, sans-serif;
	--metric-unit-size: 0.54em;
	--metric-unit-raise: 0;
	--metric-unit-color: var(--color-primary);
	--metric-ghost-opacity: 0;
	--metric-front-opacity: 1;
}

/* SM3 shell geometry. Stacked on mobile; a two-column grid from md up, with the
   sidebar width in ONE custom property so the content column (1fr) follows on
   its own. This was a string of Tailwind arbitrary utilities on the layout div
   (md:[--sm3-sidebar-w:220px], md:grid-cols-[var(--sm3-sidebar-w)_1fr], ...);
   those are only emitted because Tailwind scans internal/views, so they would
   vanish for a theme dropped in after the build. Plain CSS here instead.
   md = 48rem: Tailwind's DEFAULT breakpoint. The --breakpoint-* values in
   source.css's `@plugin "daisyui"` block are daisyUI config and Tailwind does
   not read them, so the compiled queries are 40/48/64/80/96rem. */
.sm3-shell {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
}
@media (min-width: 48rem) {
	.sm3-shell {
		--sm3-sidebar-w: 220px;
		display: grid;
		grid-template-columns: var(--sm3-sidebar-w) 1fr;
		grid-template-rows: auto 1fr;
		min-height: 0;
		height: 100dvh;
		overflow: hidden;
	}
}

/* Sidebar collapse. One class on <body> drives both the rail width and the
   labels. Two classes (0,2,0) outrank the .sm3-shell rule above (0,1,0), so the
   narrow width wins regardless of source order. */
body.sidebar-narrow .sm3-shell {
	--sm3-sidebar-w: 4.5rem;
}

/* Labels outside the nav proper (collapse toggle, version footer) just go. */
body.sidebar-narrow .nav-label {
	display: none;
}

/* Nav labels become fly-outs instead. Pattern lifted from securemail-ui's
   TheMainNav: the label rides inside its own link and is revealed by :hover on
   that link, so it is pure CSS. The previous JS version listened for
   mouseover/mouseout, which BUBBLE from descendants -- crossing from the <a>
   onto the icon inside it fired a leave then an enter and restarted the
   animation, which is what made it flicker. A parent :hover cannot do that. */
body.sidebar-narrow aside .menu a,
body.sidebar-narrow aside .menu summary {
	position: relative;
	/* Fixed clip the label slides right THROUGH, so the slab stays rigid and the
	   text never wipes. It must sit at the link's left EDGE: this clips the link's
	   own contents too, so anything further in slices the icon (50% halved it).
	   The corner leak it used to cause is handled by squaring the backing below,
	   not by moving this.
	   Rounded to match the backing: top/bottom pinned to the link's own box so the
	   `round` has corners at the left edge to act on -- with -100vw there they sit
	   off-screen and the rounding does nothing. Right stays open for the label. */
	clip-path: inset(0 -100vw 0 0 round var(--radius-field));
}

/* Opaque backing for the whole link. daisyUI's own hover background is only 10%
   opaque, so the label passing beneath it showed straight through and never read
   as sliding out from UNDER the link. This pseudo sits above the label (-2) and
   below the link's own background, so the hover tint still paints over it. */
body.sidebar-narrow aside .menu a::before,
body.sidebar-narrow aside .menu summary::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	/* The backing IS the visible slab -- it covers the link's own background -- so
	   the rounding lives here and the link needs none of its own. Safe to round
	   despite sitting over the label: at rest the label is opacity 0, and on hover
	   the backing goes the same colour as it, so a corner bleed cannot show.
	   --radius-field is daisyUI's button/input radius, so the rail tracks whatever
	   the active design uses for buttons instead of pinning a number. */
	border-radius: var(--radius-field);
	/* Matches the RAIL, not base-100. The backing has to be invisible at rest, and
	   the rail is its own surface now (darker than base-100 in dark mode) -- using
	   base-100 here painted a lighter slab behind every icon. */
	background-color: var(--sm3-rail-bg);
}

/* Square off the side the label arrives on, and carry the fly-out's foreground so
   the icon stays legible once the slab goes dark -- the icon is a mask painted
   with currentColor. Same pairing as securemail-ui's hover:bg-emerald-500 +
   hover:text-white against its bg-emerald-500 flyout. */
body.sidebar-narrow aside .menu a:hover,
body.sidebar-narrow aside .menu summary:hover {
	border-top-right-radius: 0;
	border-bottom-right-radius: 0;
	color: var(--color-primary-content);
}

/* The hover background goes on the BACKING, not the link. A negative-z-index
   child paints after its parent's background, so this pseudo covers anything set
   on the link itself -- a colour set there is simply invisible. Squares the edge
   the label arrives on so the two meet as one slab. */
body.sidebar-narrow aside .menu a:hover::before,
body.sidebar-narrow aside .menu summary:hover::before {
	background-color: var(--color-primary);
	border-radius: var(--radius-field) 0 0 var(--radius-field);
}

/* The ACTIVE item's orange has to be set on the backing too. The link's own
   background-color is painted over by this opaque pseudo (negative z-index
   children paint after the parent's background), so the .menu-active rule above
   is invisible in the rail without this. Kept fully rounded at rest -- it only
   squares off on hover, when the label slab arrives to meet it. */
body.sidebar-narrow aside .menu :where(a, summary).menu-active::before {
	background-color: var(--color-primary);
}

/* The ACTIVE item gets no fly-out. Its tile is already solid primary, so the
   label slab -- also primary -- slid out of an identically coloured block and
   just looked like the tile growing sideways. There is also nothing to tell the
   user: they are on that page.
   Kept in the layout (visibility, not display) so the fixed geometry the clip
   and backing depend on is unchanged; only the reveal is suppressed. */
body.sidebar-narrow aside .menu :where(a, summary).menu-active .nav-label,
body.sidebar-narrow aside .menu :where(a, summary).menu-active:hover .nav-label {
	opacity: 0;
	visibility: hidden;
	transform: translateX(-100%);
}

/* The active item is also INERT: it links to the page you are already on, so a
   click is a no-op that costs a full navigation. pointer-events:none also means
   it never enters :hover, which is what actually stops the corner-squaring and
   the hover cue -- both are keyed off :hover.
   Keyboard focus is deliberately left alone: Tab still reaches it and Enter
   re-loads the current page, which is harmless, whereas removing it from the tab
   order would make the nav skip an item for keyboard users only. */
body.sidebar-narrow aside .menu :where(a, summary).menu-active {
	pointer-events: none;
	cursor: default;
}

/* Belt and braces on the radius. Currently unreachable -- pointer-events:none
   above prevents :hover firing at all -- but the generic hover rules square the
   right-hand corners to meet an incoming label slab, and the active item has no
   slab. Kept so relaxing pointer-events later cannot silently reintroduce a tile
   that flattens against nothing. Specificity: 4 classes here vs 3 there. */
body.sidebar-narrow aside .menu :where(a, summary).menu-active:hover,
body.sidebar-narrow aside .menu :where(a, summary).menu-active:hover::before {
	border-radius: var(--radius-field);
}

body.sidebar-narrow aside .menu .nav-label {
	display: flex;
	align-items: center;
	/* Flex strips the literal space between the text and a trailing glyph (Docs
	   carries an external-link icon), so put the spacing back. */
	gap: 0.25rem;
	position: absolute;
	/* Anchored at the link's right edge and pulled back over itself, so at rest it
	   sits exactly under the opaque backing (and the clip takes what overhangs to
	   the left). Its leading edge therefore starts level with the button's right
	   edge: the first pixel of movement is the first pixel visible.
	   It used to rest a further link-width to the left, which meant 41% of the
	   journey elapsed before anything appeared -- felt like the animation lagged
	   behind the hover. Only transform moves now, so it also composites. */
	left: 100%;
	top: 0;
	height: 100%;
	/* Behind the link's opaque backing (-1), so it emerges from UNDER the whole
	   link rather than sliding over it. Works because the link sets no z-index of
	   its own. */
	z-index: -2;
	padding: 0 0.5rem 0 0;
	/* Square where it meets the button, rounded on the far end, so button and label
	   read as one slab with a single cap rather than two rounded pieces. Same
	   --radius-field the backing uses, so both caps match the theme's buttons. */
	border-radius: 0 var(--radius-field) var(--radius-field) 0;
	/* Primary, not neutral: securemail-ui's rail fly-out is the brand accent
	   (hover:bg-emerald-500, which is orange there), and neutral renders as a dark
	   grey slab that reads as a tooltip rather than part of the nav. The link's
	   :hover backing above uses the same pair so button and label are one slab. */
	background-color: var(--color-primary);
	color: var(--color-primary-content);
	font-size: 0.75rem;
	line-height: 1.4;
	white-space: nowrap;
	pointer-events: none;
	opacity: 0;
	transform: translateX(-100%);
	/* Position only. Opacity deliberately snaps rather than fading: a fade would
	   show the slab through the icon while it is still under it.
	   ease-OUT, not ease-in-out: the slow ramp at the start of an ease-in-out is
	   what reads as lead-in lag. This leaves at full speed and settles. */
	transition: transform 0.3s ease-out;
}

body.sidebar-narrow aside .menu a:hover .nav-label,
body.sidebar-narrow aside .menu summary:hover .nav-label {
	opacity: 1;
	transform: translateX(0);
}

/* Bigger icons in the rail -- at 1em they were 14px in a 72px column. Direct
   children only, so the external-link glyph inside the Docs label keeps its
   text-sized proportions. */
body.sidebar-narrow aside .menu a > .icon,
body.sidebar-narrow aside .menu summary > .icon {
	width: 1.5rem;
	height: 1.5rem;
}

@media (prefers-reduced-motion: reduce) {
	body.sidebar-narrow aside .menu .nav-label {
		transition: none;
	}
}

/* With the labels gone, centre what is left instead of leaving it hugging the
   left edge of the rail. */
body.sidebar-narrow aside .menu a,
body.sidebar-narrow aside .menu summary,
body.sidebar-narrow aside .btn {
	justify-content: center;
	padding-inline: 0.5rem;
}

/* daisyUI draws a disclosure arrow on <summary>; with no label beside it the
   rail looks lopsided. */
body.sidebar-narrow aside .menu details > summary::after {
	display: none;
}

/* Sub-menus keep daisyUI's indent (margin 1rem + padding 0.5rem + rule), which
   made the Notifications row 116px wide inside a 71px rail and put a horizontal
   scrollbar under the whole nav. Flatten it, then mark the nesting with a rule of
   our own: a solid left border, which survives the rail where daisyUI's ::before
   connector was getting clipped. 3px + 5px keeps the row inside the rail. */
body.sidebar-narrow aside .menu :where(li ul) {
	margin-inline-start: 0;
	padding-inline-start: 5px;
	border-inline-start: 3px solid color-mix(in oklch, var(--color-base-content) 30%, transparent);
}

/* daisyUI's own nesting connector, which the rail clips into a stub. */
body.sidebar-narrow aside .menu :where(li ul)::before {
	display: none;
}

/* The fly-out has to cross the sidebar edge, but the nav scrolls, and an
   overflow-y: auto box clips its children on BOTH axes. Rather than give up
   scrolling, push the clip boundary out to the right: overflow clips at the
   padding box, so a wide right padding with a matching negative margin moves
   that edge past the rail without changing where anything lays out. */
body.sidebar-narrow aside .sidebar-scroll {
	padding-right: 15rem;
	margin-right: -15rem;
}

/* The inner <ul> is a scroll container too; it has nothing to scroll in the rail
   and would clip the fly-out at 71px. The stacking context lifts the fly-out over
   the sidebar's right border, which otherwise paints across it. */
body.sidebar-narrow aside #side-nav-desktop {
	overflow: visible;
	position: relative;
	/* Above the page's sticky header card (z-30), which otherwise paints over the
	   fly-out where the two overlap. Still below the topbar (z-40) and modals. */
	z-index: 35;
}

/* The toggle's own arrow flips to point the way it will move the sidebar. */
body.sidebar-narrow .sidebar-collapse-icon {
	transform: rotate(180deg);
}
