
// tweaks-panel.jsx
// Reusable Tweaks shell + form-control helpers.
//
// Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode,
// posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so
// individual prototypes don't re-roll it. Ships a consistent set of controls so you
// don't hand-draw <input type="range">, segmented radios, steppers, etc.
//
// Usage (in an HTML file that loads React + Babel):
//
//   const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
//     "primaryColor": "#D97757",
//     "fontSize": 16,
//     "density": "regular",
//     "dark": false
//   }/*EDITMODE-END*/;
//
//   function App() {
//     const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
//     return (
//       <div style={{ fontSize: t.fontSize, color: t.primaryColor }}>
//         Hello
//         <TweaksPanel>
//           <TweakSection label="Typography" />
//           <TweakSlider label="Font size" value={t.fontSize} min={10} max={32} unit="px"
//                        onChange={(v) => setTweak('fontSize', v)} />
//           <TweakRadio  label="Density" value={t.density}
//                        options={['compact', 'regular', 'comfy']}
//                        onChange={(v) => setTweak('density', v)} />
//           <TweakSection label="Theme" />
//           <TweakColor  label="Primary" value={t.primaryColor}
//                        onChange={(v) => setTweak('primaryColor', v)} />
//           <TweakToggle label="Dark mode" value={t.dark}
//                        onChange={(v) => setTweak('dark', v)} />
//         </TweaksPanel>
//       </div>
//     );
//   }
//
// ─────────────────────────────────────────────────────────────────────────────

const __TWEAKS_STYLE = `
  .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
    max-height:calc(100vh - 32px);display:flex;flex-direction:column;
    background:rgba(250,249,247,.78);color:#29261b;
    -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
    border:.5px solid rgba(255,255,255,.6);border-radius:14px;
    box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
    font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
  .twk-hd{display:flex;align-items:center;justify-content:space-between;
    padding:10px 8px 10px 14px;cursor:move;user-select:none}
  .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
  .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
    width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
  .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
  .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
    overflow-y:auto;overflow-x:hidden;min-height:0;
    scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
  .twk-body::-webkit-scrollbar{width:8px}
  .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
  .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
    border:2px solid transparent;background-clip:content-box}
  .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
    border:2px solid transparent;background-clip:content-box}
  .twk-row{display:flex;flex-direction:column;gap:5px}
  .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
  .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
    color:rgba(41,38,27,.72)}
  .twk-lbl>span:first-child{font-weight:500}
  .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}

  .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
    color:rgba(41,38,27,.45);padding:10px 0 0}
  .twk-sect:first-child{padding-top:0}

  .twk-field{appearance:none;width:100%;height:26px;padding:0 8px;
    border:.5px solid rgba(0,0,0,.1);border-radius:7px;
    background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
  .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
  select.twk-field{padding-right:22px;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
    background-repeat:no-repeat;background-position:right 8px center}

  .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
    border-radius:999px;background:rgba(0,0,0,.12);outline:none}
  .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
    width:14px;height:14px;border-radius:50%;background:#fff;
    border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
  .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
    background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}

  .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
    background:rgba(0,0,0,.06);user-select:none}
  .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
    background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
    transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
  .twk-seg.dragging .twk-seg-thumb{transition:none}
  .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
    background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
    border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
    overflow-wrap:anywhere}

  .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
    background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
  .twk-toggle[data-on="1"]{background:#34c759}
  .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
    background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
  .twk-toggle[data-on="1"] i{transform:translateX(14px)}

  .twk-num{display:flex;align-items:center;height:26px;padding:0 0 0 8px;
    border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
  .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
    user-select:none;padding-right:8px}
  .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
    font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
    outline:none;color:inherit;-moz-appearance:textfield}
  .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
    -webkit-appearance:none;margin:0}
  .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}

  .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
    background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
  .twk-btn:hover{background:rgba(0,0,0,.88)}
  .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
  .twk-btn.secondary:hover{background:rgba(0,0,0,.1)}

  .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
    border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
    background:transparent;flex-shrink:0}
  .twk-swatch::-webkit-color-swatch-wrapper{padding:0}
  .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
  .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}
`;

// ── useTweaks ───────────────────────────────────────────────────────────────
// Single source of truth for tweak values. setTweak persists via the host
// (__edit_mode_set_keys → host rewrites the EDITMODE block on disk).
function useTweaks(defaults) {
  const [values, setValues] = React.useState(defaults);
  // Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a
  // useState-style call doesn't write a "[object Object]" key into the persisted
  // JSON block.
  const setTweak = React.useCallback((keyOrEdits, val) => {
    const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null
      ? keyOrEdits : { [keyOrEdits]: val };
    setValues((prev) => ({ ...prev, ...edits }));
    window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');
  }, []);
  return [values, setTweak];
}

// ── TweaksPanel ─────────────────────────────────────────────────────────────
// Floating shell. Registers the protocol listener BEFORE announcing
// availability -- if the announce ran first, the host's activate could land
// before our handler exists and the toolbar toggle would silently no-op.
// The close button posts __edit_mode_dismissed so the host's toolbar toggle
// flips off in lockstep; the host echoes __deactivate_edit_mode back which
// is what actually hides the panel.
function TweaksPanel({ title = 'Tweaks', children }) {
  const [open, setOpen] = React.useState(false);
  const dragRef = React.useRef(null);
  const offsetRef = React.useRef({ x: 16, y: 16 });
  const PAD = 16;

  const clampToViewport = React.useCallback(() => {
    const panel = dragRef.current;
    if (!panel) return;
    const w = panel.offsetWidth, h = panel.offsetHeight;
    const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
    const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
    offsetRef.current = {
      x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
      y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)),
    };
    panel.style.right = offsetRef.current.x + 'px';
    panel.style.bottom = offsetRef.current.y + 'px';
  }, []);

  React.useEffect(() => {
    if (!open) return;
    clampToViewport();
    if (typeof ResizeObserver === 'undefined') {
      window.addEventListener('resize', clampToViewport);
      return () => window.removeEventListener('resize', clampToViewport);
    }
    const ro = new ResizeObserver(clampToViewport);
    ro.observe(document.documentElement);
    return () => ro.disconnect();
  }, [open, clampToViewport]);

  React.useEffect(() => {
    const onMsg = (e) => {
      const t = e?.data?.type;
      if (t === '__activate_edit_mode') setOpen(true);
      else if (t === '__deactivate_edit_mode') setOpen(false);
    };
    window.addEventListener('message', onMsg);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const dismiss = () => {
    setOpen(false);
    window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
  };

  const onDragStart = (e) => {
    const panel = dragRef.current;
    if (!panel) return;
    const r = panel.getBoundingClientRect();
    const sx = e.clientX, sy = e.clientY;
    const startRight = window.innerWidth - r.right;
    const startBottom = window.innerHeight - r.bottom;
    const move = (ev) => {
      offsetRef.current = {
        x: startRight - (ev.clientX - sx),
        y: startBottom - (ev.clientY - sy),
      };
      clampToViewport();
    };
    const up = () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };

  if (!open) return null;
  return (
    <>
      <style>{__TWEAKS_STYLE}</style>
      <div ref={dragRef} className="twk-panel" data-noncommentable=""
           style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
        <div className="twk-hd" onMouseDown={onDragStart}>
          <b>{title}</b>
          <button className="twk-x" aria-label="Close tweaks"
                  onMouseDown={(e) => e.stopPropagation()}
                  onClick={dismiss}>✕</button>
        </div>
        <div className="twk-body">{children}</div>
      </div>
    </>
  );
}

// ── Layout helpers ──────────────────────────────────────────────────────────

function TweakSection({ label, title, children }) {
  label = label || title;
  return (
    <>
      <div className="twk-sect">{label}</div>
      {children}
    </>
  );
}

function TweakRow({ label, value, children, inline = false }) {
  return (
    <div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>
      <div className="twk-lbl">
        <span>{label}</span>
        {value != null && <span className="twk-val">{value}</span>}
      </div>
      {children}
    </div>
  );
}

// ── Controls ────────────────────────────────────────────────────────────────

function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) {
  return (
    <TweakRow label={label} value={`${value}${unit}`}>
      <input type="range" className="twk-slider" min={min} max={max} step={step}
             value={value} onChange={(e) => onChange(Number(e.target.value))} />
    </TweakRow>
  );
}

function TweakToggle({ label, value, onChange }) {
  return (
    <div className="twk-row twk-row-h">
      <div className="twk-lbl"><span>{label}</span></div>
      <button type="button" className="twk-toggle" data-on={value ? '1' : '0'}
              role="switch" aria-checked={!!value}
              onClick={() => onChange(!value)}><i /></button>
    </div>
  );
}

function TweakRadio({ label, value, options, onChange }) {
  const trackRef = React.useRef(null);
  const [dragging, setDragging] = React.useState(false);
  const opts = (options || []).map((o) => (typeof o === 'object' ? o : { value: o, label: o }));
  const idx = Math.max(0, opts.findIndex((o) => o.value === value));
  const n = opts.length;

  // The active value is read by pointer-move handlers attached for the lifetime
  // of a drag -- ref it so a stale closure doesn't fire onChange for every move.
  const valueRef = React.useRef(value);
  valueRef.current = value;

  const segAt = (clientX) => {
    const r = trackRef.current.getBoundingClientRect();
    const inner = r.width - 4;
    const i = Math.floor(((clientX - r.left - 2) / inner) * n);
    return opts[Math.max(0, Math.min(n - 1, i))].value;
  };

  const onPointerDown = (e) => {
    setDragging(true);
    const v0 = segAt(e.clientX);
    if (v0 !== valueRef.current) onChange(v0);
    const move = (ev) => {
      if (!trackRef.current) return;
      const v = segAt(ev.clientX);
      if (v !== valueRef.current) onChange(v);
    };
    const up = () => {
      setDragging(false);
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };

  return (
    <TweakRow label={label}>
      <div ref={trackRef} role="radiogroup" onPointerDown={onPointerDown}
           className={dragging ? 'twk-seg dragging' : 'twk-seg'}>
        <div className="twk-seg-thumb"
             style={{ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
                      width: `calc((100% - 4px) / ${n})` }} />
        {opts.map((o) => (
          <button key={o.value} type="button" role="radio" aria-checked={o.value === value}>
            {o.label}
          </button>
        ))}
      </div>
    </TweakRow>
  );
}

function TweakSelect({ label, value, options, onChange }) {
  return (
    <TweakRow label={label}>
      <select className="twk-field" value={value} onChange={(e) => onChange(e.target.value)}>
        {options.map((o) => {
          const v = typeof o === 'object' ? o.value : o;
          const l = typeof o === 'object' ? o.label : o;
          return <option key={v} value={v}>{l}</option>;
        })}
      </select>
    </TweakRow>
  );
}

function TweakText({ label, value, placeholder, onChange }) {
  return (
    <TweakRow label={label}>
      <input className="twk-field" type="text" value={value} placeholder={placeholder}
             onChange={(e) => onChange(e.target.value)} />
    </TweakRow>
  );
}

function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) {
  const clamp = (n) => {
    if (min != null && n < min) return min;
    if (max != null && n > max) return max;
    return n;
  };
  const startRef = React.useRef({ x: 0, val: 0 });
  const onScrubStart = (e) => {
    e.preventDefault();
    startRef.current = { x: e.clientX, val: value };
    const decimals = (String(step).split('.')[1] || '').length;
    const move = (ev) => {
      const dx = ev.clientX - startRef.current.x;
      const raw = startRef.current.val + dx * step;
      const snapped = Math.round(raw / step) * step;
      onChange(clamp(Number(snapped.toFixed(decimals))));
    };
    const up = () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };
  return (
    <div className="twk-num">
      <span className="twk-num-lbl" onPointerDown={onScrubStart}>{label}</span>
      <input type="number" value={value} min={min} max={max} step={step}
             onChange={(e) => onChange(clamp(Number(e.target.value)))} />
      {unit && <span className="twk-num-unit">{unit}</span>}
    </div>
  );
}

function TweakColor({ label, value, onChange }) {
  return (
    <div className="twk-row twk-row-h">
      <div className="twk-lbl"><span>{label}</span></div>
      <input type="color" className="twk-swatch" value={value}
             onChange={(e) => onChange(e.target.value)} />
    </div>
  );
}

function TweakButton({ label, children, onClick, secondary = false }) {
  label = label || children;
  return (
    <button type="button" onClick={onClick} className={secondary ? 'twk-btn secondary' : 'twk-btn'}
            onClick={onClick}>{label}</button>
  );
}

Object.assign(window, {
  useTweaks, TweaksPanel, TweakSection, TweakRow,
  TweakSlider, TweakToggle, TweakRadio, TweakSelect,
  TweakText, TweakNumber, TweakColor, TweakButton,
});
// Shared data for the NM training portal

