<?php
/**
 * Portal Theme  -  unified, themable look for all front-end portals.
 *
 * Provides:
 *   * Color presets (CSS custom properties scoped to portal containers).
 *   * Three structurally distinct layouts for the student + teacher
 *     portals (parent/sponsor portal is intentionally untouched):
 *
 *        profile   -  friendly card-stack with cover + avatar (default).
 *        console   -  light SaaS shell with sticky left nav rail.
 *        command   -  dark dashboard shell with sidebar + KPI strip on top.
 *
 * Layouts are applied with a small DOM-rewiring script so we don't have
 * to fork every shortcode template. CSS is scoped to .tsm-portal-wrap so
 * host WordPress themes are unaffected.
 *
 * @package TradeSchoolManager
 * @since   1.19.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class TSM_Portal_Theme {

	public static function presets() {
		$layouts = array(
			'clean_saas' => array(
				'label'   => __( 'Clean Modern SaaS', 'tradeschool-manager' ),
				'primary' => '#2563eb', 'accent' => '#1d4ed8',
				'surface' => '#f5f7fb', 'ink'    => '#0f172a',
				'cover'   => 'linear-gradient(135deg,#2563eb 0%,#60a5fa 60%,#bfdbfe 100%)',
			),
			'warm_academy' => array(
				'label'   => __( 'Warm Academy', 'tradeschool-manager' ),
				'primary' => '#1e2a44', 'accent' => '#c9a14a',
				'surface' => '#faf8f3', 'ink'    => '#1e2a44',
				'cover'   => 'linear-gradient(135deg,#1e2a44 0%,#3a4a6f 60%,#c9a14a 100%)',
			),
			'trade_school' => array(
				'label'   => __( 'Bold Trade-School', 'tradeschool-manager' ),
				'primary' => '#ea580c', 'accent' => '#0f172a',
				'surface' => '#f8fafc', 'ink'    => '#0f172a',
				'cover'   => 'linear-gradient(135deg,#0f172a 0%,#334155 50%,#ea580c 100%)',
			),
			'friendly_edu' => array(
				'label'   => __( 'Friendly Education', 'tradeschool-manager' ),
				'primary' => '#0d9488', 'accent' => '#f97316',
				'surface' => '#fefcf9', 'ink'    => '#1e293b',
				'cover'   => 'linear-gradient(135deg,#0d9488 0%,#5eead4 60%,#f97316 100%)',
			),
		);
	}

	/**
	 * Three structurally distinct portal layouts. Each one is a different
	 * shell  -  not just a paint job.
	 */
	public static function layouts() {
		return array(
			'profile' => __( 'Profile  -  friendly cover & stat strip (default)', 'tradeschool-manager' ),
			'console' => __( 'Focused  -  Moxtra-style profile workspace',       'tradeschool-manager' ),
			'command' => __( 'Bento  -  vivid gradient sidebar + KPI tiles',     'tradeschool-manager' ),
		);
		$layouts['profile'] = __( 'Profile - friendly cover & stat strip', 'tradeschool-manager' );
		$layouts['console'] = __( 'Focused - Moxtra-style profile workspace (default)', 'tradeschool-manager' );
		return $layouts;
	}

	public static function active() {
		$s        = TSM_Settings::get_all();
		$preset   = isset( $s['portal_theme_preset'] ) ? sanitize_key( $s['portal_theme_preset'] ) : 'clean_saas';
		$layout   = isset( $s['portal_layout'] ) ? sanitize_key( $s['portal_layout'] ) : 'console';
		if ( ! array_key_exists( $layout, self::layouts() ) ) { $layout = 'console'; }
		$presets  = self::presets();
		$base     = isset( $presets[ $preset ] ) ? $presets[ $preset ] : $presets['clean_saas'];
		return array(
			'primary' => self::pick_color( $s['portal_color_primary'] ?? '', $base['primary'] ),
			'accent'  => self::pick_color( $s['portal_color_accent']  ?? '', $base['accent'] ),
			'surface' => self::pick_color( $s['portal_color_surface'] ?? '', $base['surface'] ),
			'ink'     => self::pick_color( $s['portal_color_ink']     ?? '', $base['ink'] ),
			'cover'   => $base['cover'],
			'preset'  => $preset,
			'layout'  => $layout,
		);
	}

	private static function pick_color( $candidate, $fallback ) {
		$candidate = trim( (string) $candidate );
		if ( '' === $candidate ) { return $fallback; }
		$valid = sanitize_hex_color( $candidate );
		return $valid ? $valid : $fallback;
	}

	private static function contrast_color( $hex ) {
		$hex = ltrim( (string) $hex, '#' );
		if ( 3 === strlen( $hex ) ) {
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}
		if ( 6 !== strlen( $hex ) ) {
			return '#ffffff';
		}

		$r = hexdec( substr( $hex, 0, 2 ) );
		$g = hexdec( substr( $hex, 2, 2 ) );
		$b = hexdec( substr( $hex, 4, 2 ) );
		$brightness = ( ( $r * 299 ) + ( $g * 587 ) + ( $b * 114 ) ) / 1000;
		return $brightness > 150 ? '#111827' : '#ffffff';
	}

	public static function inline_css() {
		$t = self::active();
		$contrast = self::contrast_color( $t['primary'] );

		// Shared CSS custom properties for all three portals.
		$vars = sprintf(
			'.tsm-portal-wrap,.tsm-parent-portal{--tsm-primary:%1$s;--tsm-primary-600:%2$s;--tsm-bg:%3$s;--tsm-text:%4$s;--tsm-cover:%5$s;--tsm-primary-contrast:%6$s;}',
			esc_attr( $t['primary'] ), esc_attr( $t['accent'] ),
			esc_attr( $t['surface'] ), esc_attr( $t['ink'] ),
			esc_attr( $t['cover'] ), esc_attr( $contrast )
		);

		// =============================================================
		// FULL-WIDTH BREAKOUT + theme container neutralisation.
		// When a console/command portal is present we strip the host
		// theme's vertical padding so the portal fills from menu to
		// footer with no white gap above or below.
		// =============================================================
		$breakout = '.tsm-portal-wrap[data-tsm-layout="console"]:not(.tsm-parent-portal),
.tsm-portal-wrap[data-tsm-layout="command"]:not(.tsm-parent-portal){
  max-width:none !important;width:100vw;position:relative;background:transparent;
  margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw);
  margin-top:0;box-sizing:border-box;min-height:calc(100vh - 140px);
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-main,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-main{min-width:0;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-main > *,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-main > *{float:none !important;clear:none !important;}
/* Kill host-theme vertical padding so the portal fills the viewport */
body.tsm-has-portal .tss-section,
body.tsm-has-portal .site-content,
body.tsm-has-portal .content-area,
body.tsm-has-portal main,
body.tsm-has-portal #main,
body.tsm-has-portal #content,
body.tsm-has-portal #primary,
body.tsm-has-portal article.page,
body.tsm-has-portal .entry-content,
body.tsm-has-portal .page-content{padding-top:0 !important;padding-bottom:0 !important;margin-top:0 !important;}
body.tsm-has-portal .tss-section{padding:0 !important;}
/* Common inline padding-top:75px on the page wrapper (theme spacer) */
body.tsm-has-portal [style*="padding-top: 75px"],
body.tsm-has-portal [style*="padding-top:75px"]{padding-top:0 !important;}';

		// =============================================================
		// CONSOLE  -  "Focused Profile" workspace.
		// Inspired by the Moxtra-style client profile screen: a white
		// left identity column with a large centred avatar + nav, and
		// a roomy main area of white content cards on a neutral canvas.
		// No background colour on the portal wrap itself  -  only on the
		// cards  -  so it sits cleanly inside any WordPress theme.
		// =============================================================
		$console = '.tsm-portal-wrap[data-tsm-layout="console"]{
  background:transparent;padding:0;border-radius:0;font-size:14px;color:#1f2937;
}
.tsm-portal-wrap[data-tsm-layout="console"] > .tsm-console-shell{
  display:grid !important;grid-template-columns:260px minmax(0,1fr) !important;gap:0 !important;align-items:stretch !important;
  width:100% !important;float:none !important;clear:both !important;
  min-height:calc(100vh - 140px);background:#f3f5f9;
}
@media (max-width:640px){
  .tsm-portal-wrap[data-tsm-layout="console"] > .tsm-console-shell{grid-template-columns:1fr !important;}
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side{
  background:#fff;border-right:1px solid #e5e9ef;padding:32px 18px;box-shadow:none;border-radius:0;
  display:flex;flex-direction:column;
}

/* Identity block at the top of the sidebar (avatar + name + role) */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-side-identity{
  display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;
  padding:0 8px 24px;border-bottom:1px solid #eef1f5;margin-bottom:18px;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-side-identity .tsm-avatar-wrap{
  grid-area:auto !important;width:104px !important;height:104px !important;position:relative;margin:0 auto;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-side-identity .tsm-avatar,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-side-identity .tsm-avatar-initials,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-side-identity img{
  width:104px !important;height:104px !important;border-radius:50% !important;
  object-fit:cover;display:flex;align-items:center;justify-content:center;
  background:var(--tsm-primary);color:#fff;font-weight:700;font-size:34px;
  text-align:center;border:4px solid #fff;box-shadow:0 4px 14px rgba(15,23,42,.12);
  margin:0 auto;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-side-name{
  font-size:1.05rem;font-weight:600;color:#0f172a;margin:6px 0 0;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-side-role{
  font-size:12px;color:#94a3b8;margin:0;letter-spacing:.02em;
}
/* Move the "upload new photo" button out from inside the avatar circle
   and place it just below the avatar in the sidebar identity block. */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-avatar-wrap{
  overflow:visible;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-avatar-edit{
  position:static !important;bottom:auto !important;right:auto !important;
  display:flex;justify-content:center;margin-top:10px;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-avatar-edit-btn{
  width:auto;height:auto;border-radius:999px;padding:6px 12px;gap:6px;
  font-size:12px;font-weight:600;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-side-identity .tsm-avatar-edit-btn::after{
  content:"Change photo";
}

/* Hide the duplicate teacher hero header in the console/command shells  - 
   identity and year switcher are lifted into the sidebar; keep the stats
   strip visible by stripping the card chrome and only hiding the body. */
.tsm-portal-wrap[data-tsm-layout="console"].tsm-teacher-portal .tsm-teacher-hero,
.tsm-portal-wrap[data-tsm-layout="command"].tsm-teacher-portal .tsm-teacher-hero{
  background:transparent !important;box-shadow:none !important;border:0 !important;
  padding:0 !important;margin:0 0 18px !important;overflow:visible !important;
}
.tsm-portal-wrap[data-tsm-layout="console"].tsm-teacher-portal .tsm-teacher-hero .tsm-cover,
.tsm-portal-wrap[data-tsm-layout="console"].tsm-teacher-portal .tsm-teacher-hero-body,
.tsm-portal-wrap[data-tsm-layout="command"].tsm-teacher-portal .tsm-teacher-hero .tsm-cover,
.tsm-portal-wrap[data-tsm-layout="command"].tsm-teacher-portal .tsm-teacher-hero-body{
  display:none !important;
}

/* Vertical nav rail */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tabs{
  display:flex;flex-direction:column;gap:2px;border:0;background:transparent;padding:0;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab{
  display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  padding:11px 14px;border:0;background:transparent;border-radius:8px;
  color:#475569;font-weight:500;font-size:14px;cursor:pointer;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab::before{
  content:"";width:6px;height:6px;border-radius:50%;background:#cbd5e1;flex:0 0 6px;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab:hover{background:#eef2f7;color:#0f172a;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab.is-active{
  background:#eef4ff;color:var(--tsm-primary) !important;font-weight:600;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab.is-active *{color:var(--tsm-primary) !important;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tab.is-active::before{background:var(--tsm-primary);}

/* Main column */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-main{
  padding:28px 36px 48px;background:transparent;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-main > *{margin-bottom:18px;}

.tsm-portal-wrap[data-tsm-layout="console"] .tsm-card,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-panel,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-profile-card,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-info-card,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-announcements{
  background:#fff;border:1px solid #e5e9ef;border-radius:10px;box-shadow:0 1px 2px rgba(15,23,42,.04);
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-card--pad{padding:24px 28px;}

/* Hide the duplicate identity (already shown in the sidebar); keep stats */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-profile-card{
  background:transparent !important;border:0 !important;box-shadow:none !important;padding:0 !important;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-cover,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-profile-body{display:none !important;}

/* Stats  -  horizontal pill row */
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-stats{
  display:flex;flex-wrap:wrap;gap:8px;background:transparent;border-top:1px solid #eef1f5;
  padding:14px 0 0;margin-top:14px;border-radius:0;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-stat{
  background:#f3f6fb;border:1px solid #e5e9ef;border-radius:999px;padding:6px 14px;
  display:inline-flex;align-items:baseline;gap:8px;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-stat-num{font-size:14px;font-weight:700;color:#0f172a;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-stat-lbl{
  font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#64748b;font-weight:600;
}

.tsm-portal-wrap[data-tsm-layout="console"] table{border-collapse:collapse;width:100%;}
.tsm-portal-wrap[data-tsm-layout="console"] table th{
  text-transform:uppercase;font-size:11px;letter-spacing:.08em;color:#64748b;font-weight:600;
  border-bottom:1px solid #e5e9ef;padding:10px 14px;text-align:left;background:#fafbfd;
}
.tsm-portal-wrap[data-tsm-layout="console"] table td{padding:12px 14px;border-bottom:1px solid #f1f4f8;color:#1f2937;}
.tsm-portal-wrap[data-tsm-layout="console"] table tbody tr:hover td{background:#f7faff;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-btn,
.tsm-portal-wrap[data-tsm-layout="console"] .button{border-radius:6px;font-weight:500;}

.tsm-portal-wrap[data-tsm-layout="console"] .tsm-card > .tsm-h3,
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-card > h3{
  font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:#64748b;font-weight:700;
  margin:0 0 14px;padding-bottom:10px;border-bottom:1px solid #eef1f5;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-console-side .tsm-tabs{flex:1 1 auto;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--logout{
  margin-top:auto;color:#dc2626;text-decoration:none;
}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--logout::before{background:#fca5a5 !important;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--logout:hover{background:#fef2f2;color:#b91c1c;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--action{color:var(--tsm-primary);font-weight:600;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--action::before{background:var(--tsm-primary) !important;opacity:.55;}
.tsm-portal-wrap[data-tsm-layout="console"] .tsm-tab--action:hover{background:#eef4ff;color:var(--tsm-primary);}';

		// =============================================================
		// COMMAND  -  "Bento Analytics" light dashboard.
		// Bright, energetic dashboard with a vivid gradient sidebar, a
		// soft neutral main canvas, and big colourful KPI bento tiles.
		// No dark background anywhere  -  designed to drop into any
		// WordPress theme without taking over the whole page.
		// =============================================================
		$command = '.tsm-portal-wrap[data-tsm-layout="command"]{
  background:transparent;color:#1f2937;padding:0;border-radius:0;font-size:14px;
}
.tsm-portal-wrap[data-tsm-layout="command"] > .tsm-console-shell{
  display:grid !important;grid-template-columns:240px minmax(0,1fr) !important;gap:0 !important;align-items:stretch !important;
  width:100% !important;float:none !important;clear:both !important;
  min-height:calc(100vh - 140px);background:#f6f7fb;
}
@media (max-width:640px){
  .tsm-portal-wrap[data-tsm-layout="command"] > .tsm-console-shell{grid-template-columns:1fr !important;}
}
.tsm-portal-wrap[data-tsm-layout="command"] a{color:#4f46e5;}

/* Vivid gradient sidebar  -  the layout\'s signature element */
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side{
  background:linear-gradient(180deg,#4f46e5 0%,#7c3aed 60%,#ec4899 100%);
  border-right:0;padding:28px 16px;color:#fff;position:relative;overflow:hidden;
  display:flex;flex-direction:column;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side::after{
  content:"";position:absolute;inset:0;
  background:radial-gradient(circle at 0 100%,rgba(255,255,255,.15),transparent 60%);
  pointer-events:none;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side > *{position:relative;z-index:1;}

.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity{
  display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;
  padding:0 8px 22px;border-bottom:1px solid rgba(255,255,255,.18);margin-bottom:18px;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity .tsm-avatar-wrap{
  grid-area:auto !important;width:92px !important;height:92px !important;position:relative;margin:0 auto;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity .tsm-avatar,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity .tsm-avatar-initials,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity img{
  width:92px !important;height:92px !important;border-radius:50% !important;
  object-fit:cover;display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.18);color:#fff;font-weight:700;font-size:30px;
  text-align:center;border:3px solid rgba(255,255,255,.55);
  box-shadow:0 10px 30px -10px rgba(0,0,0,.4);margin:0 auto;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity .tsm-side-name{
  color:#fff;font-weight:700;font-size:1.05rem;margin:8px 0 0;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-side-identity .tsm-side-role{color:rgba(255,255,255,.78);font-size:12px;margin:0;}

.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tabs{
  display:flex;flex-direction:column;gap:4px;border:0;background:transparent;padding:0;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tab{
  display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  padding:12px 14px;border:0;background:transparent;border-radius:10px;
  color:rgba(255,255,255,.82);font-weight:500;font-size:14px;cursor:pointer;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tab::before{
  content:"";width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.4);flex:0 0 6px;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tab:hover{background:rgba(255,255,255,.12);color:#fff;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tab.is-active{
  background:rgba(255,255,255,.96);color:#4f46e5;font-weight:700;box-shadow:0 6px 18px -10px rgba(0,0,0,.35);
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tab.is-active::before{background:#4f46e5;}

/* Main canvas */
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-main{padding:28px 36px 56px;background:transparent;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-main > *{margin-bottom:20px;}

.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-panel,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-info-card,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-announcements{
  background:#fff;border:1px solid #e6e8ef;border-radius:14px;
  box-shadow:0 1px 2px rgba(15,23,42,.04);color:#1f2937;position:relative;overflow:hidden;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card--pad{padding:24px 28px;}

/* Hide the duplicate identity card  -  sidebar owns it */
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-profile-card{
  background:transparent !important;border:0 !important;box-shadow:none !important;padding:0 !important;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-cover,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-profile-body{display:none !important;}

/* Bento KPI tiles  -  each tile a different vivid accent */
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stats{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;
  padding:0;background:transparent;border:0;border-radius:0;margin:0;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat{
  position:relative;background:#fff;border:1px solid #e6e8ef;border-radius:16px;padding:18px 20px;
  display:flex;flex-direction:column;gap:6px;overflow:hidden;
  box-shadow:0 6px 18px -14px rgba(15,23,42,.18);
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat::before{
  content:"";position:absolute;top:0;left:0;right:0;height:4px;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat::after{
  content:"";position:absolute;right:-30px;bottom:-30px;width:120px;height:120px;border-radius:50%;opacity:.10;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(1)::before{background:linear-gradient(90deg,#4f46e5,#818cf8);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(1)::after{background:#4f46e5;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(2)::before{background:linear-gradient(90deg,#7c3aed,#c084fc);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(2)::after{background:#7c3aed;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(3)::before{background:linear-gradient(90deg,#059669,#34d399);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(3)::after{background:#059669;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(4)::before{background:linear-gradient(90deg,#f59e0b,#fbbf24);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(4)::after{background:#f59e0b;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(5)::before{background:linear-gradient(90deg,#ec4899,#fb7185);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat:nth-child(5)::after{background:#ec4899;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat-num{font-size:2rem;font-weight:700;color:#0f172a;display:block;line-height:1;font-feature-settings:"tnum";}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-stat-lbl{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:#64748b;font-weight:600;}

.tsm-portal-wrap[data-tsm-layout="command"] table{border-collapse:collapse;width:100%;}
.tsm-portal-wrap[data-tsm-layout="command"] table th{
  text-transform:uppercase;font-size:11px;letter-spacing:.1em;color:#64748b;font-weight:600;
  border-bottom:1px solid #e6e8ef;padding:12px 14px;text-align:left;background:#fafbfd;
}
.tsm-portal-wrap[data-tsm-layout="command"] table td{padding:14px;border-bottom:1px solid #f1f4f8;color:#1f2937;}
.tsm-portal-wrap[data-tsm-layout="command"] table tbody tr:hover td{background:#f7faff;}

.tsm-portal-wrap[data-tsm-layout="command"] .tsm-btn,
.tsm-portal-wrap[data-tsm-layout="command"] .button{border-radius:10px;background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;border:0;font-weight:600;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-btn--ghost{background:transparent;border:1px solid #d6d9e2;color:#475569;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-chip{background:rgba(79,70,229,.10);color:#4f46e5;border-radius:999px;padding:3px 12px;font-size:12px;font-weight:600;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-dl dt{color:#64748b;text-transform:uppercase;font-size:11px;letter-spacing:.08em;font-weight:600;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-dl dd{color:#1f2937;}

.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card > .tsm-h3,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card > h3{
  display:flex;align-items:center;gap:10px;margin:0 0 16px;color:#0f172a;font-size:1rem;font-weight:700;
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card > .tsm-h3::before,
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-card > h3::before{
  content:"";width:4px;height:18px;border-radius:3px;background:linear-gradient(180deg,#4f46e5,#ec4899);
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-console-side .tsm-tabs{flex:1 1 auto;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--logout{
  margin-top:auto;text-decoration:none;color:#fff;background:rgba(255,255,255,.08);
}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--logout::before{background:#fecaca !important;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--logout:hover{background:rgba(255,255,255,.18);color:#fff;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--action{color:#fff;font-weight:600;background:rgba(255,255,255,.10);}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--action::before{background:#fff !important;opacity:.85;}
.tsm-portal-wrap[data-tsm-layout="command"] .tsm-tab--action:hover{background:rgba(255,255,255,.22);color:#fff;}';

		return $vars . $breakout . $console . $command;

	}

	/**
	 * Inline CSS + DOM-rewiring script. The script:
	 *   1. Tags every student/teacher portal wrapper with [data-tsm-layout].
	 *   2. For console/command layouts, wraps content into a sidebar+main
	 *      shell by moving the existing .tsm-tabs nav into a left rail
	 *      and the rest of the portal into the main column.
	 *   3. Keeps the existing tab-switching JS working (we move the nodes,
	 *      we don't clone them  -  so event handlers stay attached).
	 *
	 * Parent/sponsor portal is intentionally skipped.
	 */
	public static function maybe_print_head() {
		static $printed = false;
		if ( $printed ) { return; }
		$printed = true;
		$layout  = self::active()['layout'];
		$css     = self::inline_css();

		// Pre-hide portal wrappers on console/command layouts until the
		// rewire script has applied the shell. Prevents a flash of the
		// un-rewired default layout on initial page load / refresh.
		if ( 'console' === $layout || 'command' === $layout ) {
			// Only pre-hide wrappers that actually need the shell rewire (i.e. logged-in portal with .tsm-tabs).
			// The login form has no .tsm-tabs, so :has() keeps it visible and prevents a flash on the login screen.
			$css .= '.tsm-portal-wrap:not(.tsm-parent-portal):has(> .tsm-tabs):not([data-tsm-shell-applied]){visibility:hidden!important;}';
		}
		wp_register_style( 'tsm-portal-theme', false, array(), defined( 'TSM_VERSION' ) ? TSM_VERSION : '1' );
		wp_enqueue_style( 'tsm-portal-theme' );
		wp_add_inline_style( 'tsm-portal-theme', $css );

		$js = <<<'JS'
(function(){
  function setImportantStyles(node, styles){
    if(!node || !node.style) return;
    for(var key in styles){
      if(Object.prototype.hasOwnProperty.call(styles, key)){
        node.style.setProperty(key, styles[key], 'important');
      }
    }
  }
  function directChildByClass(root, cls){
    var kids = root.children || [];
    for(var i=0;i<kids.length;i++){
      if(kids[i].classList && kids[i].classList.contains(cls)) return kids[i];
    }
    return null;
  }
  function portalTabs(root){
    var direct = directChildByClass(root, 'tsm-tabs');
    if(direct) return direct;
    var nested = root.querySelector('.tsm-tabs');
    if(!nested) return null;
    var shell = root.querySelector('.tsm-console-shell');
    if(shell && shell.contains(nested)) return null;
    return nested;
  }
  function applyShellStyles(root, shell, side, main, tabs, layout){
    var isMobile = window.matchMedia('(max-width: 640px)').matches;
    var sideWidth = layout === 'command' ? '240px' : '260px';
    setImportantStyles(root, {
      'padding': '0',
      'border-radius': '0',
      'background': 'transparent'
    });
    setImportantStyles(shell, {
      'display': 'grid',
      'grid-template-columns': isMobile ? '1fr' : (sideWidth + ' minmax(0,1fr)'),
      'gap': '0',
      'align-items': 'stretch',
      'width': '100%',
      'min-height': isMobile ? '0' : 'calc(100vh - 140px)',
      'float': 'none',
      'clear': 'both'
    });
    setImportantStyles(side, {
      'display': 'flex',
      'flex-direction': 'column',
      'width': isMobile ? '100%' : sideWidth,
      'min-width': isMobile ? '0' : sideWidth,
      'max-width': isMobile ? '100%' : sideWidth,
      'flex': isMobile ? '0 0 auto' : ('0 0 ' + sideWidth),
      'float': 'none',
      'clear': 'none',
      'box-sizing': 'border-box'
    });
    setImportantStyles(main, {
      'min-width': '0',
      'width': 'auto',
      'float': 'none',
      'clear': 'none',
      'box-sizing': 'border-box'
    });
    if(tabs){
      setImportantStyles(tabs, {
        'display': 'flex',
        'flex-direction': 'column',
        'width': '100%'
      });
    }
  }
  function buildIdentity(root){
    var card = root.querySelector('.tsm-profile-card');
    if(!card) return null;
    var wrap   = card.querySelector('.tsm-avatar-wrap');
    var avatar = wrap ? null : card.querySelector('.tsm-avatar, .tsm-avatar-initials');
    var name   = card.querySelector('.tsm-name');
    var tag    = card.querySelector('.tsm-tagline, .tsm-role');
    if(!wrap && !avatar && !name) return null;
    var ident = document.createElement('div'); ident.className = 'tsm-side-identity';
    if(wrap){ ident.appendChild(wrap); }
    else if(avatar){ ident.appendChild(avatar.cloneNode(true)); }
    if(name){ var n = document.createElement('div'); n.className='tsm-side-name'; n.textContent = name.textContent.trim(); ident.appendChild(n); }
    if(tag){  var t = document.createElement('div'); t.className='tsm-side-role'; t.textContent = tag.textContent.trim();  ident.appendChild(t); }
    return ident;
  }
  function appendHeaderActions(root, tabs){
    // In the console/command shells the .tsm-profile-card header is hidden,
    // which also hides any add-on buttons rendered there (Report Card,
    // Transcript, etc.). Lift those anchors into the sidebar nav so they
    // remain reachable.
    var actions = root.querySelector('.tsm-profile-actions, .tsm-teacher-hero-actions');
    if(!actions) return;
    var anchors = actions.querySelectorAll('a');
    for(var i=0;i<anchors.length;i++){
      var src = anchors[i];
      var href = src.getAttribute('href') || '';
      if(/tsm_(logout|teacher_logout|parent_logout)=1/.test(href)) continue;
      var label = (src.textContent || '').trim();
      if(!label) continue;
      var key = href.replace(/"/g,'');
      if(tabs.querySelector('a.tsm-tab--action[data-href="'+key+'"]')) continue;
      var a = document.createElement('a');
      a.className = 'tsm-tab tsm-tab--action';
      a.href = href;
      a.setAttribute('data-href', href);
      if(src.target) a.target = src.target;
      a.textContent = label;
      tabs.appendChild(a);
    }
  }
  function appendLogout(root, tabs){
    var src = root.querySelector('a[href*="tsm_logout=1"], a[href*="tsm_teacher_logout=1"], a[href*="tsm_parent_logout=1"]');
    if(!src) return;
    // Avoid duplicating an existing logout tab if the markup already provides one.
    if(tabs.querySelector('.tsm-tab--logout')) return;
    var a = document.createElement('a');
    a.className = 'tsm-tab tsm-tab--logout';
    a.href = src.getAttribute('href');
    a.textContent = (src.textContent || 'Log out').trim();
    tabs.appendChild(a);
  }
  function rewire(root){
    if(root.dataset.tsmShellApplied) return;
    var layout = root.getAttribute('data-tsm-layout');
    // Always reveal the wrap, even when we don't apply the shell
    // (e.g. logged-out login form has no tabs, or standard layout).
    if(layout !== 'console' && layout !== 'command'){ root.setAttribute('data-tsm-shell-applied','1'); return; }
    var tabs = portalTabs(root);
    // When the observer sees the portal wrapper before the parser has finished
    // adding its direct children, wait for the next scan instead of marking the
    // shell as applied too early. This avoids the broken hybrid layout.
    if(!tabs){ return; }
    var shell = document.createElement('div'); shell.className = 'tsm-console-shell';
    var side  = document.createElement('aside'); side.className = 'tsm-console-side';
    var main  = document.createElement('div'); main.className = 'tsm-console-main';
    applyShellStyles(root, shell, side, main, tabs, layout);
    var ident = buildIdentity(root);
    if(ident) side.appendChild(ident);
    side.appendChild(tabs);
    appendHeaderActions(root, tabs);
    appendLogout(root, tabs);
    var kids = Array.prototype.slice.call(root.children);
    for(var i=0;i<kids.length;i++){
      if(kids[i] !== shell && kids[i] !== side && kids[i] !== main){
        main.appendChild(kids[i]);
      }
    }
    shell.appendChild(side); shell.appendChild(main);
    root.appendChild(shell);
    root.setAttribute('data-tsm-shell-applied','1');
  }
  function tagOne(node){
    if(!node.hasAttribute('data-tsm-layout')) node.setAttribute('data-tsm-layout', __TSM_LAYOUT__);
    rewire(node);
    document.body && document.body.classList.add('tsm-has-portal');
    document.body && document.body.classList.add('tsm-portal-layout-' + __TSM_LAYOUT__);
  }
  function scan(){
    var nodes = document.querySelectorAll('.tsm-portal-wrap:not(.tsm-parent-portal)');
    for(var i=0;i<nodes.length;i++){ tagOne(nodes[i]); }
  }
  // Run as early as possible to eliminate the flash: watch the document
  // body as it streams in, then do a final sweep on DOMContentLoaded.
  scan();
  if(window.MutationObserver && document.documentElement){
    var mo = new MutationObserver(function(muts){
      for(var i=0;i<muts.length;i++){
        var added = muts[i].addedNodes;
        for(var j=0;j<added.length;j++){
          var n = added[j];
          if(n.nodeType !== 1) continue;
          if(n.classList && n.classList.contains('tsm-portal-wrap') && !n.classList.contains('tsm-parent-portal')){
            tagOne(n);
          } else if(n.querySelectorAll){
            var inner = n.querySelectorAll('.tsm-portal-wrap:not(.tsm-parent-portal)');
            for(var k=0;k<inner.length;k++){ tagOne(inner[k]); }
          }
        }
      }
    });
    mo.observe(document.documentElement, {childList:true, subtree:true});
    document.addEventListener('DOMContentLoaded', function(){ scan(); mo.disconnect(); });
  } else {
    document.addEventListener('DOMContentLoaded', scan);
  }
  // Re-apply shell styles on viewport change so mobile/desktop layouts swap correctly.
  var _resizeT;
  window.addEventListener('resize', function(){
    clearTimeout(_resizeT);
    _resizeT = setTimeout(function(){
      var wraps = document.querySelectorAll('.tsm-portal-wrap[data-tsm-layout]');
      for(var i=0;i<wraps.length;i++){
        var w = wraps[i];
        var shell = w.querySelector(':scope > .tsm-console-shell');
        if(!shell) continue;
        var side = shell.querySelector(':scope > .tsm-console-side');
        var main = shell.querySelector(':scope > .tsm-console-main');
        var tabs = side ? side.querySelector('.tsm-tabs') : null;
        var layout = w.getAttribute('data-tsm-layout') || 'console';
        if(shell && side && main) applyShellStyles(w, shell, side, main, tabs, layout);
      }
    }, 120);
  });
})();
JS;
		$js = str_replace( '__TSM_LAYOUT__', wp_json_encode( $layout ), $js );
		wp_register_script( 'tsm-portal-layout', false, array(), defined( 'TSM_VERSION' ) ? TSM_VERSION : '1', false );
		wp_enqueue_script( 'tsm-portal-layout' );
		wp_add_inline_script( 'tsm-portal-layout', $js );
	}

	public static function init() {
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'maybe_print_head' ), 20 );
	}
}

TSM_Portal_Theme::init();