const NMData = {
  // CHANGED: added culture and vision modules to Sales Associate path
  modules: [
    { id: 'home',      label: 'Welcome',            week: '--',      mins: 5,  img: null },
    { id: 'plan',      label: '8-Week Plan',         week: '--',      mins: 4,  img: 'assets/modules/plan.jpg' },
    { id: 'culture',   label: 'Culture & Foundation',week: 'Week 1', mins: 10, img: 'assets/modules/culture.jpg' },
    { id: 'history',   label: 'History & Heritage',  week: 'Week 1', mins: 12, img: 'assets/modules/history.jpg' },
    { id: 'vision',    label: 'Vision',              week: 'Week 1', mins: 8,  img: 'assets/modules/vision.jpg' },
    { id: 'sales',     label: 'Sales Philosophy',    week: 'Week 2', mins: 15, img: 'assets/modules/sales-philosophy.jpg' },
    { id: 'sustain',   label: 'Sustainability',      week: 'Week 4', mins: 14, img: 'assets/modules/sustainability.jpg' },
    { id: 'products',  label: 'Product Knowledge',   week: 'Week 2-5', mins: 25, img: 'assets/products/allrounder-e.jpg' },
    { id: 'store-ops', label: 'Store Operations',    week: 'Week 2', mins: 12, img: 'assets/modules/store-sop.jpg' },
    { id: 'faq',       label: 'Practical FAQ',       week: 'Always', mins: 8,  img: 'assets/modules/faq.jpg' },
    { id: 'quiz',      label: 'Validation Check',    week: 'Week 12',mins: 10, img: 'assets/modules/validation.jpg' },
  ],

  milestones: [
    { year: '1916', title: 'The craft begins', body: 'Johnny Evensen qualifies as a royal court shoemaker in Oslo, laying the foundation for a family tradition of quality craftsmanship.' },
    { year: '1940-45', title: 'Unknowingly pioneers circularity', body: "During wartime, Rolf Evensen turns customers' old handbags and suitcases into footwear. Necessity becomes the original recycling program." },
    { year: '2017', title: 'New Movements is founded', body: 'Martin Evensen, fourth-generation cobbler, establishes New Movements in Oslo -- family heritage redirected toward circular, premium footwear.' },
    { year: '2020', title: "World's first shoe recycling program", body: 'NM launches an industry-first initiative: customers return worn shoes and receive 20% off their next pair.' },
    { year: '2024', title: 'First store at Steen & Strøm', body: 'The first NM community store opens -- a milestone built together with the founding family and partners.' },
  ],

  generations: [
    { n: '1ST', name: 'Johnny Evensen', born: '1893, Oslo', note: 'Royal court shoemaker. Founded the family craft in 1916.' },
    { n: '2ND', name: 'Rolf Johnny Evensen', born: '1918, Stavern', note: 'Apprenticed under his father. Repurposed wartime suitcases into footwear.' },
    { n: '3RD', name: 'Knut Evensen', born: '1954, Hamar', note: 'Trained but did not qualify, as the Norwegian shoe industry wound down.' },
    { n: '4TH', name: 'Martin Evensen', born: '1990, Oslo', note: 'Studied at the Academy of Art University, San Francisco. Founded NM in 2017.' },
  ],

  salesPrinciples: [
    { id: 'never-on-sale', title: 'Never on sale', body: 'We make products that last. It\'s okay if a customer doesn\'t buy today -- the story stays with them either way.', mantra: 'Story over pressure.' },
    { id: 'low-gravity', title: 'Low gravity', body: 'Meet customers at their level, physically and emotionally. If they sit, you squat. Read the room. Let shoes come up organically.', mantra: 'Match the energy.' },
    { id: 'be-yourself', title: 'Be yourself', body: 'NM hires people, not experience. Customers remember people, not scripts. Your personality isn\'t something to dial down.', mantra: 'No performing.' },
    { id: 'movement', title: 'Movement creates movements', body: 'Every action compounds. A pair sold walks the city. A conversation started is a community member gained.', mantra: 'Show up. Make things move.' },
    { id: 'storytelling', title: 'Storytelling', body: 'We convert by sharing the story -- materials, sizing, production, sustainability -- with genuine ownership. Know the products inside out.', mantra: 'Be confident enough to answer anything.' },
    { id: 'try-on', title: 'The perfect try-on', body: 'Both shoes on. Correct size. Laces tied properly from bottom up. Let them walk. Give materials time to soften.', mantra: 'Set up for success.' },
  ],

  // CHANGED: expanded low-gravity principles
  lowGravityPrinciples: [
    { n: '01', title: 'Read the room first', body: 'Before you say anything, take 2 seconds. Is the customer focused, browsing, or just passing through? Your energy should match theirs, not override it.' },
    { n: '02', title: 'Never open with the product', body: 'The shoe is not the first thing. The person is. Ask about them. Where they\'re from. What they do. Shoes come up naturally when trust is established.' },
    { n: '03', title: 'Sit when they sit', body: 'If a customer is seated trying on shoes, you crouch or sit. Eye level is equal level. Towering over someone while they\'re in their socks is not a welcoming experience.' },
    { n: '04', title: 'Give them space to breathe', body: 'Offer help once, then back off. Let them move around the store. Be visible but not hovering. They will find you when they\'re ready.' },
    { n: '05', title: 'Match pace, not performance', body: 'A slow browser wants a slow conversation. A decisive customer wants quick, confident answers. Don\'t default to the same energy for everyone.' },
  ],

  // CHANGED: common floor objections
  floorObjections: [
    {
      objection: '"They\'re quite expensive."',
      context: 'Usually said while holding or trying on a shoe they\'re drawn to.',
      response: 'Acknowledge it honestly. "They are. And here\'s why --" then talk about the materials, how they\'re made, and the recycling program. Don\'t apologise for the price. Explain what\'s behind it.',
      avoid: 'Offering a discount, deflecting, or over-explaining before they\'ve even tried it on.',
    },
    {
      objection: '"I need to think about it."',
      context: 'Often means: I\'m interested but I\'m not sure yet.',
      response: 'Don\'t push. Say "Of course -- take your time. You can always come back, and if you want to return old shoes when you do, you\'ll get 20% off." Leave the door open with the recycling hook.',
      avoid: 'Following up with more features. They have what they need. Give them room.',
    },
    {
      objection: '"I can\'t find my size."',
      context: 'Stock is out or the fit feels off.',
      response: 'Never return from the stockroom empty-handed. Bring two sizes and one alternative model. Use the extra insole or top lace hole to solve fit. If nothing works, offer to check stock for them and follow up.',
      avoid: 'Giving up after one try. Sizing is a skill -- use it.',
    },
    {
      objection: '"Every brand says they\'re sustainable."',
      context: 'The informed, slightly sceptical customer.',
      response: 'Agree with them. "You\'re right -- most brands do. Here\'s what\'s different about us:" then use specifics: LWG Gold certification, SINTEF research partnership, the world\'s first shoe recycling program. No overclaiming.',
      avoid: 'Generic sustainability language. Be specific or stay quiet.',
    },
  ],

  // CHANGED: closing tips
  closingTips: [
    { title: 'Sign up before the sale', body: 'Introduce the customer club before you go to the register. It\'s easier to say yes when they\'re still in the experience, not being rung up.' },
    { title: 'Mention the recycling program as a reason to buy', body: 'Frame it as value, not charity. "When these are done, you bring them back for 20% off the next pair." That\'s a practical, compelling reason to choose NM over a comparable brand.' },
    { title: 'Offer the natural add-on', body: 'Impregnation spray for suede, leather wax for leather, spare laces. Offer it because it genuinely extends the shoe\'s life -- say that. Don\'t pitch it as an upsell.' },
    { title: 'It\'s okay if they don\'t buy', body: 'Say it out loud if it feels right: "Come back anytime, there\'s no pressure here." A customer who leaves without buying but with a great impression will return. And they\'ll tell someone.' },
  ],

  tryOnSteps: [
    { do: 'Get both shoes on', dont: 'Try a shoe quickly off the shelf' },
    { do: 'Bring the correct size', dont: 'Wrong size just to see the style' },
    { do: 'Tie laces properly, bottom-up', dont: 'Loose laces -- it changes the fit' },
    { do: 'Let them walk for a minute', dont: 'Rush the experience' },
    { do: 'Use insoles to dial in fit', dont: 'Assume the standard insert is final' },
  ],

  iceBreakers: [
    'Is this the first time seeing New Movements?',
    'Have you heard about us before?',
    'That shoe has a funny story to it!',
    'Let me give you an elevator pitch.',
    'English or Norwegian? Where are you from?',
    'Yes -- we sell shoes!',
  ],

  products: {
    SS: [
      { id: 'allrounder-vegan', name: 'Allrounder Vegan', tag: 'Vegan', material: 'Technical textile', feature: 'Perforated upper, running-shoe construction', colors: ['White & grey','White & olive','White & black','White & orange','White & white'], pitch: 'The ultimate everyday sneaker. Perforated detailing for breathability -- suitable for any occasion.' },
      { id: 'allrounder-e', name: 'Allrounder E', tag: 'Textile', material: 'Technical textile + rubber', feature: 'Rubber rand, perforated upper', colors: ['Smooth forest','Beige & sand','Beige & natural blue'], pitch: 'Built for versatility. Running-shoe construction finished with a durable rubber rand.' },
      { id: 'allrounder-s', name: 'Allrounder S', tag: 'Leather', material: 'Gold-Certified Italian leather', feature: 'Signature NM lines, perforated upper', colors: ['Beige & olive','Beige & black','Beige & orange','Beige & grape grey','Beige & pink','White & off-white'], pitch: 'The Allrounder in Gold-Certified Italian leather -- NM\'s signature lines.' },
      { id: 'original-leather', name: 'Original Leather', tag: 'Leather', material: 'Gold-Certified Italian leather', feature: 'Signature wave design, all-year wear', colors: ['Black','White','Full black'], pitch: 'NM\'s first sneaker. Wave design, unique silhouette, made for daily all-year wear.' },
      { id: 'original-nubuck', name: 'Original Nubuck', tag: 'Nubuck', material: 'Gold-Certified Italian nubuck', feature: 'Signature wave design', colors: ['Olive brown','Dark blue','Light beige','Light blue'], pitch: 'The Original silhouette in nubuck -- a softer, more premium finish.' },
      { id: 'classic-vegan', name: 'Classic Vegan', tag: 'Vegan', material: 'Vegan & organic materials', feature: 'Washable, minimalist design', colors: ['Nubuck green','White','Black'], pitch: 'NM\'s cleanest sneaker. Minimalist, soft, washable.' },
      { id: 'classic-leather', name: 'Classic Leather', tag: 'Leather', material: 'Gold-Certified Italian leather', feature: 'Renewable energy production, timeless design', colors: ['Black','White'], pitch: 'The Classic in premium leather -- timeless silhouette, made with renewable energy.' },
      { id: 'zen-tech-suede', name: 'Zen Technical Suede', tag: 'Tech suede', material: 'Spanish technical suede', feature: 'Water-resistant, memory foam insole', colors: ['Black','Oak brown','Midnight blue','Olive green','Cloud grey'], pitch: 'Smooth, breathable, water-resistant. Anti-slip outsole and memory foam.' },
      { id: 'zen-suede', name: 'Zen Suede', tag: 'Suede', material: 'Italian Gold-Certified suede', feature: 'NM lace system, slip-on wear', colors: ['Dark blue','Off white','Cognac brown'], pitch: 'Luxuriously soft Italian suede with the NM flexible lace system.' },
      { id: 'zen-tech-nubuck', name: 'Zen Technical Nubuck', tag: 'Tech nubuck', material: 'Spanish technical nubuck', feature: 'Water-resistant, NM lace system', colors: ['Natural blue','Olive green','Sand beige'], pitch: 'Durability and everyday versatility, water-resistant for daily use.' },
      { id: 'zen-air', name: 'Zen Air', tag: 'Suede', material: 'Perforated Italian suede', feature: 'Max breathability, extra laces included', colors: ['Night blue','Off white'], pitch: 'Perforated Italian suede for ultimate airflow. A community favourite.' },
      { id: 'moss', name: 'Moss', tag: 'Suede', material: 'Premium suede + smooth leather lining', feature: 'Hiking-inspired grip, flexible lace system', colors: ['Macchiato brown','Off white','Black','Azure blue'], pitch: 'Classic hiking boot, reimagined as a low-top. City walks and easy hikes.' },
      { id: 'loafer', name: 'Loafer', tag: 'Polished', material: 'Polido leather + cow leather lining', feature: 'High-shine finish, slip-on', colors: ['Polido','Raw black','Cognac brown'], pitch: 'A refined update of the classic penny loafer. Sleek, lightweight, breathable.' },
    ],
    AW: [
      { id: 'allrounder-y', name: 'Allrounder Y', tag: 'Tech', material: 'Rubberised upper + wool lining', feature: 'Water-resistant, memory foam, wool warmth', colors: ['Beige & sand','Beige & smooth forest','Black & nordic grey','Black & smooth forest','Full black','Smooth forest','White'], pitch: 'Built for cooler, wet seasons. Rubberised upper, wool lining, anti-slip rubber sole.' },
      { id: 'norwegian-sneaker', name: 'Norwegian Sneaker', tag: 'Leather', material: 'Water-resistant leather + wool lining', feature: 'Water-resistant leather, wool warmth', colors: ['White & beige','Black (white sole)','Full black','Beige & sand','Smooth forest'], pitch: 'Cooler-season leather sneaker. Water-resistant upper, wool lining, memory foam.' },
    ],
    Boots: [
      { id: 'low-cloud', name: 'Low Cloud Boot', tag: 'Low · Lace-up', material: 'Premium suede + wool lining', feature: '100% waterproof, rubber rand', colors: ['Macchiato brown','Black','Camel brown'], pitch: 'Waterproof low-cut. Thick suede, durable rubber rand, signature recycled outsole.' },
      { id: 'cloud', name: 'Cloud Boot', tag: 'Mid · Lace-up', material: 'Premium suede + wool lining', feature: '100% waterproof, rubber rand', colors: ['Black','Macchiato brown','Camel brown','Dark grey','Olive green'], pitch: 'Mid-height waterproof boot -- city streets or mountain trails.' },
      { id: 'flow', name: 'Flow Boot', tag: 'Mid · Slip-on', material: 'Suede + wool lining', feature: '100% waterproof, slip-on', colors: ['Macchiato brown','Black','Olive green'], pitch: 'Waterproof and easy to slip on. Reliable grip, all-season comfort.' },
      { id: 'space', name: 'Space Boot', tag: 'Mid-High · Zipper', material: 'Premium suede + wool lining', feature: '100% waterproof, side zipper', colors: ['Camel brown','Macchiato brown','Black'], pitch: 'Discreet side zipper for easy on/off. Recycled rubber sole, excellent grip.' },
      { id: 'high-flow', name: 'High Flow Boot', tag: 'High · Slip-on', material: 'Polished suede + wool lining', feature: '100% waterproof, slip-on', colors: ['Black polished'], pitch: 'High-cut waterproof slip-on. Polished finish with all the Flow comforts.' },
    ],
  },

  sustainStats: [
    { n: '24bn', label: 'Pairs of shoes produced every year', src: 'Worldfootwear, 2022' },
    { n: '8%',   label: 'Of global emissions from fashion',     src: 'McKinsey & Company, 2022' },
    { n: '1%',   label: 'Of footwear materials are recycled',   src: 'Ellen MacArthur Foundation, 2017' },
    { n: '95%',  label: 'Of shoes end up in landfills',         src: 'Worldfootwear, 2022' },
  ],

  materials: [
    { name: 'Leather & Nubuck', spec: 'Gold-Certified Italian', body: 'Sourced from LWG Gold-certified tanneries -- the highest rating, audited across 17 sections covering energy, water, chemicals, waste.' },
    { name: 'Outsole', spec: 'Rubber + recycled rubber', body: 'Natural and recycled rubber. Durable, flexible, built for long-term wear, reducing the need for replacement.' },
    { name: 'Laces', spec: '100% recycled PET', body: 'Made from recycled plastic bottles. Every set keeps material out of landfill.' },
    { name: 'Packaging', spec: '90% recycled cardboard', body: '10% paper. No excess plastic. Minimal, purposeful packaging.' },
    { name: 'Boot lining', spec: 'Sheep\'s wool', body: 'Natural, renewable, breathable. Wool regulates temperature naturally and is fully biodegradable.' },
    { name: 'Production', spec: 'Felgueiras, Portugal', body: 'Handcrafted in Europe. The factory runs on 30% renewable energy. Shorter supply chains, better quality, stronger labour standards.' },
  ],

  recyclingFlow: [
    { n: '01', title: 'Customer returns worn shoes', body: 'Any NM shoes, regardless of condition, can be returned to any store at the end of their life.' },
    { n: '02', title: 'Customer receives 20% off',  body: 'The discount applies to their next pair, closing the loop and rewarding responsible behaviour.' },
    { n: '03', title: 'Materials are recovered',    body: 'Shoe materials are repurposed into components for new shoes. Shoe to shoe to shoe.' },
  ],

  trainingPlan: [
    { wk: 'Week 1', brand: 'The Story · The Vision · The Culture',          ops: 'Practical info · Personalhåndboken',          tb: '15 min', extra: 'Meet & Greet' },
    { wk: 'Week 2', brand: 'Sales Philosophy · Product Knowledge',              ops: 'Opening / Closing · Weekly Routines',             tb: '15 min' },
    { wk: 'Week 3', brand: 'Product Knowledge · Materials · Production',   ops: 'E-com · Tech stack',                              tb: '15 min' },
    { wk: 'Week 4', brand: 'Sustainability & Recycling · Design DNA',           ops: 'Returns and Repairs',                                  tb: '15 min' },
    { wk: 'Week 5', brand: 'Product Knowledge',                                      ops: 'Stock Handling',                                       tb: '--' },
    { wk: 'Week 6', brand: 'Sales Philosophy repetition',                            ops: 'Visual Merchandising NM',                              tb: '--' },
    { wk: 'Week 7', brand: 'Preparation / Repetition',                               ops: 'Preparation / Repetition',                             tb: '--' },
    { wk: 'Week 8', brand: '2-month review',                                         ops: '2-month review',                                       tb: '30 min' },
    { wk: '3 mo.', brand: 'Validation',                                              ops: 'Validation',                                           tb: '1 hr' },
  ],

  faq: [
    { cat: 'Sick leave', q: 'How do I report sick leave on my first day out?', a: 'Call your manager directly by phone on your first day of absence. In-store: call the store manager. Office: call the CEO. State how long you expect to be absent. Late or missing notification can result in loss of sick pay for that period.' },
    { cat: 'Sick leave', q: 'How many self-certified sick days (egenmelding) can I take?', a: 'Up to 3 consecutive calendar days, up to 4 times within any 12-month period. You must have been employed for at least 2 months. If you fall ill again within 16 calendar days of a previous period, the new absence counts toward the same period. Beyond 3 days, a doctor\'s note (sykemelding) is required -- without one, you lose sick pay for the entire period.' },
    { cat: 'Sick leave', q: 'What happens if I am sick for more than 3 days?', a: 'You must obtain a doctor\'s note. Log the absence in Tripletex: within the employer\'s 16-day period select "syk tom 16 kalenderdag", beyond 16 days select "syk fra dag 17 kalenderdag".' },
    { cat: 'Sick leave', q: 'What if I fall ill during my holiday?', a: 'You can request that holiday days be converted to sick days, preserving those days for later use. Notify your manager as soon as possible and provide a doctor\'s note to HR. New holiday dates are agreed with your manager based on the team schedule.' },
    { cat: 'Salary', q: 'What is my compensation structure?', a: 'Base salary: 100 NOK per hour. Commission: 5% of turnover. Bonuses: 120 NOK per online order shipped, 200 NOK per Google review collected. Commission and bonuses are paid monthly together with your salary.' },
    { cat: 'Salary', q: 'When is the salary paid?', a: 'The 5th of each month, unless otherwise stated in your contract. If the 5th falls on a public holiday, payment is moved to the last working day before it. In December, half-rate tax applies. You will receive your payslip by email or via the Tripletex app.' },
    { cat: 'Salary', q: 'How does holiday pay work?', a: 'Holiday pay is 10.2% of your holiday pay basis (feriepengegrunnlag), paid out in July. In July, your salary is also deducted for all 21 working holiday days at once -- there are no individual deductions each time you take a holiday day during the year. Employees aged 60 and over are entitled to one additional week of holiday.' },
    { cat: 'Salary', q: 'How do I log overtime?', a: 'Overtime must be agreed in advance with the CEO. In Tripletex, log total hours and whether work was store or office, then add an overtime entry with the applicable rate. 06:00-21:00: 50% supplement for hours over 8. 21:00-06:00: 100% supplement.' },
    { cat: 'Salary', q: 'What pension and insurance do I have?', a: '2% employer pension contribution via DNB/EIKA. Occupational injury insurance (yrkesskade) via Sødeberg & Partners -- covers injuries and illness that occur at work, during working hours, at the workplace.' },
    { cat: 'Holiday', q: 'How many holiday days do I have?', a: '25 working days (4 weeks + 1 day) by law. With a 5-day week, this equals 21 days with full holiday pay. The remaining 4 days can be taken but without pay. Holidays must be taken within the calendar year -- maximum 12 days can be carried over with written HR agreement.' },
    { cat: 'Holiday', q: 'How do I apply for holiday in Tripletex?', a: 'Browser: Ferieplanlegging → Ferieplan → add days. HR must approve before days are deducted. App: Timeføring → Oversikt → Ferie → Be om ferie → add days. Main holiday requests (3 consecutive weeks, June-September) must be submitted at least 2 months in advance.' },
    { cat: 'Holiday', q: 'What are the standard working hours?', a: '7.5 hours per day / 37.5 hours per week, with a 30-minute unpaid lunch break. Office: flexitime up to 09:00. Store: within the store\'s opening hours. December Sundays: the store opens 3-4 Sundays in December (typically 4-hour shifts) -- you may be required to work these.' },
    { cat: 'Practical', q: 'What is the probation period and notice period?', a: 'Probation is 6 months unless your employment contract states otherwise. Absences can extend the probation period by an equivalent amount of time. Notice during probation is 14 days. During probation you will have a start conversation, training, and two evaluation conversations with your manager.' },
    { cat: 'Practical', q: 'How are my goals set and measured?', a: 'Goals are set through OKRs (Objectives and Key Results) per functional team, renewed each quarter. You are responsible for reaching your goals, which are set together with management. Progress is reviewed at quarterly OKR meetings. You will also have an annual performance review (medarbeidersamtale) with HR and/or your manager -- come prepared with your own topics.' },
  ],

  quiz: [
    { q: 'Which year did Martin Evensen found New Movements?', options: ['2014','2017','2020','2024'], correct: 1, why: '2017, in Oslo. The first store opened in 2024 at Steen & Strøm.' },
    { q: 'Which generation of shoemaker is Martin Evensen?', options: ['2nd','3rd','4th','5th'], correct: 2, why: 'Fourth generation: Johnny → Rolf → Knut → Martin.' },
    { q: 'What discount does the recycling program offer?', options: ['10% off','15% off','20% off','Free pair after 5 returns'], correct: 2, why: 'Customers return worn NM shoes and receive 20% off their next pair.' },
    { q: 'Where are NM shoes handcrafted?', options: ['Oslo, Norway','Felgueiras, Portugal','Milan, Italy','Porto, Portugal'], correct: 1, why: 'Handmade in Felgueiras, Portugal, on 30% renewable energy.' },
    { q: 'What does LWG Gold certification require?', options: ['Above 60% in any section','Above 75% overall','Above 85% in every section','Tannery age over 50 years'], correct: 2, why: 'Audited across 17 sections -- above 85% in every section. No exceptions.' },
    { q: '"Low gravity" means...', options: ['Sell with low pressure tactics','Meet the customer at their level, physically and emotionally','Use the lowest-priced model as an entry point','Speak quietly so customers lean in'], correct: 1, why: 'If a customer sits, you squat. Read the room. Match their energy.' },
    { q: 'What percentage of footwear materials are recycled globally?', options: ['1%','8%','24%','95%'], correct: 0, why: 'Just 1% (Ellen MacArthur Foundation, 2017). 95% of shoes end up in landfills.' },
    { q: 'For a customer who walks daily in all weathers, which AW model is most appropriate?', options: ['Loafer','Classic Vegan','Allrounder Y','Zen Air'], correct: 2, why: 'Allrounder Y: rubberised water-resistant upper, wool lining, anti-slip rubber sole, memory foam.' },
    { q: 'What is NM\'s mission statement?', options: ['"Footwear for everyone, forever"','"Make circularity the new standard for premium footwear globally, by 2035"','"The most sustainable shoe brand in Europe"','"Walking lighter on the planet"'], correct: 1, why: '"Make circularity the new standard for premium footwear globally, by 2035." Premium quality, ethical production, circular by design.' },
    { q: 'How should you respond to "Every brand claims to be sustainable"?', options: ['List every certification we have','Agree, then be specific: LWG Gold, SINTEF partnership, world\'s first recycling program -- no overclaiming','Show the price as proof of quality','Mention renewable energy only'], correct: 1, why: 'Be specific and honest. Mention real, verified differentiators -- don\'t overclaim.' },
  ],
};

window.NMData = NMData;
// Shared visual primitives: WaveLogo, ShoeIllustration, etc.

function WaveLogo({ size = 32, color = 'currentColor', animated = false }) {
  // Use the official NM PNG mark.
  return (
    <img
      src="assets/nm-logo.png"
      alt="New Movements"
      width={size}
      height={size}
      style={{ display: 'inline-block', verticalAlign: 'middle', objectFit: 'contain' }}
    />
  );
}

// A schematic shoe silhouette (not a real product render -- placeholder)
function ShoeGlyph({ variant = 'sneaker', size = 120, color = 'currentColor', accent = null }) {
  const a = accent || color;
  if (variant === 'boot') {
    return (
      <svg width={size} height={size * 0.75} viewBox="0 0 160 120" fill="none">
        <path d="M 30 95 L 30 30 Q 30 18 42 18 L 70 18 Q 82 18 82 30 L 82 70 L 130 70 Q 142 70 142 82 L 142 95 Z"
              stroke={color} strokeWidth="2" fill="none" strokeLinejoin="round"/>
        <line x1="30" y1="95" x2="142" y2="95" stroke={color} strokeWidth="2"/>
        <line x1="30" y1="100" x2="142" y2="100" stroke={a} strokeWidth="3" strokeLinecap="round"/>
        {/* laces */}
        <line x1="46" y1="35" x2="76" y2="35" stroke={color} strokeWidth="1.2"/>
        <line x1="46" y1="48" x2="76" y2="48" stroke={color} strokeWidth="1.2"/>
        <line x1="46" y1="61" x2="76" y2="61" stroke={color} strokeWidth="1.2"/>
      </svg>
    );
  }
  if (variant === 'loafer') {
    return (
      <svg width={size} height={size * 0.55} viewBox="0 0 160 90" fill="none">
        <path d="M 18 65 Q 18 40 50 35 Q 90 28 130 38 Q 148 42 148 60 Q 148 70 138 72 L 28 72 Q 18 72 18 65 Z"
              stroke={color} strokeWidth="2" fill="none"/>
        <path d="M 60 48 Q 80 44 100 48" stroke={color} strokeWidth="1.4" fill="none"/>
        <line x1="20" y1="76" x2="146" y2="76" stroke={a} strokeWidth="3" strokeLinecap="round"/>
      </svg>
    );
  }
  // sneaker
  return (
    <svg width={size} height={size * 0.55} viewBox="0 0 160 90" fill="none">
      <path d="M 14 70 L 14 55 Q 14 48 22 46 L 50 38 Q 60 35 68 28 L 80 18 Q 88 12 96 18 L 102 26 Q 108 34 118 36 L 138 40 Q 150 42 150 55 L 150 70 Z"
            stroke={color} strokeWidth="2" fill="none" strokeLinejoin="round"/>
      <line x1="14" y1="70" x2="150" y2="70" stroke={color} strokeWidth="2"/>
      <line x1="14" y1="76" x2="150" y2="76" stroke={a} strokeWidth="3" strokeLinecap="round"/>
      {/* perforation */}
      <circle cx="68" cy="42" r="1" fill={color}/>
      <circle cx="76" cy="40" r="1" fill={color}/>
      <circle cx="84" cy="38" r="1" fill={color}/>
      <circle cx="92" cy="38" r="1" fill={color}/>
      <circle cx="100" cy="40" r="1" fill={color}/>
      {/* lace area */}
      <path d="M 64 32 L 72 28 M 72 36 L 80 32 M 80 40 L 88 36" stroke={color} strokeWidth="1.2"/>
      {/* signature wave swoosh */}
      <path d="M 30 58 Q 50 50 75 56 Q 100 62 130 56" stroke={a} strokeWidth="1.5" fill="none" strokeLinecap="round"/>
    </svg>
  );
}

function PageHeader({ eyebrow, title, lede, week, mins, img }) {
  if (!img) {
    return (
      <header style={{ marginBottom: 56 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 28 }}>
          <span className="eyebrow">{eyebrow}</span>
          {week && <span className="mono" style={{ color: 'var(--stone)' }}>· {week}</span>}
          {mins != null && <span className="mono" style={{ color: 'var(--stone)' }}>· ~{mins} min</span>}
        </div>
        <h1 className="display" style={{ fontSize: 'clamp(48px, 7vw, 96px)', margin: '0 0 24px', lineHeight: 0.95 }}>
          {title}
        </h1>
        {lede && (
          <p style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 22, lineHeight: 1.35, color: 'var(--ink-2)', maxWidth: '36ch', margin: 0 }}>
            {lede}
          </p>
        )}
      </header>
    );
  }

  return (
    <header style={{ marginBottom: 56 }}>
      {/* Full-bleed hero image with title overlay */}
      <div className="nm-hero" style={{
        position: 'relative',
        height: 'clamp(400px, 60vh, 640px)',
        marginBottom: 56,
        overflow: 'hidden',
      }}>
        {/* Background image */}
        <img
          src={img}
          alt=""
          loading="eager"
          style={{
            position: 'absolute', inset: 0,
            width: '100%', height: '100%',
            objectFit: 'cover',
          }}
        />
        {/* Dark gradient so text is legible */}
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(to bottom, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.55) 100%)',
        }} />
        {/* Text overlay */}
        <div className="nm-hero-text" style={{
          position: 'absolute', bottom: 0, left: 0, right: 0,
          padding: '40px 48px',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20 }}>
            <span className="eyebrow" style={{ color: 'rgba(255,255,255,0.7)', borderColor: 'rgba(255,255,255,0.3)' }}>{eyebrow}</span>
            {week && <span className="mono" style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11 }}>· {week}</span>}
            {mins != null && <span className="mono" style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11 }}>· ~{mins} min</span>}
          </div>
          <h1 className="display" style={{ fontSize: 'clamp(40px, 6vw, 88px)', margin: '0 0 16px', lineHeight: 0.95, color: '#fff' }}>
            {title}
          </h1>
          {lede && (
            <p style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 18, lineHeight: 1.35, color: 'rgba(255,255,255,0.8)', maxWidth: '40ch', margin: 0 }}>
              {lede}
            </p>
          )}
        </div>
      </div>
    </header>
  );
}

function StatBlock({ n, label, src }) {
  return (
    <div style={{ borderTop: '1px solid var(--line-soft)', paddingTop: 20 }}>
      <div className="display" style={{ fontSize: 88, lineHeight: 0.9, marginBottom: 12 }}>{n}</div>
      <div style={{ fontSize: 13, lineHeight: 1.4, marginBottom: 6 }}>{label}</div>
      {src && <div className="mono" style={{ color: 'var(--stone)', fontSize: 10 }}>{src}</div>}
    </div>
  );
}

function ProgressBar({ value, total }) {
  const pct = total === 0 ? 0 : Math.round((value / total) * 100);
  return (
    <div style={{ width: '100%' }}>
      <div style={{ height: 2, background: 'var(--line-soft)', position: 'relative', overflow: 'hidden' }}>
        <div style={{ position: 'absolute', inset: 0, width: pct + '%', background: 'var(--ink)', transition: 'width 0.5s ease' }} />
      </div>
      <div className="mono" style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8, color: 'var(--stone)' }}>
        <span>{value}/{total} complete</span>
        <span>{pct}%</span>
      </div>
    </div>
  );
}

function Divider({ label }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, margin: '64px 0 24px' }}>
      <span className="mono" style={{ color: 'var(--stone)' }}>{label}</span>
      <div style={{ flex: 1, height: 1, background: 'var(--line-soft)' }} />
    </div>
  );
}

Object.assign(window, { WaveLogo, ShoeGlyph, PageHeader, StatBlock, ProgressBar, Divider });
// CHANGED: module cards with image placeholders + module images
function ModuleHome({ navigate, completed }) {

  return (
    <div className="fade-up">
      {/* Hero heading */}
      <div style={{ borderBottom: '1px solid var(--line-soft)', paddingBottom: 48, marginBottom: 48 }}>
        <div className="eyebrow" style={{ marginBottom: 24 }}>Onboarding · Sales Associate</div>

        <h1 className="display" style={{ fontSize: 'clamp(36px, 8vw, 112px)', margin: '0 0 32px', lineHeight: 0.95 }}>
          Welcome to<br /><span className="display-it">New Movements.</span>
        </h1>

      </div>

      {/* Module grid */}
      <div style={{ marginBottom: 28 }}>
        <h2 className="display" style={{ fontSize: 32, margin: 0 }}>Your path</h2>
      </div>

      {/* CHANGED: module cards with image placeholders */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 1, background: 'var(--line-soft)', border: '1px solid var(--line-soft)' }}>
        {window.NMData.modules.filter(m => m.id !== 'home').map((m, i) => (
          <button
            key={m.id}
            onClick={() => navigate(m.id)}
            className="focus-ring"
            style={{
              all: 'unset', cursor: 'pointer',
              background: 'var(--paper)', minHeight: 200,
              display: 'flex', flexDirection: 'column',
              transition: 'background 0.2s ease',
            }}
            onMouseEnter={e => e.currentTarget.style.background = 'var(--paper-2)'}
            onMouseLeave={e => e.currentTarget.style.background = 'var(--paper)'}
          >
            {/* CHANGED: module image placeholder */}
            <div style={{ height: 160, background: '#E8E4DE', overflow: 'hidden', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              {m.img ? (
                <img
                  src={m.img} alt="" loading="lazy"
                  style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
                  onError={e => { e.currentTarget.style.display = 'none'; }}
                />
              ) : (
                <WaveLogo size={28} color="var(--stone-light)" />
              )}
            </div>
            <div style={{ padding: '20px 22px', flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 16 }}>
              <div>
                <div className="display" style={{ fontSize: 24, lineHeight: 1.1 }}>{m.label}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                <span className="mono" style={{ color: 'var(--stone)' }}>~{m.mins} min</span>
              </div>
            </div>
          </button>
        ))}
      </div>

      {/* Founder quote */}
      <Divider label="From the founder" />
      <blockquote style={{ margin: 0, maxWidth: 820 }}>
        <p className="display" style={{ fontSize: 'clamp(22px, 3vw, 38px)', lineHeight: 1.18, marginBottom: 20 }}>
          New Movements is never on sale. We make products that last, and when their lifetime is over, you can repair or recycle your shoes for 20% off the next pair.{' '}
          <em className="display-it">It is okay if you don't buy shoes today.</em>{' '}
          Let me show you how we make them, why we make them, and what we can offer.
        </p>
        <footer className="mono" style={{ color: 'var(--stone)' }}>-- Martin Evensen, founder</footer>
      </blockquote>
    </div>
  );
}

window.ModuleHome = ModuleHome;
function ModuleHistory({ markComplete }) {
  const [active, setActive] = React.useState(0);
  const M = window.NMData.milestones;
  const G = window.NMData.generations;

  React.useEffect(() => { markComplete && markComplete('history'); }, []);

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="01 · Brand knowledge"
        week="Week 1"
        mins={12}
        img="assets/modules/history.jpg"
        title={<>The history of <span className="display-it">New Movements</span></>}
        lede="A family tradition that stretches back over a century -- redirected, by the fourth generation, toward a circular future."
      />

      {/* Four generations */}
      <Divider label="Four generations of shoemakers" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0, border: '1px solid var(--line-soft)', borderRight: 'none' }}>
        {G.map((g, i) => (
          <div key={i} style={{ padding: '28px 22px', borderRight: '1px solid var(--line-soft)', minHeight: 220 }}>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 18 }}>{g.n} GEN.</div>
            <div className="display" style={{ fontSize: 26, lineHeight: 1.05, marginBottom: 6 }}>{g.name}</div>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 14 }}>b. {g.born}</div>
            <p style={{ fontSize: 13, lineHeight: 1.4, color: 'var(--ink-2)', margin: 0 }}>{g.note}</p>
          </div>
        ))}
      </div>

      {/* Interactive timeline */}
      <Divider label="Milestones · click to expand" />
      <div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: 48, alignItems: 'start' }}>
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          {M.map((m, i) => (
            <button
              key={i}
              onClick={() => setActive(i)}
              className="focus-ring"
              style={{
                all: 'unset', cursor: 'pointer',
                padding: '20px 0',
                borderBottom: '1px solid var(--line-soft)',
                display: 'flex', alignItems: 'baseline', gap: 16,
                opacity: active === i ? 1 : 0.45,
                transition: 'opacity 0.2s ease',
              }}
            >
              <div className="mono" style={{ width: 60, color: active === i ? 'var(--ink)' : 'var(--stone)' }}>{m.year}</div>
              <div style={{ flex: 1, fontSize: 14, lineHeight: 1.3, fontWeight: active === i ? 500 : 400 }}>
                {m.title}
              </div>
              <span style={{ fontSize: 14 }}>{active === i ? '●' : '○'}</span>
            </button>
          ))}
        </div>

        <div key={active} className="fade-up" style={{ position: 'sticky', top: 32 }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>{M[active].year}</div>
          <h3 className="display" style={{ fontSize: 56, lineHeight: 1, margin: '0 0 24px' }}>
            {M[active].title}
          </h3>
          <p style={{ fontSize: 17, lineHeight: 1.5, color: 'var(--ink-2)', maxWidth: '50ch', margin: 0 }}>
            {M[active].body}
          </p>
        </div>
      </div>

      {/* Pull quote from Askim Museum */}
      <Divider label="From the archive · Askim Museum" />
      <blockquote style={{ margin: 0, maxWidth: 760 }}>
        <p style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 24, lineHeight: 1.35, margin: '0 0 20px', color: 'var(--ink)' }}>
          Necessity teaches a naked woman to spin, goes an old proverb, but Rolf Evensen can testify that necessity also teaches shoeless people to shoe themselves. During the war, people would come in carrying old handbags, suitcases, and similar items, expressing the wish to have these objects converted into footwear. It was often possible.
        </p>
        <footer className="mono" style={{ color: 'var(--stone)' }}>-- Askim Museum, recounting Rolf Evensen</footer>
      </blockquote>

      {/* Store photo in milestones section */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, margin: '40px 0' }}>
        <div style={{ background: '#EFEFED' }}>
          <img src="assets/store-community.jpg" alt="NM store community" loading="lazy" style={{ width: '100%', height: 'auto', display: 'block' }} />
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 16 }}>
          <div className="mono" style={{ color: 'var(--stone)' }}>The store today</div>
          <p style={{ fontWeight: 700, fontSize: 22, lineHeight: 1.2, letterSpacing: '-0.01em', margin: 0 }}>Steen &amp; Strøm, Oslo -- the first NM Community Center.</p>
          <p style={{ fontSize: 14, lineHeight: 1.55, color: 'var(--ink-2)', margin: 0 }}>Opened in 2024. The result of meticulous planning together with the founding family and partners. A milestone for a brand built on 108 years of craft.</p>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 48 }}>
        <p style={{ fontSize: 16, lineHeight: 1.55, color: 'var(--ink-2)' }}>
          Founded in Oslo in 2017, New Movements has grown into a premium leader in size-inclusive, circular footwear. Rooted in respect for nature, European craftsmanship, and family heritage, the brand combines innovation with tradition to create shoes of exceptional quality.
        </p>
        <p style={{ fontSize: 16, lineHeight: 1.55, color: 'var(--ink-2)' }}>
          Circularity has been part of New Movements' DNA from the very first shoe, culminating in the launch of the world\'s first shoe recycling program. Today the brand continues its journey toward a new standard of responsible premium craftsmanship: shaping the modern wardrobe with purpose, precision, and quiet confidence.
        </p>
      </div>

      {/* Martin\'s words */}
      <Divider label="In Martin\'s words" />
      <blockquote style={{ margin: 0, maxWidth: 820 }}>
        <p style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 22, lineHeight: 1.45, margin: '0 0 20px', color: 'var(--ink-2)' }}>
          The opening of our first store at Steen & Strøm is an emotional and proud moment for all of us at New Movements. The store is the result of meticulous planning and construction together with our family and partners, representing our dedication to quality materials and fashion with a positive impact. Since the beginning, we\'ve aimed to create products that consider the planet without compromising on style.
        </p>
        <footer className="mono" style={{ color: 'var(--stone)' }}>-- Martin Evensen, founder and designer</footer>
      </blockquote>
    </div>
  );
}

window.ModuleHistory = ModuleHistory;
function ModuleSales({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('sales'); }, []);

  const TRY = window.NMData.tryOnSteps;

  const iceBreakers = [
    'Is this the first time seeing New Movements?',
    'Have you heard about us before?',
    'Are we establishing a first impression?',
    'Yes, we sell shoes!',
    'Hello, how are you today?',
    'That shoe has a funny story to it!',
    'Let me give you a 20 second elevator pitch.',
    'Let me tell you who we are, and what we do.',
    'Nice shoes!',
    'English or Norwegian? Oh English, where are you from?... You came all this way just to get shoes?',
    'Our designs are at the crossroads between casual and formal.',
    'We want our shoes to work with both everyday clothes as well as more formal outfits.',
  ];

  const movement = [
    'Motion in the store attracts customers',
    'Each pair sold spreads the word',
    'Genuine connections grow the community',
    'More stores = more visibility everywhere',
  ];

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="03 · Brand knowledge"
        week="Week 2"
        mins={15}
        img="assets/modules/sales-philosophy.jpg"
        title={<>Sales philosophy.</>}
        lede="The store is a community center, not a transaction floor. Story over pressure. Movement creates movements."
      />

      {/* Founder quote */}
      <div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '48px' }}>
        <p style={{ fontFamily: 'var(--sans)', fontStyle: 'italic', fontSize: 'clamp(18px, 2.5vw, 26px)', lineHeight: 1.45, margin: '0 0 20px', maxWidth: '52ch' }}>
          "New Movements is never on sale. We make products that last, and when their lifetime is over, you can repair or recycle your shoes for a 20% discount on the next pair. It is okay if you don't buy shoes today. Let me show you how we make them, why we make them, and what we can offer."
        </p>
        <div className="mono" style={{ opacity: 0.55 }}>Martin Evensen, founder</div>
      </div>

      {/* The store as a community center */}
      <Divider label="The store as a community center" />
      <div style={{ maxWidth: 720 }}>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', margin: '0 0 20px' }}>
          A New Movements store is not just a retail space. It's a place where customers engage with the brand, learn the story, feel the materials, and experience the culture and the people behind the products. A customer can stop by for a conversation and leave with a lasting impression, even without buying anything.
        </p>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink)', margin: 0, fontWeight: 600 }}>
          Your goal is never to push a sale. It's to create a genuine connection that brings the customer back.
        </p>
      </div>

      {/* Movement creates movements */}
      <Divider label="Movement creates movements" />
      <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', maxWidth: 680, margin: '0 0 28px' }}>
        Every action compounds. A pair sold is a pair walking around the city. A conversation started is a community member gained. A store opened drives e-commerce. The more you do, the more happens -- so show up with energy and make things move.
      </p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 0, border: '1px solid var(--line-soft)' }}>
        {movement.map((m, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '40px 1fr', gap: 20, padding: '18px 24px', borderBottom: i < movement.length - 1 ? '1px solid var(--line-soft)' : 'none', alignItems: 'baseline' }}>
            <div className="mono" style={{ color: 'var(--stone)' }}>0{i+1}</div>
            <div style={{ fontSize: 15 }}>{m}</div>
          </div>
        ))}
      </div>
      <div style={{ marginTop: 24, padding: '20px 24px', background: 'var(--paper-2)', borderLeft: '3px solid var(--stone)' }}>
        <p style={{ fontSize: 14, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>
          One way to create movement in the store is to take a few shoeboxes and place them around the store, making the environment more alive, friendly, and welcoming.
        </p>
      </div>

      {/* Be yourself */}
      <Divider label="Be yourself" />
      <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', maxWidth: 680, margin: 0 }}>
        New Movements hires people, not experience. What we look for is curiosity, warmth, and a genuine interest in others. The best interactions in this store happen when the person behind the counter isn't performing a role -- they're simply being human. Your personality isn't something to manage or dial down. Customers remember people, not scripts.
      </p>

      {/* Low gravity */}
      <Divider label="Low gravity -- meeting the customer" />
      <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', maxWidth: 680, margin: '0 0 28px' }}>
        Low gravity means meeting customers at their level, physically and emotionally. If a customer sits down, squat down when talking to them. Read the room: are they friendly and open, or focused and professional? Be curious. Let shoes come up organically.
      </p>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0, border: '1px solid var(--line-soft)' }}>
        <div style={{ padding: '28px 24px', borderRight: '1px solid var(--line-soft)' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 14 }}>Adapt your approach</div>
          <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>Match the customer's energy. A relaxed customer wants a conversation. A direct customer wants answers. Read the signals.</p>
        </div>
        <div style={{ padding: '28px 24px' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 14 }}>Be genuinely curious</div>
          <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>Ask about their life, not just their shoe size. Curiosity builds trust, and trust converts customers.</p>
        </div>
      </div>

      {/* Storytelling */}
      <Divider label="Storytelling" />
      <div style={{ maxWidth: 720 }}>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', margin: '0 0 20px' }}>
          There is no buying pressure in a New Movements store. We convert customers by sharing the story, the vision, and the products with genuine passion and ownership. Some customers buy today. Some buy next month. The story stays with them either way.
        </p>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', margin: 0 }}>
          You are one of the people behind the products, which means you need to know them inside out. Materials, sizing, production, sustainability. Be confident enough to answer any question without hesitation.
        </p>
      </div>

      {/* Perfect try-on */}
      <Divider label="The perfect try-on" />
      <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', maxWidth: 680, margin: '0 0 28px' }}>
        Every try-on should be set up for success. The goal is that the customer truly feels what our shoes are like.
      </p>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0, border: '1px solid var(--line-soft)' }}>
        <div style={{ padding: 28, borderRight: '1px solid var(--line-soft)' }}>
          <div className="mono" style={{ color: 'var(--ink)', marginBottom: 20 }}>Always do</div>
          {TRY.map((t, i) => (
            <div key={i} style={{ padding: '12px 0', borderBottom: i < TRY.length - 1 ? '1px dashed var(--line-soft)' : 'none', display: 'flex', gap: 14, alignItems: 'baseline' }}>
              <span className="mono" style={{ color: 'var(--stone)', minWidth: 24 }}>0{i+1}</span>
              <span style={{ fontSize: 14 }}>{t.do}</span>
            </div>
          ))}
        </div>
        <div style={{ padding: 28, background: 'var(--paper-2)' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 20 }}>Avoid</div>
          {TRY.map((t, i) => (
            <div key={i} style={{ padding: '12px 0', borderBottom: i < TRY.length - 1 ? '1px dashed var(--line-soft)' : 'none', display: 'flex', gap: 14, alignItems: 'baseline' }}>
              <span className="mono" style={{ color: 'var(--stone)', minWidth: 24 }}>0{i+1}</span>
              <span style={{ fontSize: 14, color: 'var(--ink-3)' }}>{t.dont}</span>
            </div>
          ))}
        </div>
      </div>

      {/* Upselling and cross-selling */}
      <Divider label="Upselling and cross-selling" />
      <div style={{ maxWidth: 720 }}>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', margin: '0 0 20px' }}>
          Offering protection spray, leather wax, or spare laces isn't a sales tactic -- it's good advice. You genuinely want these products to last. When customers hear that, they feel it. Only offer what actually makes sense for the shoes they're buying.
        </p>
        <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', margin: 0 }}>
          If a size or model is out of stock, never return from the stockroom empty-handed. Bring something similar in fit, color, or style. Most customers are open to trying it. Always bring two sizes from the stockroom.
        </p>
      </div>

      {/* Customer club and recycling */}
      <Divider label="Customer club and recycling program" />
      <p style={{ fontSize: 16, lineHeight: 1.65, color: 'var(--ink-2)', maxWidth: 680, margin: '0 0 28px' }}>
        New Movements has one of the strongest customer retention programs in the industry, built on two pillars:
      </p>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0, border: '1px solid var(--line-soft)' }}>
        <div style={{ padding: '32px 28px', borderRight: '1px solid var(--line-soft)' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 12 }}>Customer club</div>
          <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>A small discount on the first purchase, plus direct access to newsletters, new collections, and brand updates. It also gives us insight into who our customers are, improving how we reach them.</p>
        </div>
        <div style={{ padding: '32px 28px', background: 'var(--ink)', color: 'var(--paper)' }}>
          <div className="mono" style={{ opacity: 0.6, marginBottom: 12 }}>Recycling program</div>
          <p style={{ fontSize: 15, lineHeight: 1.6, opacity: 0.85, margin: 0 }}>When shoes reach the end of their life, customers return them for a 20% discount on their next pair. It closes the loop, and keeps customers engaged with the brand's mission.</p>
        </div>
      </div>

      {/* Ice breakers */}
      <Divider label="Ice-breakers" />
      <p style={{ fontSize: 15, lineHeight: 1.55, color: 'var(--ink-2)', maxWidth: '56ch', margin: '0 0 24px' }}>
        Use these to open a conversation naturally. Not scripts -- starting points. Write down anything else you come up with.
      </p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 0, border: '1px solid var(--line-soft)' }}>
        {iceBreakers.map((line, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '40px 1fr', gap: 20, padding: '16px 24px', borderBottom: i < iceBreakers.length - 1 ? '1px solid var(--line-soft)' : 'none', alignItems: 'baseline' }}>
            <div className="mono" style={{ color: 'var(--stone)' }}>{String(i+1).padStart(2,'0')}</div>
            <div style={{ fontStyle: 'italic', fontSize: 15, lineHeight: 1.5 }}>"{line}"</div>
          </div>
        ))}
      </div>

      {/* One liners */}
      <Divider label="One liners" />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 720 }}>
        {[
          'We are making products and services to improve people\'s lives for the better, more effective, more comfortable -- and our products can always be even better.',
          'In NM we are ready to change the course if one idea is better than the existing. We believe that our products and services can never be better than the people, materials, and processes behind them.',
          'We are working towards creating positive ripple effects in this world.',
        ].map((line, i) => (
          <div key={i} style={{ padding: '20px 24px', background: 'var(--paper-2)', borderLeft: '3px solid var(--line-soft)' }}>
            <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0, fontStyle: 'italic' }}>"{line}"</p>
          </div>
        ))}
      </div>
    </div>
  );
}

window.ModuleSales = ModuleSales;
// CHANGED: real product images added, lazy loading, fallback bg
function ModuleProducts({ markComplete }) {
  const [collection, setCollection] = React.useState('SS');
  const [filter, setFilter] = React.useState('All');
  const [sel, setSel] = React.useState(null);
  React.useEffect(() => { markComplete && markComplete('products'); }, []);

  // CHANGED: image map -- slug to asset path
  const IMG = {
    'allrounder-vegan':   'assets/products/allrounder-vegan.jpg',
    'allrounder-e':       'assets/products/allrounder-e.jpg',
    'allrounder-s':       'assets/products/allrounder-s.jpg',
    'original-leather':   'assets/products/original-leather.jpg',
    'original-nubuck':    'assets/products/original-nubuck.jpg',
    'classic-vegan':      null,
    'classic-leather':    null,
    'zen-tech-suede':     'assets/products/zen-tech-suede.jpg',
    'zen-suede':          'assets/products/zen-suede.jpg',
    'zen-tech-nubuck':    'assets/products/zen-tech-nubuck.jpg',
    'zen-air':            'assets/products/zen-air.jpg',
    'moss':               'assets/products/moss.jpg',
    'loafer':             'assets/products/loafer.jpg',
    'allrounder-y':       'assets/products/allrounder-y.jpg',
    'norwegian-sneaker':  'assets/products/norwegian-sneaker.jpg',
    'low-cloud':          'assets/products/low-cloud-boot.jpg',
    'cloud':              'assets/products/cloud-boot.jpg',
    'flow':               'assets/products/flow-boot.jpg',
    'space':              'assets/products/space-boot.jpg',
    'high-flow':          null,
  };

  const list = window.NMData.products[collection];
  const tags = ['All', ...Array.from(new Set(list.map(p => p.tag)))];
  const filtered = filter === 'All' ? list : list.filter(p => p.tag === filter);

  React.useEffect(() => { setFilter('All'); setSel(null); }, [collection]);

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="04 · Brand knowledge"
        week="Week 2-5"
        mins={25}
        title={<>The catalogue.</>}
        lede="Handcrafted in Felgueiras, Portugal. Sizes 35-47(48). Recycled-PET laces, recycled cardboard packaging -- across every model."
      />

      {/* Collection switch */}
      <div style={{ display: 'flex', gap: 0, marginBottom: 24, border: '1px solid var(--line-soft)' }}>
        {[
          ['SS', 'Spring / Summer'],
          ['AW', 'Autumn / Winter'],
          ['Boots', 'Boots -- All-weather'],
        ].map(([key, label]) => (
          <button
            key={key}
            onClick={() => setCollection(key)}
            style={{
              all: 'unset', cursor: 'pointer',
              flex: 1, padding: '18px 20px', textAlign: 'left',
              borderRight: '1px solid var(--line-soft)',
              background: collection === key ? 'var(--ink)' : 'transparent',
              color: collection === key ? 'var(--paper)' : 'var(--ink)',
              transition: 'all 0.2s ease', minHeight: 64,
            }}
          >
            <div className="mono" style={{ marginBottom: 4, opacity: 0.6 }}>{key}</div>
            <div style={{ fontSize: 14, fontWeight: 500 }}>{label}</div>
          </button>
        ))}
      </div>

      {/* Filter chips */}
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 32 }}>
        {tags.map(t => (
          <button
            key={t}
            onClick={() => setFilter(t)}
            style={{
              all: 'unset', cursor: 'pointer',
              padding: '8px 16px', minHeight: 44,
              border: '1px solid ' + (filter === t ? 'var(--ink)' : 'var(--line-soft)'),
              borderRadius: 999, fontSize: 12,
              background: filter === t ? 'var(--ink)' : 'transparent',
              color: filter === t ? 'var(--paper)' : 'var(--ink)',
              fontFamily: 'var(--mono)', textTransform: 'uppercase', letterSpacing: '0.08em',
            }}
          >{t}</button>
        ))}
      </div>

      {/* Grid -- CHANGED: real images with lazy loading and grey fallback */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 1, background: 'var(--line-soft)', border: '1px solid var(--line-soft)' }}>
        {filtered.map((p) => {
          const imgSrc = IMG[p.id];
          return (
            <button
              key={p.id}
              onClick={() => setSel(p)}
              className="focus-ring"
              style={{
                all: 'unset', cursor: 'pointer',
                background: 'var(--paper)',
                padding: 0,
                minHeight: 300,
                display: 'flex', flexDirection: 'column',
                transition: 'background 0.2s ease',
              }}
              onMouseEnter={(e) => e.currentTarget.style.background = 'var(--paper-2)'}
              onMouseLeave={(e) => e.currentTarget.style.background = 'var(--paper)'}
            >
              {/* CHANGED: product image with fallback */}
              <div style={{
                width: '100%', aspectRatio: '1/1',
                background: '#EFEFED', overflow: 'hidden',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
              }}>
                {imgSrc ? (
                  <img
                    src={imgSrc}
                    alt={p.name}
                    loading="lazy"
                    style={{ width: '100%', height: 'auto', display: 'block' }}
                    onError={(e) => { e.target.style.display = 'none'; }}
                  />
                ) : (
                  <div className="mono" style={{ color: 'var(--stone-light)', fontSize: 10 }}>Image coming soon</div>
                )}
              </div>
              <div style={{ padding: '16px 18px', flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
                <div>
                  <div className="mono" style={{ color: 'var(--stone)', marginBottom: 6 }}>{p.tag}</div>
                  <div style={{ fontWeight: 700, fontSize: 16, lineHeight: 1.1, marginBottom: 6 }}>{p.name}</div>
                  <p style={{ fontSize: 12, lineHeight: 1.4, color: 'var(--ink-3)', margin: 0 }}>{p.feature}</p>
                </div>
                <div className="mono" style={{ color: 'var(--stone)', marginTop: 12, fontSize: 10 }}>
                  {p.colors.length} colour{p.colors.length === 1 ? '' : 's'} · tap for details
                </div>
              </div>
            </button>
          );
        })}
      </div>

      {/* Detail drawer */}
      {sel && (
        <div
          onClick={() => setSel(null)}
          style={{ position: 'fixed', inset: 0, background: 'rgba(14,14,12,0.4)', zIndex: 50, display: 'flex', justifyContent: 'flex-end' }}
        >
          <div
            onClick={(e) => e.stopPropagation()}
            className="fade-up scroll-hide"
            style={{ width: 'min(520px, 100vw)', height: '100%', background: 'var(--paper)', overflowY: 'auto', boxShadow: '-20px 0 40px rgba(0,0,0,0.05)' }}
          >
            {/* Image header */}
            {IMG[sel.id] && (
              <div style={{ width: '100%', aspectRatio: '1/1', background: '#EFEFED', overflow: 'hidden' }}>
                <img src={IMG[sel.id]} alt={sel.name} loading="lazy" style={{ width: '100%', height: 'auto', display: 'block' }} />
              </div>
            )}
            <div style={{ padding: '28px 32px' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
                <span className="mono" style={{ color: 'var(--stone)' }}>{sel.tag}</span>
                <button onClick={() => setSel(null)} style={{ all: 'unset', cursor: 'pointer', fontSize: 22, minWidth: 44, minHeight: 44, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>✕</button>
              </div>
              <h3 style={{ fontFamily: 'var(--sans)', fontWeight: 700, fontSize: 40, letterSpacing: '-0.025em', lineHeight: 1, margin: '0 0 16px' }}>{sel.name}</h3>
              <p style={{ fontStyle: 'italic', fontSize: 17, lineHeight: 1.4, color: 'var(--ink-2)', margin: '0 0 28px' }}>{sel.pitch}</p>
              <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: '14px 24px', borderTop: '1px solid var(--line-soft)', paddingTop: 20 }}>
                <div className="mono" style={{ color: 'var(--stone)' }}>Material</div>
                <div style={{ fontSize: 14 }}>{sel.material}</div>
                <div className="mono" style={{ color: 'var(--stone)' }}>Key feature</div>
                <div style={{ fontSize: 14 }}>{sel.feature}</div>
                <div className="mono" style={{ color: 'var(--stone)' }}>Colours</div>
                <div style={{ fontSize: 14 }}>{sel.colors.join(' · ')}</div>
                <div className="mono" style={{ color: 'var(--stone)' }}>Sizes</div>
                <div style={{ fontSize: 14 }}>35-{collection === 'Boots' ? '48' : '47(48)'}</div>
                <div className="mono" style={{ color: 'var(--stone)' }}>Outsole</div>
                <div style={{ fontSize: 14 }}>Natural &amp; recycled rubber</div>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

window.ModuleProducts = ModuleProducts;
function ModuleSustainability({ markComplete }) {
  const [step, setStep] = React.useState(0);
  React.useEffect(() => { markComplete && markComplete('sustain'); }, []);

  const Stats = window.NMData.sustainStats;
  const Mat = window.NMData.materials;
  const Flow = window.NMData.recyclingFlow;

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="05 · Brand knowledge"
        week="Week 4"
        mins={14}
        img="assets/modules/sustainability.jpg"
        title={<>Sustainability.</>}
        lede="Not a feature. The reason the brand exists. Circularity wasn't invented by NM -- it was inherited."
      />

      {/* The industry problem */}
      <Divider label="The industry problem" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 24 }}>
        {Stats.map((s, i) => <StatBlock key={i} {...s} />)}
      </div>

      {/* Rolf's story */}
      <Divider label="Where it started · Rolf\'s story" />
      <div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '56px 48px', position: 'relative' }}>
        <div className="mono" style={{ opacity: 0.5, marginBottom: 24 }}>2nd Generation · Rolf Evensen</div>
        <p className="display" style={{ fontSize: 'clamp(28px, 3.6vw, 44px)', lineHeight: 1.15, margin: '0 0 24px', maxWidth: '24ch', fontStyle: 'italic' }}>
          During WW2, wartime shortages left materials scarce. Rolf responded by asking people to bring their old suitcases -- repurposing the leather to craft durable shoes. Unknowingly pioneering circular footwear.
        </p>
        <p style={{ fontSize: 14, lineHeight: 1.55, opacity: 0.7, maxWidth: '54ch', margin: 0 }}>
          Today, this story forms the foundation of our design philosophy: to create products that endure and can be renewed or recycled at the end of their life. Circularity wasn\'t invented by New Movements -- it was inherited.
        </p>
      </div>

      {/* Materials */}
      <Divider label="Materials & production" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 0, border: '1px solid var(--line-soft)' }}>
        {Mat.map((m, i) => (
          <div key={i} style={{
            padding: '24px 24px',
            borderRight: i % 2 === 0 ? '1px solid var(--line-soft)' : 'none',
            borderBottom: i < Mat.length - 2 ? '1px solid var(--line-soft)' : 'none',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 14 }}>
              <div className="mono" style={{ color: 'var(--ink)' }}>{m.name}</div>
              <div className="mono" style={{ color: 'var(--stone)' }}>{m.spec}</div>
            </div>
            <p style={{ fontSize: 14, lineHeight: 1.5, color: 'var(--ink-2)', margin: 0 }}>{m.body}</p>
          </div>
        ))}
      </div>

      {/* Recycling flow -- interactive */}
      <Divider label="The recycling program · click each step" />
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
        {Flow.map((f, i) => (
          <button
            key={i}
            onClick={() => setStep(i)}
            className="focus-ring"
            style={{
              all: 'unset', cursor: 'pointer',
              padding: '32px 24px',
              minHeight: 240,
              border: '1px solid ' + (step === i ? 'var(--ink)' : 'var(--line-soft)'),
              background: step === i ? 'var(--paper-2)' : 'var(--paper)',
              transition: 'all 0.2s ease',
              display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
            }}
          >
            <div className="display" style={{ fontSize: 64, lineHeight: 0.9, color: step === i ? 'var(--ink)' : 'var(--stone-light)' }}>{f.n}</div>
            <div>
              <div className="display" style={{ fontSize: 22, lineHeight: 1.1, marginBottom: 10 }}>{f.title}</div>
              <p style={{ fontSize: 13, lineHeight: 1.5, color: 'var(--ink-2)', margin: 0 }}>{f.body}</p>
            </div>
          </button>
        ))}
      </div>

      {/* LWG box */}
      <Divider label="LWG Gold certification" />
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.6fr', gap: 48, alignItems: 'start' }}>
        <div className="display" style={{ fontSize: 88, lineHeight: 0.9 }}>
          85<span style={{ fontFamily: 'var(--mono)', fontSize: 22, verticalAlign: 'top' }}>%</span>
        </div>
        <div>
          <p style={{ fontSize: 16, lineHeight: 1.55, color: 'var(--ink-2)', margin: '0 0 16px', maxWidth: '54ch' }}>
            Tanneries are assessed across <strong>17 sections</strong> -- energy, water, chemicals, waste, air emissions, supply chain traceability. To achieve <strong>Gold</strong>, a tannery must score above <strong>85% in every section</strong>. No exceptions: a lower score in any single area pulls the overall rating down.
          </p>
          <p style={{ fontSize: 14, lineHeight: 1.5, color: 'var(--ink-3)', margin: 0, maxWidth: '54ch' }}>
            Gold also requires full traceability -- every hide can be tracked. We chose Gold-certified leather because it represents the most rigorous independently verified standard available today.
          </p>
        </div>
      </div>

      {/* Ethical production */}
      <Divider label="Ethical production & European manufacturing" />
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 48 }}>
        <p style={{ fontSize: 16, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>
          Sustainability also means taking care of people. We are committed to ethical production practices -- choosing manufacturing partners who provide fair wages, safe working conditions, and respect for their workers. Our production sites in Portugal and Spain exemplify our commitment to ethical standards.
        </p>
        <p style={{ fontSize: 16, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>
          The workers at our manufacturing plants work 8-hour shifts, Monday to Friday. Overtime is only allowed with additional pay, and it is not possible to work more than two extra hours per day. All workers have access to 22 vacation days per year. We frequently visit our suppliers to ensure they continuously live up to our expectations.
        </p>
      </div>

      {/* SINTEF */}
      <Divider label="Looking ahead · SINTEF partnership" />
      <p style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 26, lineHeight: 1.3, color: 'var(--ink)', maxWidth: '32ch', margin: 0 }}>
        New Movements is partnering with SINTEF, one of Europe\'s leading research institutes, to develop new materials and production methods that don\'t yet exist. The goal is a fully closed-loop system where shoes can go from shoe to shoe to shoe, without any material going to waste.
      </p>
    </div>
  );
}

window.ModuleSustainability = ModuleSustainability;
// CHANGED: Culture module added to Sales Associate path
function ModuleCulture({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('culture'); }, []);

  const principles = [
    { title: 'Contribution focus', body: 'Ask yourself: how can I contribute? There are no internal politics at NM. Once a decision is made, we respect it and execute together.' },
    { title: 'Meritocracy', body: 'Good ideas come from anyone, regardless of position. Active listening is expected from everyone -- including management.' },
    { title: 'Transparency & honesty', body: 'Openness doesn\'t mean real-time. Take time to prepare before sharing difficult information. But share it.' },
    { title: 'Clarity & feedback', body: 'Don\'t soften feedback to the point of losing meaning. If you react negatively, colleagues stop giving feedback -- and learning stops for everyone.' },
    { title: 'Flat structure, resilience', body: 'Everyone is heard. Management decides. Face challenges as opportunities to grow -- not obstacles to avoid.' },
  ];

  const core = [
    { label: 'Core belief', value: '"People with passion can change the world."' },
    { label: 'Who we are', value: 'Circular products · Bigger than ourselves · Challenge the status quo' },
    { label: 'Mission', value: '"Make circularity the new standard for premium footwear globally, by 2035."' },
  ];

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="06 · Culture"
        week="Week 1"
        mins={10}
        img="assets/modules/culture.jpg"
        title={<>Our culture.</>}
        lede="People with passion can change the world. This is how we work together -- and what we expect from each other."
      />

      {/* Core statements */}
      <Divider label="Foundation" />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 0, border: '1px solid var(--line-soft)' }}>
        {core.map((c, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '180px 1fr', gap: 0, padding: '22px 24px', borderBottom: i < core.length - 1 ? '1px solid var(--line-soft)' : 'none', alignItems: 'baseline' }}>
            <div className="mono" style={{ color: 'var(--stone)' }}>{c.label}</div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 500, lineHeight: 1.4 }}>{c.value}</div>
          </div>
        ))}
      </div>

      {/* Principles */}
      <Divider label="How we work together" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 1, background: 'var(--line-soft)', border: '1px solid var(--line-soft)' }}>
        {principles.map((p, i) => (
          <div key={i} style={{ background: 'var(--paper)', padding: '28px 24px', minHeight: 180 }}>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 12 }}>0{i+1}</div>
            <div style={{ fontWeight: 700, fontSize: 17, marginBottom: 10 }}>{p.title}</div>
            <p style={{ fontSize: 14, lineHeight: 1.5, color: 'var(--ink-2)', margin: 0 }}>{p.body}</p>
          </div>
        ))}
      </div>

      {/* Scenario */}
      <Divider label="Floor scenario" />
      <div style={{ background: 'var(--paper-2)', padding: '36px 32px', maxWidth: 700 }}>
        <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>Practise this</div>
        <p style={{ fontFamily: 'var(--sans)', fontWeight: 700, fontSize: 20, lineHeight: 1.3, margin: '0 0 16px' }}>
          "A colleague gives you critical feedback on how you handled a customer. Walk me through how you'd respond."
        </p>
        <p style={{ fontSize: 14, lineHeight: 1.5, color: 'var(--ink-2)', margin: 0 }}>
          Also try: "You disagree with a decision the manager has made. What do you do?"
        </p>
      </div>

      {/* Full PDF */}
      <Divider label="Full document" />
      <a
        href="assets/Our_Culture.pdf"
        target="_blank"
        rel="noopener"
        style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          padding: '22px 24px', border: '1px solid var(--line-soft)',
          color: 'var(--ink)', textDecoration: 'none',
          transition: 'all 0.18s ease',
        }}
        onMouseEnter={e => e.currentTarget.style.background = 'var(--ink)' && (e.currentTarget.style.color = 'var(--paper)')}
        onMouseLeave={e => { e.currentTarget.style.background = ''; e.currentTarget.style.color = 'var(--ink)'; }}
      >
        <span>
          <div style={{ fontWeight: 700, fontSize: 18, marginBottom: 4 }}>Our Culture & Foundation</div>
          <div className="mono" style={{ color: 'var(--stone)' }}>PDF · Open in new tab</div>
        </span>
        <span style={{ fontSize: 22 }}>↗</span>
      </a>
    </div>
  );
}

window.ModuleCulture = ModuleCulture;
// CHANGED: Vision module -- replaces Growth Strategy in SA path
function ModuleVision({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('vision'); }, []);

  const pillars = [
    { n: '01', title: 'Premium quality', body: 'Every material choice is intentional. Every stitch has a reason. Quality is not a marketing word -- it\'s a standard applied to people, products, and service.' },
    { n: '02', title: 'Ethical production', body: 'Handcrafted in Felgueiras, Portugal. Workers on 8-hour shifts, fair wages, 22 vacation days. We visit frequently. We hold them to our standards because we hold ourselves to them.' },
    { n: '03', title: 'Circular by design', body: 'From the first shoe, circularity was built in -- not added on. The recycling program, the SINTEF partnership, the material choices: all in service of a closed loop.' },
  ];

  const milestoneTargets = [
    { year: '2025', target: 'Expand NM Community Center footprint in Oslo' },
    { year: '2027', target: 'Fully closed-loop recycling in partnership with SINTEF' },
    { year: '2030', target: 'Carbon-neutral across full supply chain' },
    { year: '2035', target: 'Make circularity the new standard for premium footwear globally' },
  ];

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="07 · Vision"
        week="Week 1"
        mins={8}
        img="assets/modules/vision.jpg"
        title={<>Vision.</>}
        lede='"Make circularity the new standard for premium footwear globally, by 2035." Not a slogan -- a direction every store decision either moves toward or away from.'
      />

      {/* The headline vision */}
      <Divider label="The headline" />
      <div style={{ background: 'var(--ink)', color: 'var(--paper)', padding: '56px 48px' }}>
        <div className="mono" style={{ opacity: 0.5, marginBottom: 20 }}>NM Mission 2035</div>
        <div className="display" style={{ fontSize: 'clamp(36px, 5vw, 72px)', lineHeight: 1, maxWidth: '18ch' }}>
          Make circularity the new standard.
        </div>
        <p style={{ fontSize: 16, lineHeight: 1.55, opacity: 0.75, maxWidth: '50ch', margin: '28px 0 0' }}>
          Premium quality, ethical production, circular by design. These three commitments are not in tension with each other. They belong together -- and NM exists to prove it.
        </p>
      </div>

      {/* Three pillars */}
      <Divider label="Three commitments" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 0, border: '1px solid var(--line-soft)' }}>
        {pillars.map((p, i) => (
          <div key={i} style={{ padding: '32px 28px', borderRight: i < pillars.length - 1 ? '1px solid var(--line-soft)' : 'none', borderBottom: '1px solid transparent' }}>
            <div className="display" style={{ fontSize: 48, color: 'var(--stone)', lineHeight: 0.9, marginBottom: 20 }}>{p.n}</div>
            <div style={{ fontWeight: 700, fontSize: 20, marginBottom: 12 }}>{p.title}</div>
            <p style={{ fontSize: 14, lineHeight: 1.55, color: 'var(--ink-2)', margin: 0 }}>{p.body}</p>
          </div>
        ))}
      </div>

      {/* Forward roadmap */}
      <Divider label="The road ahead" />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 0, border: '1px solid var(--line-soft)' }}>
        {milestoneTargets.map((m, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '80px 1fr', gap: 24, padding: '20px 24px', borderBottom: i < milestoneTargets.length - 1 ? '1px solid var(--line-soft)' : 'none', alignItems: 'baseline' }}>
            <div className="display" style={{ fontSize: 22 }}>{m.year}</div>
            <div style={{ fontSize: 15, color: 'var(--ink-2)' }}>{m.target}</div>
          </div>
        ))}
      </div>

      {/* What it means for the floor */}
      <Divider label="What this means on the floor" />
      <p style={{ fontFamily: 'var(--sans)', fontStyle: 'italic', fontWeight: 700, fontSize: 24, lineHeight: 1.25, maxWidth: '30ch', margin: 0, color: 'var(--ink)' }}>
        Every conversation you have is brand-building. Every pair sold is the strategy made real.
      </p>
      <p style={{ fontSize: 15, lineHeight: 1.55, color: 'var(--ink-2)', maxWidth: '54ch', marginTop: 20 }}>
        The store is the most physical proof of the vision. What customers see, touch, and feel on the shop floor is what the next decade is built on. Take that seriously -- and take pride in it.
      </p>
    </div>
  );
}

window.ModuleVision = ModuleVision;
// Store Operations module -- for Sales Associate portal
function ModuleStoreOps({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('store-ops'); }, []);
  const [openIdx, setOpenIdx] = React.useState(null);

  const [openSystem, setOpenSystem] = React.useState(null);

  const systems = [
    {
      name: 'Sitoo',
      tag: 'POS & stock management',
      use: 'Your main tool on the floor. Every sale, return, stock movement, and location transfer runs through Sitoo.',
      detail: [
        { label: 'Processing a sale', body: 'Scan the barcode or search by SKU. Select size, confirm payment method. Always ask if the customer wants a receipt -- digital or printed.' },
        { label: 'Returns & exchanges', body: 'Open the original receipt in Sitoo, select the item, choose return or exchange. Shoes must be unworn and clean. Refund goes back to original payment method.' },
        { label: 'Location transfers', body: 'When moving stock from back warehouse to front: scan the item out of back location and into front location in Sitoo. Do this before handing to a customer -- no exceptions.' },
        { label: 'Stocktaking', body: 'Count physical stock and reconcile against Sitoo. Discrepancies are logged and reported to the store manager. Never adjust stock without manager approval.' },
      ],
    },
    {
      name: 'Shopify',
      tag: 'E-commerce & online orders',
      use: 'Manages the online store, product listings, discount codes, and customer data. Check it every morning at opening.',
      detail: [
        { label: 'Fulfilling online orders', body: 'Check Shopify first thing every morning. Pick the item from stock, quality check before packing, print the correct shipping label via Webshipper. Mark as fulfilled in Shopify once packed.' },
        { label: 'Discount codes', body: 'All active discount codes live in Shopify. Never create or modify codes without manager approval. Recycling program discount (20% off) is applied here.' },
        { label: 'Customer data', body: 'Customer club sign-ups are stored in Shopify. When signing up a customer in-store, use the iPad and confirm their email address carefully.' },
        { label: 'Stock sync', body: 'Shopify and Sitoo are synced. When you move stock in Sitoo, Shopify updates automatically. If you notice a discrepancy, notify the store manager immediately.' },
      ],
    },
    {
      name: 'Webshipper',
      tag: 'Shipping labels',
      use: 'Automatically generates shipping labels for online orders. Integrated with Shopify -- labels are created when an order is fulfilled.',
      detail: [
        { label: 'Bring vs DHL Express', body: 'Standard domestic orders use Bring. International or express orders use DHL Express. Webshipper selects the correct carrier automatically based on the order details.' },
        { label: 'Printing labels', body: 'After fulfilling an order in Shopify, open Webshipper and print the label. Attach it to the outside of the package -- never inside.' },
        { label: 'Tracking', body: 'Every label has a tracking number. If a customer contacts the store about a delivery, find the order in Webshipper and share the tracking link.' },
      ],
    },
    {
      name: 'Tripletex',
      tag: 'Expenses & timekeeping',
      use: 'Used for logging expenses, sick leave, holiday requests, and clocking in/out. You will use this every shift.',
      detail: [
        { label: 'Clocking in/out', body: 'Stemple inn at the start of your shift, stemple ut at the end. Do this every shift without exception -- it is your official time record.' },
        { label: 'Logging an expense', body: 'Take a photo of the receipt immediately. In Tripletex: Expense → New → upload photo, add amount, add a comment describing what it was for. Submit for approval.' },
        { label: 'Sick leave', body: 'Log absence in Tripletex on your first day out. Within the employer\'s 16-day period: select "syk tom 16 kalenderdag". Beyond 16 days: select "syk fra dag 17 kalenderdag".' },
        { label: 'Holiday requests', body: 'Browser: Ferieplanlegging → Ferieplan → add days. App: Timeføring → Oversikt → Ferie → Be om ferie. Submit at least 2 months in advance for main holiday (June-September).' },
      ],
    },
    {
      name: 'Planday',
      tag: 'Scheduling',
      use: 'Where your shifts live. Check it regularly -- schedule changes are communicated here, not by text.',
      detail: [
        { label: 'Checking your schedule', body: 'Log in to Planday to see your upcoming shifts. Notifications are sent when new shifts are published or changes are made.' },
        { label: 'Training shifts', body: 'All training shifts are tagged with the "training" label. The module name is added in the shift comments so you know what to prepare.' },
        { label: 'Touch base sessions', body: 'Your 15-minute weekly touch bases and the 30-minute two-month review are scheduled in Planday and tagged as "training" with "touchbase" in the comments.' },
        { label: 'Shift swaps', body: 'If you need to swap a shift, coordinate with the store manager first -- do not arrange swaps directly with colleagues without manager approval.' },
      ],
    },
  ];

  const returnPolicy = [
    { policy: 'Open purchase (full refund)', time: '10 days' },
    { policy: 'Exchange right', time: '30 days' },
    { policy: 'Online right of withdrawal', time: '14 days' },
    { policy: 'Warranty -- SS production', time: '2 years' },
    { policy: 'Warranty -- AW production', time: '5 years' },
  ];

  const faqs = [
    {
      q: 'How do I move stock from back to front?',
      a: 'Scan the item out of back warehouse and into front warehouse in Sitoo before handing it to a customer or placing it on the floor. No exceptions -- even if the customer is standing right there.',
    },
    {
      q: 'What do I do when a size is missing from the shelf?',
      a: 'Check back-stock first. When fetching a size, grab 1-2 extra to reduce repeat trips. Always update Shopify immediately after moving stock, even if a customer is waiting.',
    },
    {
      q: 'How do I process a complaint (reklamasjon)?',
      a: 'Fill out the complaint form in the blue binder completely. Take photos of the form and the defect. Send from the store email to sigurd@newmovements.com, cc Fabian and Ingrid. Subject: Reklamasjon. If unsure about validity, place shoes on the narrow shelf at the front stockroom entrance and notify Fabian.',
    },
    {
      q: 'How do I read a SKU code?',
      a: 'A1 or A3 at the start = Leather (dyreskinn). B1, B3, C3, C4 = Technical textile.',
    },
    {
      q: 'What is the stock formula?',
      a: 'Standard: 3 pairs in sizes 38-39 and 42-43; 2 pairs in 37, 40, 41, 44, 45; 1 pair in 35-36 and 46-47. Core Collection (winter): 18 pairs total.',
    },
  ];

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="08 · Store Operations"
        week="Week 2"
        mins={12}
        img="assets/modules/store-sop.jpg"
        title={<>Store operations.</>}
        lede="Day-to-day procedures for running the NM Community Center -- opening, closing, stock, returns, and systems."
      />

      {/* Opening */}
      <Divider label="Opening routine" />
      <div style={{ display: 'flex', flexDirection: 'column', borderTop: '1px solid var(--line-soft)' }}>
        {[
          'Clock in (stemple inn)',
          'Open cash drawer and count cash -- log in the blue binder',
          'Walk all shelves -- shoes in straight, neat lines. Clean up from the day before.',
          'Restock upsell products',
          'Check Shopify for online orders -- fulfill immediately',
          'Keep counter and stockroom tidy. Everything has a dedicated, labelled place.',
        ].map((s, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '44px 1fr', gap: 20, padding: '16px 0', borderBottom: '1px solid var(--line-soft)', alignItems: 'baseline' }}>
            <div style={{ fontWeight: 700, fontSize: 20, color: 'var(--stone)', lineHeight: 1 }}>0{i+1}</div>
            <div style={{ fontSize: 15 }}>{s}</div>
          </div>
        ))}
      </div>

      {/* Closing */}
      <Divider label="Closing routine" />
      <div style={{ display: 'flex', flexDirection: 'column', borderTop: '1px solid var(--line-soft)' }}>
        {[
          'Finish unfinished tasks, or mark clearly where you left off',
          'Clear the floor of excess products, trash, and shoe boxes',
          'Count cash and log in the blue binder',
          'Put the iPad on charge',
          'Take out trash to -2nd floor (replace the bag)',
          'Make sure the store looks clean and presentable before leaving',
          'Clock out (stemple ut)',
        ].map((s, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '44px 1fr', gap: 20, padding: '16px 0', borderBottom: '1px solid var(--line-soft)', alignItems: 'baseline' }}>
            <div style={{ fontWeight: 700, fontSize: 20, color: 'var(--stone)', lineHeight: 1 }}>0{i+1}</div>
            <div style={{ fontSize: 15 }}>{s}</div>
          </div>
        ))}
      </div>

      {/* Returns policy */}
      <Divider label="Returns & exchanges" />
      <div style={{ border: '1px solid var(--line-soft)', overflow: 'hidden' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 120px', padding: '12px 20px', background: 'var(--ink)', color: 'var(--paper)' }}>
          <div className="mono" style={{ opacity: 0.7 }}>Policy</div>
          <div className="mono" style={{ opacity: 0.7 }}>Timeframe</div>
        </div>
        {returnPolicy.map((r, i) => (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 120px', padding: '16px 20px', borderTop: '1px solid var(--line-soft)', alignItems: 'baseline' }}>
            <div style={{ fontSize: 15 }}>{r.policy}</div>
            <div style={{ fontWeight: 700, fontSize: 15 }}>{r.time}</div>
          </div>
        ))}
      </div>
      <p style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 12 }}>Shoes must be completely clean and unworn to qualify for return. Refund always requires a receipt.</p>

      {/* Systems */}
      <Divider label="Systems & tools" />
      <div style={{ display: 'flex', flexDirection: 'column', border: '1px solid var(--line-soft)' }}>
        {systems.map((s, i) => (
          <div key={i} style={{ borderBottom: i < systems.length - 1 ? '1px solid var(--line-soft)' : 'none' }}>
            <button
              onClick={() => setOpenSystem(openSystem === i ? null : i)}
              className="focus-ring"
              style={{ all: 'unset', cursor: 'pointer', width: '100%', padding: '22px 24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 24, background: openSystem === i ? 'var(--paper-2)' : 'var(--paper)', transition: 'background 0.15s ease' }}
            >
              <span style={{ display: 'flex', alignItems: 'baseline', gap: 16 }}>
                <span style={{ fontWeight: 700, fontSize: 18 }}>{s.name}</span>
                <span className="mono" style={{ color: 'var(--stone)' }}>{s.tag}</span>
              </span>
              <span style={{ fontSize: 18, color: 'var(--stone)', flexShrink: 0 }}>{openSystem === i ? '-' : '+'}</span>
            </button>
            {openSystem === i && (
              <div className="fade-up" style={{ padding: '0 24px 28px' }}>
                <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: '0 0 24px', maxWidth: '60ch' }}>{s.use}</p>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: 1, background: 'var(--line-soft)', border: '1px solid var(--line-soft)' }}>
                  {s.detail.map((d, j) => (
                    <div key={j} style={{ background: 'var(--paper)', padding: '20px 20px' }}>
                      <div className="mono" style={{ color: 'var(--stone)', marginBottom: 8 }}>{d.label}</div>
                      <p style={{ fontSize: 13, lineHeight: 1.55, color: 'var(--ink-2)', margin: 0 }}>{d.body}</p>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>
        ))}
      </div>

      {/* FAQ accordion */}
      <Divider label="Common questions" />
      <div style={{ borderTop: '1px solid var(--line-soft)' }}>
        {faqs.map((f, i) => (
          <div key={i} style={{ borderBottom: '1px solid var(--line-soft)' }}>
            <button
              onClick={() => setOpenIdx(openIdx === i ? null : i)}
              className="focus-ring"
              style={{ all: 'unset', cursor: 'pointer', width: '100%', padding: '20px 0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 20, minHeight: 64 }}
            >
              <span style={{ fontWeight: 600, fontSize: 15 }}>{f.q}</span>
              <span style={{ fontSize: 18, color: 'var(--stone)', flexShrink: 0 }}>{openIdx === i ? '-' : '+'}</span>
            </button>
            {openIdx === i && (
              <div className="fade-up" style={{ paddingBottom: 20 }}>
                <p style={{ fontSize: 14, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>{f.a}</p>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

window.ModuleStoreOps = ModuleStoreOps;
function ModulePlan({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('plan'); }, []);
  const P = window.NMData.trainingPlan;

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="02 · Onboarding SOP"
        week="Reference"
        mins={4}
        title={<>The 8-week plan.</>}
        lede="Designed for sales associates. Adapted by the store manager to suit each individual's background and pace."
      />

      <div style={{ border: '1px solid var(--line-soft)', overflow: 'hidden' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '120px 1.4fr 1.4fr 100px', padding: '14px 22px', background: 'var(--ink)', color: 'var(--paper)' }}>
          <div className="mono" style={{ opacity: 0.7 }}>Week</div>
          <div className="mono" style={{ opacity: 0.7 }}>Brand knowledge</div>
          <div className="mono" style={{ opacity: 0.7 }}>Operational training</div>
          <div className="mono" style={{ opacity: 0.7, textAlign: 'right' }}>Touchbase</div>
        </div>
        {P.map((row, i) => (
          <div key={i} style={{
            display: 'grid',
            gridTemplateColumns: '120px 1.4fr 1.4fr 100px',
            padding: '20px 22px',
            borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)',
            background: i === P.length - 1 ? 'var(--paper-2)' : 'transparent',
            alignItems: 'baseline',
          }}>
            <div className="display" style={{ fontSize: 22 }}>{row.wk}</div>
            <div style={{ fontSize: 14, color: 'var(--ink-2)' }}>
              {row.brand}
              {row.extra && <div className="mono" style={{ color: 'var(--accent)', marginTop: 4 }}>+ {row.extra}</div>}
            </div>
            <div style={{ fontSize: 14, color: 'var(--ink-2)' }}>{row.ops}</div>
            <div className="mono" style={{ color: 'var(--stone)', textAlign: 'right' }}>{row.tb}</div>
          </div>
        ))}
      </div>

      <Divider label="Five touch-bases" />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 1, background: 'var(--line-soft)', border: '1px solid var(--line-soft)' }}>
        {[
          { tb: 'TB 1', wk: 'Week 1', topic: 'Introduction', q: 'How has it matched your expectations? How have colleagues received you?', t: '15 min' },
          { tb: 'TB 2', wk: 'Week 2', topic: 'Successes & challenges', q: 'Share positive feedback. Ask about biggest challenges.', t: '15 min' },
          { tb: 'TB 3', wk: 'Week 3', topic: 'Goal setting', q: 'Set 3 concrete focus points (e.g. perfect try-on, customer club).', t: '15 min' },
          { tb: 'TB 4', wk: 'Week 4', topic: 'First-month review', q: 'Evaluate progress on the focus points. Plan training gaps.', t: '15 min' },
          { tb: 'TB 5', wk: 'Week 8', topic: 'Two-month review', q: 'Comprehensive review. Preview the validation.', t: '30 min' },
        ].map((t, i) => (
          <div key={i} style={{ background: 'var(--paper)', padding: 20, minHeight: 200 }}>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 6 }}>{t.tb} · {t.wk}</div>
            <div className="display" style={{ fontSize: 22, lineHeight: 1.1, marginBottom: 12 }}>{t.topic}</div>
            <p style={{ fontSize: 12, lineHeight: 1.45, color: 'var(--ink-2)', margin: 0 }}>{t.q}</p>
            <div className="mono" style={{ color: 'var(--accent)', marginTop: 12 }}>{t.t}</div>
          </div>
        ))}
      </div>

      <Divider label="What manager and trainee can expect" />
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0, border: '1px solid var(--line-soft)' }}>
        <div style={{ padding: 28, borderRight: '1px solid var(--line-soft)' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>Manager → Trainee</div>
          <ul style={{ margin: 0, paddingLeft: 18, fontSize: 14, lineHeight: 1.7, color: 'var(--ink-2)' }}>
            <li>Be well prepared with an agenda for every shift</li>
            <li>Always approach training in a way that benefits the trainee</li>
            <li>Communicate honestly and constructively</li>
            <li>Challenge the trainee in a motivational way</li>
            <li>Answer any questions the trainee may have</li>
          </ul>
        </div>
        <div style={{ padding: 28, background: 'var(--paper-2)' }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>Trainee → Manager</div>
          <ul style={{ margin: 0, paddingLeft: 18, fontSize: 14, lineHeight: 1.7, color: 'var(--ink-2)' }}>
            <li>Show up with a positive attitude</li>
            <li>Be eager to learn and improve</li>
            <li>Come prepared for every training shift</li>
            <li>Communicate honestly and constructively</li>
            <li>Show genuine respect for the trainer</li>
          </ul>
        </div>
      </div>
    </div>
  );
}

window.ModulePlan = ModulePlan;
function ModuleFAQ({ markComplete }) {
  React.useEffect(() => { markComplete && markComplete('faq'); }, []);
  const F = window.NMData.faq;
  const cats = ['All', ...Array.from(new Set(F.map(f => f.cat)))];
  const [cat, setCat] = React.useState('All');
  const [open, setOpen] = React.useState(0);
  const [search, setSearch] = React.useState('');

  const filtered = F.filter(f =>
    (cat === 'All' || f.cat === cat) &&
    (search === '' || (f.q + ' ' + f.a).toLowerCase().includes(search.toLowerCase()))
  );

  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="06 · Practical"
        week="Always"
        mins={8}
        title={<>Practical FAQ.</>}
        lede="Plain-language answers on sick leave, salary, holiday, and the day-to-day. Based on the Personalhåndboken."
      />

      {/* Search + category filter */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 16, marginBottom: 24, alignItems: 'center' }}>
        <input
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          placeholder="Search FAQ..."
          style={{
            all: 'unset',
            padding: '14px 18px',
            border: '1px solid var(--line-soft)',
            background: 'var(--paper)',
            fontFamily: 'var(--sans)',
            fontSize: 14,
            color: 'var(--ink)',
            width: '100%',
            boxSizing: 'border-box',
          }}
        />
        <div className="mono" style={{ color: 'var(--stone)' }}>{filtered.length} of {F.length}</div>
      </div>

      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 32 }}>
        {cats.map(c => (
          <button
            key={c}
            onClick={() => setCat(c)}
            style={{
              all: 'unset', cursor: 'pointer',
              padding: '6px 14px',
              border: '1px solid ' + (cat === c ? 'var(--ink)' : 'var(--line-soft)'),
              borderRadius: 999, fontSize: 12,
              background: cat === c ? 'var(--ink)' : 'transparent',
              color: cat === c ? 'var(--paper)' : 'var(--ink)',
              fontFamily: 'var(--mono)', textTransform: 'uppercase', letterSpacing: '0.08em',
            }}
          >{c}</button>
        ))}
      </div>

      {/* Accordion */}
      <div style={{ borderTop: '1px solid var(--line-soft)' }}>
        {filtered.map((f, i) => {
          const isOpen = open === i;
          return (
            <div key={i} style={{ borderBottom: '1px solid var(--line-soft)' }}>
              <button
                onClick={() => setOpen(isOpen ? -1 : i)}
                className="focus-ring"
                style={{
                  all: 'unset', cursor: 'pointer',
                  display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
                  padding: '24px 0', width: '100%', gap: 24,
                }}
              >
                <div style={{ display: 'flex', gap: 24, alignItems: 'baseline', flex: 1 }}>
                  <span className="mono" style={{ color: 'var(--stone)', minWidth: 100 }}>{f.cat}</span>
                  <span style={{ fontFamily: 'var(--serif)', fontSize: 22, lineHeight: 1.25, color: 'var(--ink)' }}>{f.q}</span>
                </div>
                <span style={{ fontSize: 18, color: 'var(--stone)' }}>{isOpen ? '-' : '+'}</span>
              </button>
              {isOpen && (
                <div className="fade-up" style={{ padding: '0 0 28px 124px', maxWidth: 800 }}>
                  <p style={{ fontSize: 15, lineHeight: 1.6, color: 'var(--ink-2)', margin: 0 }}>{f.a}</p>
                </div>
              )}
            </div>
          );
        })}
        {filtered.length === 0 && (
          <div style={{ padding: 60, textAlign: 'center', color: 'var(--stone)' }} className="mono">
            No matches. Try a different search.
          </div>
        )}
      </div>

      <Divider label="Disclaimer" />
      <p style={{ fontSize: 13, lineHeight: 1.6, color: 'var(--ink-3)', maxWidth: '60ch' }}>
        This is a plain-language summary of the Personalhåndboken. In any conflict between this FAQ and the full handbook or your employment contract, the contract and handbook take precedence. Last updated: April 2026.
      </p>
    </div>
  );
}

window.ModuleFAQ = ModuleFAQ;
function ModuleQuiz({ markComplete }) {
  const Q = window.NMData.quiz;
  const [idx, setIdx] = React.useState(0);
  const [answers, setAnswers] = React.useState({});
  const [submitted, setSubmitted] = React.useState(false);

  const onChoose = (qi, oi) => {
    if (submitted) return;
    setAnswers(a => ({ ...a, [qi]: oi }));
  };

  const score = Object.entries(answers).filter(([qi, oi]) => Q[qi].correct === oi).length;
  const allAnswered = Object.keys(answers).length === Q.length;

  if (submitted) {
    const pct = Math.round((score / Q.length) * 100);
    const verdict = pct >= 90 ? 'Excellent' : pct >= 75 ? 'Good' : pct >= 50 ? 'Needs work' : 'Revisit modules';
    React.useEffect(() => { if (pct >= 75) markComplete && markComplete('quiz'); }, [pct]);
    return (
      <div className="fade-up">
        <PageHeader
          eyebrow="07 · Validation check"
          week="Week 12"
          mins={10}
          title={<>Results.</>}
          lede="A self-check, not a grade. Use the explanations to find the modules to revisit."
        />
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 48, alignItems: 'center', marginBottom: 56, paddingBottom: 56, borderBottom: '1px solid var(--line-soft)' }}>
          <div>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>Your score</div>
            <div className="display" style={{ fontSize: 'clamp(96px, 14vw, 200px)', lineHeight: 0.9 }}>
              {score}<span style={{ fontFamily: 'var(--mono)', fontSize: 32, verticalAlign: 'top', color: 'var(--stone)' }}>/{Q.length}</span>
            </div>
            <div className="display-it" style={{ fontSize: 38, marginTop: 12 }}>{verdict}.</div>
          </div>
          <div>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 16 }}>Scoring guide</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              {[
                ['1 -- Needs work', 'Knows concept but can\'t explain it confidently'],
                ['2 -- Good',       'Can explain clearly to a customer'],
                ['3 -- Excellent',  'Could train someone else on this'],
              ].map(([t, d], i) => (
                <div key={i} style={{ display: 'grid', gridTemplateColumns: '140px 1fr', gap: 16, paddingBottom: 12, borderBottom: '1px dashed var(--line-soft)' }}>
                  <div className="mono" style={{ color: 'var(--ink)' }}>{t}</div>
                  <div style={{ fontSize: 13, color: 'var(--ink-2)' }}>{d}</div>
                </div>
              ))}
            </div>
          </div>
        </div>

        <Divider label="Review answers" />
        <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
          {Q.map((q, qi) => {
            const correct = answers[qi] === q.correct;
            return (
              <div key={qi} style={{ padding: '24px 0', borderBottom: '1px solid var(--line-soft)' }}>
                <div style={{ display: 'flex', gap: 16, alignItems: 'baseline', marginBottom: 14 }}>
                  <span className="mono" style={{ color: correct ? 'var(--moss)' : 'var(--terracotta)' }}>
                    {correct ? '✓' : '✗'} 0{qi+1}
                  </span>
                  <span style={{ fontFamily: 'var(--serif)', fontSize: 22, lineHeight: 1.25 }}>{q.q}</span>
                </div>
                <div style={{ paddingLeft: 36, fontSize: 14, lineHeight: 1.5 }}>
                  <div style={{ color: 'var(--ink-3)', marginBottom: 4 }}>You answered: <span style={{ color: 'var(--ink)' }}>{q.options[answers[qi]]}</span></div>
                  {!correct && <div style={{ color: 'var(--ink-3)', marginBottom: 8 }}>Correct: <span style={{ color: 'var(--ink)' }}>{q.options[q.correct]}</span></div>}
                  <div style={{ fontStyle: 'italic', fontFamily: 'var(--serif)', color: 'var(--ink-2)', marginTop: 8 }}>{q.why}</div>
                </div>
              </div>
            );
          })}
        </div>

        <div style={{ display: 'flex', gap: 12, marginTop: 40 }}>
          <button className="btn" onClick={() => { setSubmitted(false); setAnswers({}); setIdx(0); }}>
            Take it again
          </button>
        </div>
      </div>
    );
  }

  const cur = Q[idx];
  return (
    <div className="fade-up">
      <PageHeader
        eyebrow="07 · Validation check"
        week="Week 12"
        mins={10}
        title={<>Validation check.</>}
        lede="Ten questions across the modules. Designed to surface the things to revisit before your formal validation."
      />

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 24 }}>
        <div className="mono" style={{ color: 'var(--stone)' }}>Question 0{idx+1} / 0{Q.length}</div>
        <div style={{ width: 240 }}><ProgressBar value={Object.keys(answers).length} total={Q.length} /></div>
      </div>

      <div key={idx} className="fade-up" style={{ borderTop: '1px solid var(--line-soft)', borderBottom: '1px solid var(--line-soft)', padding: '40px 0', marginBottom: 32 }}>
        <h3 className="display" style={{ fontSize: 'clamp(28px, 4vw, 44px)', lineHeight: 1.1, margin: '0 0 32px', maxWidth: '32ch' }}>
          {cur.q}
        </h3>
        <div style={{ display: 'grid', gap: 10 }}>
          {cur.options.map((o, oi) => {
            const sel = answers[idx] === oi;
            return (
              <button
                key={oi}
                onClick={() => onChoose(idx, oi)}
                className="focus-ring"
                style={{
                  all: 'unset', cursor: 'pointer',
                  padding: '18px 22px',
                  border: '1px solid ' + (sel ? 'var(--ink)' : 'var(--line-soft)'),
                  background: sel ? 'var(--ink)' : 'var(--paper)',
                  color: sel ? 'var(--paper)' : 'var(--ink)',
                  display: 'flex', alignItems: 'center', gap: 16,
                  transition: 'all 0.15s ease',
                }}
              >
                <span className="mono" style={{ opacity: 0.6 }}>{String.fromCharCode(65+oi)}</span>
                <span style={{ fontSize: 15 }}>{o}</span>
              </button>
            );
          })}
        </div>
      </div>

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <button
          className="btn btn-ghost"
          onClick={() => setIdx(Math.max(0, idx - 1))}
          disabled={idx === 0}
          style={{ opacity: idx === 0 ? 0.3 : 1 }}
        >← Previous</button>
        <div className="mono" style={{ color: 'var(--stone)' }}>
          {Object.keys(answers).length} / {Q.length} answered
        </div>
        {idx < Q.length - 1 ? (
          <button className="btn" onClick={() => setIdx(idx + 1)}>Next →</button>
        ) : (
          <button
            className="btn"
            onClick={() => setSubmitted(true)}
            disabled={!allAnswered}
            style={{ opacity: allAnswered ? 1 : 0.4 }}
          >See results →</button>
        )}
      </div>
    </div>
  );
}

window.ModuleQuiz = ModuleQuiz;
// Main App shell

const { useState, useEffect, useMemo } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accentTone": "stone",
  "showGrid": false,
  "userName": "Reader",
  "density": "comfortable"
}/*EDITMODE-END*/;

function loadCompleted() {
  try { return JSON.parse(localStorage.getItem('nm_completed') || '{}'); }
  catch { return {}; }
}
function saveCompleted(c) {
  try { localStorage.setItem('nm_completed', JSON.stringify(c)); } catch {}
}

function App() {
  const [route, setRoute] = useState(() => {
    const h = window.location.hash.replace('#', '');
    return h && window.NMData.modules.find(m => m.id === h) ? h : 'home';
  });
  const [completed, setCompleted] = useState(loadCompleted);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);

  useEffect(() => {
    const onHash = () => {
      const h = window.location.hash.replace('#', '');
      if (h && window.NMData.modules.find(m => m.id === h)) setRoute(h);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  const navigate = (id) => {
    window.location.hash = id;
    setRoute(id);
    window.scrollTo({ top: 0, behavior: 'instant' });
  };

  const markComplete = (id) => {
    setCompleted(prev => {
      if (prev[id]) return prev;
      const next = { ...prev, [id]: Date.now() };
      saveCompleted(next);
      return next;
    });
  };

  const totalDone = window.NMData.modules.filter(m => m.id !== 'home' && completed[m.id]).length;
  const total = window.NMData.modules.length - 1;

  // Apply tweak: accent
  useEffect(() => {
    const tones = {
      stone: '#6B5B3F',
      moss: '#4A5240',
      terracotta: '#B05A3C',
      ink: '#0E0E0C',
    };
    document.documentElement.style.setProperty('--accent', tones[tweaks.accentTone] || tones.stone);
  }, [tweaks.accentTone]);

  // CHANGED: culture and vision modules added
  const ModuleEl = (() => {
    switch (route) {
      case 'home':    return <ModuleHome navigate={navigate} completed={completed} />;
      case 'history': return <ModuleHistory markComplete={markComplete} />;
      case 'sales':   return <ModuleSales markComplete={markComplete} />;
      case 'products':return <ModuleProducts markComplete={markComplete} />;
      case 'sustain': return <ModuleSustainability markComplete={markComplete} />;
      case 'culture': return <ModuleCulture markComplete={markComplete} />;
      case 'vision':  return <ModuleVision markComplete={markComplete} />;
      case 'store-ops': return <ModuleStoreOps markComplete={markComplete} />;
      case 'plan':    return <ModulePlan markComplete={markComplete} />;
      case 'faq':     return <ModuleFAQ markComplete={markComplete} />;
      case 'quiz':    return <ModuleQuiz markComplete={markComplete} />;
      default: return null;
    }
  })();

  const idx = window.NMData.modules.findIndex(m => m.id === route);
  const next = window.NMData.modules[idx + 1];
  const prev = window.NMData.modules[idx - 1];

  const density = tweaks.density === 'compact' ? 56 : 88;

  return (
    <>
      {/* Top bar */}
      <header style={{
        position: 'sticky', top: 0, zIndex: 30,
        background: 'rgba(255,255,255,0.94)',
        backdropFilter: 'blur(10px)',
        borderBottom: '1px solid var(--line-soft)',
        padding: '14px clamp(16px, 4vw, 32px)',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 20 }}>
          {route === 'home'
            ? <a href="index.html" style={{ display: 'flex', alignItems: 'center', textDecoration: 'none' }}>
                <img src="assets/nm-logo.png" alt="New Movements" style={{ height: 32 }} />
              </a>
            : <button onClick={() => navigate('home')} style={{ all: 'unset', cursor: 'pointer', display: 'flex', alignItems: 'center' }}>
                <img src="assets/nm-logo.png" alt="New Movements" style={{ height: 32 }} />
              </button>
          }
        </div>
      </header>

      {/* Main shell */}
      <div className="nm-shell" style={{
        display: 'grid',
        gridTemplateColumns: '260px minmax(0, 1fr)',
        gap: 0,
        minHeight: 'calc(100vh - 60px)',
      }}>
        {/* Left rail nav */}
        <nav className="nm-rail" style={{
          borderRight: '1px solid var(--line-soft)',
          padding: '40px 28px',
          position: 'sticky', top: 60, alignSelf: 'start', height: 'calc(100vh - 60px)',
          overflowY: 'auto',
        }}>
          <div className="mono" style={{ color: 'var(--stone)', marginBottom: 20 }}>Modules</div>
          <ol style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
            {window.NMData.modules.map((m, i) => {
              const active = m.id === route;
              const done = completed[m.id];
              return (
                <li key={m.id}>
                  <button
                    onClick={() => navigate(m.id)}
                    className="focus-ring"
                    style={{
                      all: 'unset', cursor: 'pointer',
                      width: '100%', boxSizing: 'border-box',
                      padding: '12px 14px',
                      display: 'flex', alignItems: 'baseline', gap: 12,
                      borderLeft: '2px solid ' + (active ? 'var(--ink)' : 'transparent'),
                      background: active ? 'var(--paper-2)' : 'transparent',
                      transition: 'all 0.15s ease',
                    }}
                    onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--paper-2)'; }}
                    onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}
                  >
                    <span className="mono" style={{ color: done ? 'var(--ink)' : 'var(--stone)', minWidth: 18 }}>
                      {done ? '●' : '○'}
                    </span>
                    <span style={{ flex: 1 }}>
                      <div style={{ fontSize: 14, lineHeight: 1.2, fontWeight: active ? 500 : 400 }}>{m.label}</div>
                    </span>
                  </button>
                </li>
              );
            })}
          </ol>

          <div style={{ borderTop: '1px solid var(--line-soft)', marginTop: 32, paddingTop: 24 }}>
            <div className="mono" style={{ color: 'var(--stone)', marginBottom: 12 }}>Resources</div>
            <div style={{ fontSize: 12, lineHeight: 2, color: 'var(--ink-3)', display: 'flex', flexDirection: 'column', gap: 2 }}>
              <a href="https://docs.google.com/document/d/1Fy4VO6qOYw0sYybJ-GzOta6TUAnoblqhx2NQI8lcRgs/edit?tab=t.0#heading=h.muomzja6uvw" target="_blank" rel="noopener" style={{ color: 'var(--ink-2)', textDecoration: 'none', borderBottom: '1px solid var(--line-soft)' }}>Personalhåndboken ↗</a>
              <a href="https://docs.google.com/document/d/10wMcymhhMN0NWEubSWTONA_Tlldg21Nk/edit?usp=drive_web&ouid=112965675031066807973&rtpof=true" target="_blank" rel="noopener" style={{ color: 'var(--ink-2)', textDecoration: 'none', borderBottom: '1px solid var(--line-soft)' }}>Validation Sheet ↗</a>
              <div style={{ color: 'var(--ink-3)' }}>Tripletex · Planday · Sitoo</div>
            </div>
          </div>
        </nav>

        {/* Center content */}
        <main style={{ padding: density + 'px clamp(32px, 5vw, 88px)', maxWidth: 1080, margin: '0 auto', width: '100%' }}>
          {ModuleEl}

          {/* Continue / prev nav */}
          <div className="nm-pagnav" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', marginTop: 96, paddingTop: 32, borderTop: '1px solid var(--line-soft)', gap: 16 }}>
            {prev ? (
              <button onClick={() => navigate(prev.id)} className="focus-ring" style={{ all: 'unset', cursor: 'pointer', flex: 1, padding: 20, border: '1px solid var(--line-soft)' }}>
                <div className="mono" style={{ color: 'var(--stone)', marginBottom: 6 }}>← Previous</div>
                <div className="display" style={{ fontSize: 22 }}>{prev.label}</div>
              </button>
            ) : <div style={{ flex: 1 }} />}
            {next ? (
              <button onClick={() => navigate(next.id)} className="focus-ring" style={{ all: 'unset', cursor: 'pointer', flex: 1, padding: 20, border: '1px solid var(--ink)', background: 'var(--ink)', color: 'var(--paper)', textAlign: 'right' }}>
                <div className="mono" style={{ opacity: 0.6, marginBottom: 6 }}>Next →</div>
                <div className="display" style={{ fontSize: 22 }}>{next.label}</div>
              </button>
            ) : <div style={{ flex: 1 }} />}
          </div>

          {/* CHANGED: footer with website, email, address */}
          <footer style={{ marginTop: 80, paddingTop: 32, borderTop: '1px solid var(--line-soft)', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 20 }}>
            <div>
              <WaveLogo size={36} />
              <div className="mono" style={{ color: 'var(--stone)', marginTop: 12 }}>New Movements AS · Steen &amp; Strøm, Oslo</div>
              <div style={{ display: 'flex', gap: 20, marginTop: 8, flexWrap: 'wrap' }}>
                <a href="https://www.newmovements.com" target="_blank" rel="noopener" className="mono" style={{ color: 'var(--stone)', textDecoration: 'none', borderBottom: '1px solid transparent', transition: 'border-color 0.15s' }} onMouseEnter={e => e.currentTarget.style.borderBottomColor='var(--stone)'} onMouseLeave={e => e.currentTarget.style.borderBottomColor='transparent'}>newmovements.com</a>
                <a href="mailto:hello@newmovements.com" className="mono" style={{ color: 'var(--stone)', textDecoration: 'none', borderBottom: '1px solid transparent', transition: 'border-color 0.15s' }} onMouseEnter={e => e.currentTarget.style.borderBottomColor='var(--stone)'} onMouseLeave={e => e.currentTarget.style.borderBottomColor='transparent'}>hello@newmovements.com</a>
              </div>
            </div>
          </footer>
        </main>

      </div>

      {/* Tweaks panel */}
      <TweaksPanel>
        <TweakSection title="Display">
          <TweakRadio
            label="Accent tone"
            value={tweaks.accentTone}
            onChange={(v) => setTweak('accentTone', v)}
            options={[
              { value: 'stone', label: 'Stone' },
              { value: 'moss', label: 'Moss' },
              { value: 'terracotta', label: 'Terra' },
              { value: 'ink', label: 'Ink' },
            ]}
          />
          <TweakRadio
            label="Density"
            value={tweaks.density}
            onChange={(v) => setTweak('density', v)}
            options={[
              { value: 'comfortable', label: 'Comfortable' },
              { value: 'compact', label: 'Compact' },
            ]}
          />
        </TweakSection>
        <TweakSection title="Progress">
          <TweakButton onClick={() => {
            saveCompleted({});
            setCompleted({});
          }}>Reset all progress</TweakButton>
          <TweakButton onClick={() => {
            const done = {};
            window.NMData.modules.forEach(m => { if (m.id !== 'home') done[m.id] = Date.now(); });
            saveCompleted(done);
            setCompleted(done);
          }}>Mark all complete</TweakButton>
        </TweakSection>
      </TweaksPanel>

      {/* Mobile bottom nav -- shown only on small screens via CSS */}
      <style>{`
        @media (max-width: 980px) {
          .nm-mobile-nav { display: block !important; }
          main { padding-bottom: 80px !important; }
        }
      `}</style>
      <nav className="nm-mobile-nav" style={{
        display: 'none',
        position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 40,
        background: 'rgba(255,255,255,0.97)',
        backdropFilter: 'blur(10px)',
        borderTop: '1px solid var(--line-soft)',
        paddingBottom: 'env(safe-area-inset-bottom, 8px)',
        overflowX: 'auto',
        WebkitOverflowScrolling: 'touch',
      }}>
        <div style={{ display: 'flex', gap: 0, minWidth: 'max-content', padding: '6px 8px' }}>
          {window.NMData.modules.map((m) => {
            const active = m.id === route;
            const done = completed[m.id];
            return (
              <button
                key={m.id}
                onClick={() => navigate(m.id)}
                style={{
                  all: 'unset', cursor: 'pointer',
                  display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                  padding: '6px 14px', minWidth: 60,
                  borderRadius: 8,
                  background: active ? 'var(--paper-2)' : 'transparent',
                  color: active ? 'var(--ink)' : 'var(--stone)',
                  transition: 'all 0.15s ease',
                }}
              >
                <span style={{ fontSize: 13, marginBottom: 3 }}>{done ? '●' : '○'}</span>
                <span style={{ fontSize: 10, fontWeight: active ? 600 : 400, letterSpacing: '0.01em', whiteSpace: 'nowrap' }}>
                  {m.label.split(' ')[0]}
                </span>
              </button>
            );
          })}
        </div>
      </nav>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
