Heading
<!DOCTYPE html> <html lang="pl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Real Estate Management — Mapa Stanowisk</title> <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&family=Fraunces:ital,wght@0,300;0,600;1,300&display=swap" rel="stylesheet"> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --bg: #F4F2EE; --surface: #FAFAF8; --core-dark: #1A3A2A; --core-mid: #2D6A4F; --core-light: #E8F5EE; --core-accent: #52B788; --nc-dark: #2C1810; --nc-mid: #8B4513; --nc-light: #FDF0E8; --nc-accent: #E07B39; --client-dark: #1A1A3A; --client-mid: #3A3A8B; --client-light: #EEEEFF; --client-accent: #6B6BD4; --supplier-dark: #0D2626; --supplier-mid: #1A5C5C; --supplier-light: #E6F5F5; --supplier-accent: #3AABAB; --ink: #1C1C1C; --ink-soft: #6B6B6B; --ink-ghost: #ADADAD; --line: #E0DDD8; --white: #FFFFFF; } body { font-family: 'DM Sans', sans-serif; background: var(--bg); color: var(--ink); min-height: 100vh; overflow-x: hidden; } /* ── TOP HEADER ──────────────────────────────────────────────────── */ .page-header { padding: 48px 60px 32px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: flex-end; background: var(--surface); } .page-header h1 { font-family: 'Fraunces', serif; font-size: 36px; font-weight: 300; letter-spacing: -0.5px; line-height: 1.1; color: var(--ink); } .page-header h1 em { font-style: italic; color: var(--core-mid); } .page-header .meta { text-align: right; font-size: 12px; color: var(--ink-ghost); font-family: 'DM Mono', monospace; letter-spacing: 0.05em; line-height: 1.8; } /* ── LEGEND ──────────────────────────────────────────────────────── */ .legend-bar { display: flex; gap: 32px; padding: 16px 60px; background: var(--surface); border-bottom: 1px solid var(--line); align-items: center; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-soft); font-family: 'DM Mono', monospace; letter-spacing: 0.04em; } .legend-dot { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; } .legend-label { font-size: 11px; } /* ── MAIN LAYOUT ─────────────────────────────────────────────────── */ .canvas { padding: 48px 60px 60px; display: flex; flex-direction: column; gap: 12px; } /* ── AXIS LABEL ──────────────────────────────────────────────────── */ .axis-row { display: grid; grid-template-columns: 120px 1fr; gap: 12px; align-items: start; } .axis-label { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-ghost); padding-top: 18px; text-align: right; padding-right: 16px; line-height: 1.4; } /* ── SECTION BLOCKS ──────────────────────────────────────────────── */ .section-block { border-radius: 12px; overflow: hidden; border: 1px solid transparent; transition: all 0.25s ease; } .section-header { display: flex; align-items: center; gap: 14px; padding: 14px 20px; cursor: pointer; position: relative; } .section-num { font-family: 'DM Mono', monospace; font-size: 11px; font-weight: 500; opacity: 0.6; letter-spacing: 0.06em; min-width: 32px; } .section-title { font-size: 14px; font-weight: 600; letter-spacing: -0.2px; flex: 1; } .section-subtitle { font-size: 11px; font-weight: 400; opacity: 0.65; margin-top: 1px; } .section-tag { font-family: 'DM Mono', monospace; font-size: 10px; padding: 3px 8px; border-radius: 4px; letter-spacing: 0.06em; white-space: nowrap; } .chevron { font-size: 12px; opacity: 0.4; transition: transform 0.25s ease; margin-left: 8px; } .section-block.open .chevron { transform: rotate(180deg); } /* ── ROLES GRID ──────────────────────────────────────────────────── */ .roles-panel { display: none; padding: 0 20px 20px; gap: 8px; } .section-block.open .roles-panel { display: flex; flex-direction: column; } /* Sub-perspective header */ .perspective-label { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; padding: 10px 0 6px; border-bottom: 1px solid rgba(0,0,0,0.06); margin-bottom: 6px; opacity: 0.7; display: flex; align-items: center; gap: 8px; } .perspective-label::before { content: ''; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .roles-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; } .role-card { background: rgba(255,255,255,0.7); border-radius: 8px; padding: 10px 12px; border: 1px solid rgba(0,0,0,0.06); transition: all 0.2s ease; cursor: default; position: relative; } .role-card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.08); } .role-level { font-family: 'DM Mono', monospace; font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 5px; opacity: 0.5; } .role-title-pl { font-size: 12px; font-weight: 600; line-height: 1.3; margin-bottom: 3px; } .role-title-en { font-size: 10px; font-style: italic; opacity: 0.55; line-height: 1.3; margin-bottom: 6px; } .role-scope { font-size: 10px; line-height: 1.4; opacity: 0.6; } /* ── CORE STYLING ────────────────────────────────────────────────── */ .core-block { background: var(--core-light); border-color: rgba(45, 106, 79, 0.15); } .core-block .section-header { background: rgba(45,106,79,0.08); } .core-block .section-num { color: var(--core-mid); } .core-block .section-title { color: var(--core-dark); } .core-block .section-tag { background: var(--core-mid); color: white; } .core-block .role-card { border-color: rgba(45,106,79,0.1); } .core-block .role-level { color: var(--core-mid); } .core-block .role-title-pl { color: var(--core-dark); } /* ── NON-CORE STYLING ────────────────────────────────────────────── */ .nc-block { background: var(--nc-light); border-color: rgba(139, 69, 19, 0.15); } .nc-block .section-header { background: rgba(139,69,19,0.07); } .nc-block .section-num { color: var(--nc-mid); } .nc-block .section-title { color: var(--nc-dark); } .nc-block .section-tag { background: var(--nc-mid); color: white; } .nc-block .role-card { border-color: rgba(139,69,19,0.1); } .nc-block .role-level { color: var(--nc-mid); } .nc-block .role-title-pl { color: var(--nc-dark); } /* ── CLIENT / SUPPLIER SPLIT ─────────────────────────────────────── */ .split-panel { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 6px; } .split-side { border-radius: 8px; padding: 12px; } .client-side { background: rgba(107, 107, 212, 0.07); border: 1px solid rgba(107,107,212,0.15); } .supplier-side { background: rgba(58, 171, 171, 0.07); border: 1px solid rgba(58,171,171,0.15); } .client-side .perspective-label::before { background: var(--client-accent); } .supplier-side .perspective-label::before { background: var(--supplier-accent); } .client-side .perspective-label { color: var(--client-mid); } .supplier-side .perspective-label { color: var(--supplier-mid); } .client-side .role-level { color: var(--client-mid); } .supplier-side .role-level { color: var(--supplier-mid); } .client-side .role-card { border-color: rgba(107,107,212,0.12); } .supplier-side .role-card { border-color: rgba(58,171,171,0.12); } .split-roles-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; } /* ── DIVIDER BETWEEN CORE / NON-CORE ─────────────────────────────── */ .section-divider { display: flex; align-items: center; gap: 16px; padding: 8px 0; margin: 8px 0 4px; } .divider-label { font-family: 'DM Mono', monospace; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; white-space: nowrap; padding: 5px 14px; border-radius: 20px; font-weight: 500; } .divider-line { flex: 1; height: 1px; background: var(--line); } .core-divider .divider-label { background: var(--core-dark); color: white; } .nc-divider .divider-label { background: var(--nc-dark); color: white; } /* ── SENIORITY SCALE ─────────────────────────────────────────────── */ .seniority-bar { display: flex; gap: 6px; align-items: center; padding: 20px 60px 0; margin-bottom: -8px; } .seniority-bar-label { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-ghost); letter-spacing: 0.08em; margin-right: 8px; text-transform: uppercase; } .sen-pill { font-family: 'DM Mono', monospace; font-size: 10px; padding: 4px 12px; border-radius: 20px; background: var(--white); border: 1px solid var(--line); color: var(--ink-soft); letter-spacing: 0.06em; display: flex; align-items: center; gap: 6px; } .sen-pill::after { content: '→'; color: var(--ink-ghost); } .sen-pill:last-child::after { display: none; } /* ── EXPAND ALL BUTTON ───────────────────────────────────────────── */ .controls { padding: 0 60px; display: flex; gap: 12px; align-items: center; margin-bottom: 4px; } .btn { font-family: 'DM Mono', monospace; font-size: 11px; letter-spacing: 0.06em; padding: 7px 16px; border-radius: 6px; border: 1px solid var(--line); background: var(--white); color: var(--ink-soft); cursor: pointer; transition: all 0.15s ease; } .btn:hover { background: var(--ink); color: var(--white); border-color: var(--ink); } .stats-row { display: flex; gap: 24px; padding: 0 60px 12px; } .stat { display: flex; flex-direction: column; gap: 2px; } .stat-num { font-family: 'Fraunces', serif; font-size: 28px; font-weight: 600; color: var(--ink); line-height: 1; } .stat-label { font-size: 11px; color: var(--ink-ghost); letter-spacing: 0.04em; } .stat-divider { width: 1px; background: var(--line); align-self: stretch; margin: 4px 0; } /* ── TOOLTIP ─────────────────────────────────────────────────────── */ .tooltip { position: fixed; background: var(--ink); color: white; padding: 10px 14px; border-radius: 8px; font-size: 11px; line-height: 1.5; max-width: 260px; pointer-events: none; opacity: 0; transition: opacity 0.15s ease; z-index: 1000; box-shadow: 0 8px 24px rgba(0,0,0,0.2); } .tooltip.visible { opacity: 1; } /* ── FADE IN ─────────────────────────────────────────────────────── */ @keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .section-block { animation: fadeUp 0.4s ease forwards; opacity: 0; } .section-block:nth-child(1) { animation-delay: 0.05s; } .section-block:nth-child(2) { animation-delay: 0.10s; } .section-block:nth-child(3) { animation-delay: 0.15s; } .section-block:nth-child(4) { animation-delay: 0.20s; } .section-block:nth-child(5) { animation-delay: 0.25s; } .section-block:nth-child(6) { animation-delay: 0.30s; } .section-block:nth-child(7) { animation-delay: 0.35s; } .section-block:nth-child(8) { animation-delay: 0.40s; } .section-block:nth-child(9) { animation-delay: 0.45s; } .section-block:nth-child(10) { animation-delay: 0.50s; } .section-block:nth-child(11) { animation-delay: 0.55s; } .section-block:nth-child(12) { animation-delay: 0.60s; } .section-block:nth-child(13) { animation-delay: 0.65s; } .section-block:nth-child(14) { animation-delay: 0.70s; } /* ── LEVEL PANEL ─────────────────────────────────────────────────── */ .level-panel { background: var(--surface); border-bottom: 1px solid var(--line); padding: 24px 60px 20px; } .level-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; } .level-panel-title { font-size: 13px; font-weight: 600; color: var(--ink); display: flex; align-items: center; gap: 8px; letter-spacing: -0.1px; } .level-panel-icon { font-size: 16px; color: var(--ink-soft); } .level-panel-sub { font-size: 11px; font-weight: 400; color: var(--ink-ghost); font-family: 'DM Mono', monospace; letter-spacing: 0.02em; } .level-panel-toggle { font-family: 'DM Mono', monospace; font-size: 10px; padding: 5px 12px; border-radius: 6px; border: 1px solid var(--line); background: transparent; color: var(--ink-soft); cursor: pointer; letter-spacing: 0.06em; transition: all 0.15s; } .level-panel-toggle:hover { background: var(--ink); color: white; border-color: var(--ink); } /* Level cards */ .level-cards-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; } .level-card { border-radius: 10px; padding: 18px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s ease; position: relative; user-select: none; } .level-card:not(.active) { opacity: 0.35; filter: grayscale(0.4); } .level-card:hover { transform: translateY(-2px); } .level-card.strategic { background: linear-gradient(135deg, #EEF2FF 0%, #E8EEFF 100%); border-color: rgba(79,70,229,0.2); } .level-card.strategic.active { border-color: #4F46E5; box-shadow: 0 4px 20px rgba(79,70,229,0.15); } .level-card.tactical { background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%); border-color: rgba(217,119,6,0.2); } .level-card.tactical.active { border-color: #D97706; box-shadow: 0 4px 20px rgba(217,119,6,0.15); } .level-card.operational { background: linear-gradient(135deg, #F0FDF4 0%, #DCFCE7 100%); border-color: rgba(22,163,74,0.2); } .level-card.operational.active { border-color: #16A34A; box-shadow: 0 4px 20px rgba(22,163,74,0.15); } .level-card-badge { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-family: 'Fraunces', serif; font-size: 18px; font-weight: 600; margin-bottom: 10px; } .strategic .level-card-badge { background: #4F46E5; color: white; } .tactical .level-card-badge { background: #D97706; color: white; } .operational .level-card-badge { background: #16A34A; color: white; } .level-card-name { font-size: 13px; font-weight: 700; letter-spacing: 0.05em; margin-bottom: 2px; } .strategic .level-card-name { color: #3730A3; } .tactical .level-card-name { color: #92400E; } .operational .level-card-name { color: #14532D; } .level-card-en { font-family: 'DM Mono', monospace; font-size: 10px; opacity: 0.55; letter-spacing: 0.06em; margin-bottom: 4px; } .level-card-seniority { font-family: 'DM Mono', monospace; font-size: 10px; font-weight: 500; padding: 3px 8px; border-radius: 4px; display: inline-block; margin-bottom: 12px; } .strategic .level-card-seniority { background: rgba(79,70,229,0.1); color: #4F46E5; } .tactical .level-card-seniority { background: rgba(217,119,6,0.1); color: #D97706; } .operational .level-card-seniority { background: rgba(22,163,74,0.1); color: #16A34A; } .level-card-divider { height: 1px; background: rgba(0,0,0,0.07); margin-bottom: 12px; } .level-card-def { font-size: 12px; line-height: 1.55; color: var(--ink); margin-bottom: 12px; } .level-card-def strong { font-weight: 600; } .level-card-decisions-label { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.08em; opacity: 0.5; text-transform: uppercase; margin-bottom: 6px; } .level-card-decision-item { font-size: 11px; line-height: 1.5; color: var(--ink-soft); padding: 2px 0; } .level-card-examples { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; } .level-ex-pill { font-family: 'DM Mono', monospace; font-size: 9px; padding: 2px 7px; border-radius: 4px; letter-spacing: 0.04em; } .strategic .level-ex-pill { background: rgba(79,70,229,0.08); color: #4F46E5; border: 1px solid rgba(79,70,229,0.15); } .tactical .level-ex-pill { background: rgba(217,119,6,0.08); color: #D97706; border: 1px solid rgba(217,119,6,0.15); } .operational .level-ex-pill { background: rgba(22,163,74,0.08); color: #16A34A; border: 1px solid rgba(22,163,74,0.15); } /* Mapping strip */ .mapping-strip { background: var(--bg); border-radius: 8px; padding: 12px 16px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; } .mapping-strip-label { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-ghost); letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; } .mapping-items { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex: 1; } .mapping-item { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 6px; } .mapping-item.strat { background: rgba(79,70,229,0.06); } .mapping-item.tact { background: rgba(217,119,6,0.06); } .mapping-item.oper { background: rgba(22,163,74,0.06); } .mapping-badge { width: 22px; height: 22px; border-radius: 5px; display: flex; align-items: center; justify-content: center; font-family: 'Fraunces', serif; font-size: 13px; font-weight: 600; color: white; flex-shrink: 0; } .mapping-badge.S { background: #4F46E5; } .mapping-badge.T { background: #D97706; } .mapping-badge.O { background: #16A34A; } .mapping-title { font-size: 12px; font-weight: 600; } .mapping-roles { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-soft); } .mapping-arrow { color: var(--ink-ghost); font-size: 14px; } .mapping-note { font-size: 10px; color: var(--ink-ghost); font-style: italic; margin-left: 8px; flex: 1; min-width: 200px; } /* Filter indicator */ .filter-indicator { display: flex; align-items: center; gap: 12px; padding: 8px 14px; background: var(--ink); border-radius: 8px; color: white; font-size: 12px; font-family: 'DM Mono', monospace; letter-spacing: 0.04em; margin-top: 4px; } .filter-clear { background: rgba(255,255,255,0.15); border: none; color: white; padding: 3px 10px; border-radius: 5px; cursor: pointer; font-size: 11px; font-family: 'DM Mono', monospace; transition: background 0.15s; } .filter-clear:hover { background: rgba(255,255,255,0.3); } /* Role card level badge overlay */ .role-level-badge { position: absolute; top: 7px; right: 8px; width: 18px; height: 18px; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; font-family: 'Fraunces', serif; color: white; opacity: 0.85; } .role-level-badge.S { background: #4F46E5; } .role-level-badge.T { background: #D97706; } .role-level-badge.O { background: #16A34A; } /* Dimmed card when filtered */ .role-card.dimmed { opacity: 0.12; pointer-events: none; transform: none !important; } .level-card.wykonawczy { background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%); border-color: rgba(180,83,9,0.2); } .level-card.wykonawczy.active { border-color: #B45309; box-shadow: 0 4px 20px rgba(180,83,9,0.15); } .wykonawczy .level-card-badge { background: #B45309; color: white; } .wykonawczy .level-card-name { color: #7C2D12; } .wykonawczy .level-card-seniority { background: rgba(180,83,9,0.1); color: #B45309; } .wykonawczy .level-ex-pill { background: rgba(180,83,9,0.08); color: #B45309; border: 1px solid rgba(180,83,9,0.15); } .mapping-item.exec { background: rgba(180,83,9,0.06); } .mapping-badge.W { background: #B45309; } /* Wykonawczy role card style */ .role-card.wykonawczy-card { background: linear-gradient(135deg, #FFFBF5 0%, #FFF7ED 100%); border-color: rgba(180,83,9,0.15); border-left: 3px solid #B45309; } .role-card.wykonawczy-card .role-level { color: #B45309; } .role-card.wykonawczy-card .role-title-pl { color: #7C2D12; } .role-level-badge.W { background: #B45309; } /* Wykonawczy section within split panels */ .wykonawczy-section { margin-top: 10px; border-radius: 8px; padding: 12px; background: rgba(180,83,9,0.05); border: 1px solid rgba(180,83,9,0.15); } .wykonawczy-section .perspective-label { color: #B45309; } .wykonawczy-section .perspective-label::before { background: #B45309; } /* ── ENERGY SECTION ──────────────────────────────────────────────── */ .energy-block { background: #F0F4FF; border-color: rgba(60, 90, 180, 0.2); } .energy-block .section-header { background: rgba(60, 90, 180, 0.07); } .energy-block .section-num { color: #3C5AB4; } .energy-block .section-title { color: #1A2A5A; } .energy-block .section-tag { background: #3C5AB4; color: white; } .energy-block .role-card { border-color: rgba(60,90,180,0.12); } .energy-block .role-level { color: #3C5AB4; } .energy-block .role-title-pl { color: #1A2A5A; } .crosslinks-bar { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 0 14px; align-items: center; } .crosslinks-label { font-family: 'DM Mono', monospace; font-size: 10px; color: #888; letter-spacing: 0.08em; text-transform: uppercase; margin-right: 4px; } .crosslink-pill { font-family: 'DM Mono', monospace; font-size: 10px; padding: 3px 10px; border-radius: 20px; background: rgba(60,90,180,0.08); border: 1px solid rgba(60,90,180,0.2); color: #3C5AB4; letter-spacing: 0.04em; } .context-badge { display: inline-block; font-family: 'DM Mono', monospace; font-size: 9px; padding: 2px 6px; border-radius: 4px; background: rgba(60,90,180,0.1); color: #3C5AB4; margin-top: 5px; letter-spacing: 0.04em; } .energy-roles-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; } /* ── BUILDING TYPE FILTER ────────────────────────────────────────── */ .btype-panel { background: var(--surface); border-bottom: 2px solid var(--line); padding: 20px 60px 18px; } .btype-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; } .btype-title { font-size: 13px; font-weight: 600; color: var(--ink); display: flex; align-items: center; gap: 8px; } .btype-icon { font-size: 16px; } .btype-sub { font-size: 11px; font-weight: 400; color: var(--ink-ghost); font-family: 'DM Mono', monospace; letter-spacing: 0.02em; } .btype-cards-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; } .btype-card { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 10px 14px; border-radius: 10px; border: 2px solid var(--line); background: var(--white); cursor: pointer; transition: all 0.18s ease; min-width: 100px; flex: 1; max-width: 160px; } .btype-card:hover { border-color: #555; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); } .btype-card.active { border-color: var(--ink); background: var(--ink); } .btype-card.active .btype-name { color: white; } .btype-card.active .btype-en { color: rgba(255,255,255,0.6); } .btype-card.active .btype-emoji { filter: brightness(1.3); } .btype-emoji { font-size: 22px; line-height: 1; } .btype-name { font-size: 11px; font-weight: 600; color: var(--ink); text-align: center; line-height: 1.3; } .btype-en { font-family: 'DM Mono', monospace; font-size: 9px; color: var(--ink-ghost); text-align: center; letter-spacing: 0.04em; line-height: 1.3; } /* Unique roles panel */ .btype-unique-panel { background: var(--bg); border-radius: 10px; padding: 14px 16px; margin-bottom: 10px; border: 1px solid var(--line); } .btype-unique-header { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; } #btypeUniqueTitle { font-size: 13px; font-weight: 700; color: var(--ink); } .btype-unique-sub { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-ghost); letter-spacing: 0.04em; } .btype-unique-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 6px; } .btype-unique-card { background: white; border-radius: 8px; padding: 10px 12px; border: 1px solid var(--line); border-left: 3px solid #555; } .btype-unique-card .u-cat { font-family: 'DM Mono', monospace; font-size: 9px; color: var(--ink-ghost); letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 4px; } .btype-unique-card .u-pl { font-size: 12px; font-weight: 600; color: var(--ink); margin-bottom: 2px; } .btype-unique-card .u-en { font-size: 10px; font-style: italic; color: var(--ink-soft); margin-bottom: 4px; } .btype-unique-card .u-scope { font-size: 10px; color: var(--ink-soft); line-height: 1.4; } .btype-unique-card .u-level { font-family: 'DM Mono', monospace; font-size: 9px; padding: 2px 6px; border-radius: 3px; background: rgba(0,0,0,0.06); color: var(--ink-soft); display: inline-block; margin-top: 5px; } .btype-active-bar { display: flex; align-items: center; gap: 12px; padding: 7px 12px; background: var(--ink); border-radius: 7px; color: white; font-size: 11px; font-family: 'DM Mono', monospace; } /* role-card building type visibility */ .role-card.btype-hidden { display: none; } .section-block.btype-all-hidden { opacity: 0.2; pointer-events: none; } /* ── FINDER MODULE ───────────────────────────────────────────────── */ .finder-module { margin: 0 60px 40px; border-radius: 16px; border: 1px solid var(--line); background: var(--surface); overflow: hidden; box-shadow: 0 4px 32px rgba(0,0,0,0.05); } .finder-inner { padding: 36px 40px 32px; } .finder-header { margin-bottom: 24px; } .finder-eyebrow { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.14em; color: var(--ink-ghost); text-transform: uppercase; margin-bottom: 8px; } .finder-title { font-family: 'Fraunces', serif; font-size: 26px; font-weight: 300; letter-spacing: -0.3px; color: var(--ink); margin-bottom: 6px; line-height: 1.2; } .finder-subtitle { font-size: 13px; color: var(--ink-soft); line-height: 1.5; } /* Input */ .finder-form { margin-bottom: 0; } .finder-input-row { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 8px; } .finder-input-wrap { flex: 1; position: relative; } .finder-input { width: 100%; padding: 14px 18px; font-family: 'DM Sans', sans-serif; font-size: 15px; border: 2px solid var(--line); border-radius: 10px; background: white; color: var(--ink); outline: none; transition: border-color 0.15s; } .finder-input:focus { border-color: var(--ink); } .finder-hint { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-ghost); letter-spacing: 0.04em; } /* Autocomplete suggestions */ .finder-suggestions { position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: white; border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.1); z-index: 100; display: none; max-height: 220px; overflow-y: auto; } .finder-suggestions.open { display: block; } .finder-suggestion-item { padding: 9px 14px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--line); transition: background 0.1s; } .finder-suggestion-item:last-child { border-bottom: none; } .finder-suggestion-item:hover { background: var(--bg); } .suggestion-pl { font-weight: 500; color: var(--ink); flex: 1; } .suggestion-meta { font-family: 'DM Mono', monospace; font-size: 10px; color: var(--ink-ghost); white-space: nowrap; } .suggestion-highlight { background: #FEF08A; border-radius: 2px; } /* Button */ .finder-btn { padding: 14px 28px; border-radius: 10px; background: var(--ink); color: white; border: none; font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.15s; white-space: nowrap; min-width: 130px; } .finder-btn:hover { background: #333; transform: translateY(-1px); } .finder-btn:disabled { background: var(--ink-ghost); cursor: not-allowed; transform: none; } .finder-btn.loading { background: #555; } /* Spinner */ @keyframes spin { to { transform: rotate(360deg); } } .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 0.6s linear infinite; vertical-align: middle; margin-right: 6px; } /* Result panel */ .finder-result { margin-top: 20px; border-radius: 12px; overflow: hidden; animation: fadeUp 0.3s ease; } /* FOUND result */ .result-found { background: linear-gradient(135deg, #F0FDF4, #DCFCE7); border: 1px solid #86EFAC; padding: 20px 24px; } .result-found-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; } .result-found-icon { font-size: 22px; } .result-found-title { font-size: 15px; font-weight: 700; color: #14532D; } .result-found-sub { font-size: 12px; color: #166534; margin-top: 1px; } .result-location-cards { display: flex; flex-wrap: wrap; gap: 8px; } .result-location-card { background: white; border: 1px solid #BBF7D0; border-radius: 8px; padding: 10px 14px; cursor: pointer; transition: all 0.15s; flex: 1; min-width: 200px; max-width: 320px; } .result-location-card:hover { border-color: #16A34A; box-shadow: 0 4px 12px rgba(22,163,74,0.15); transform: translateY(-1px); } .rlc-category { font-family: 'DM Mono', monospace; font-size: 9px; color: #16A34A; letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 4px; } .rlc-name { font-size: 13px; font-weight: 600; color: var(--ink); margin-bottom: 2px; } .rlc-en { font-size: 11px; color: var(--ink-soft); font-style: italic; margin-bottom: 6px; } .rlc-badges { display: flex; gap: 4px; flex-wrap: wrap; } .rlc-badge { font-family: 'DM Mono', monospace; font-size: 9px; padding: 2px 6px; border-radius: 4px; border: 1px solid; } .rlc-badge.stl-S { background: rgba(79,70,229,0.08); color: #4F46E5; border-color: rgba(79,70,229,0.2); } .rlc-badge.stl-T { background: rgba(217,119,6,0.08); color: #D97706; border-color: rgba(217,119,6,0.2); } .rlc-badge.stl-O { background: rgba(22,163,74,0.08); color: #16A34A; border-color: rgba(22,163,74,0.2); } .rlc-badge.stl-W { background: rgba(180,83,9,0.08); color: #B45309; border-color: rgba(180,83,9,0.2); } .rlc-badge.level { background: var(--bg); color: var(--ink-soft); border-color: var(--line); } /* NOT FOUND result — add form */ .result-notfound { background: linear-gradient(135deg, #FFF7ED, #FFEDD5); border: 1px solid #FCD34D; padding: 20px 24px; } .result-nf-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } .result-nf-icon { font-size: 22px; } .result-nf-title { font-size: 15px; font-weight: 700; color: #92400E; } .result-nf-sub { font-size: 12px; color: #B45309; margin-bottom: 16px; } .result-nf-sub strong { color: #92400E; } .add-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; } .add-form-full { grid-column: 1 / -1; } .add-label { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.06em; color: var(--ink-soft); text-transform: uppercase; margin-bottom: 5px; } .add-input, .add-select, .add-textarea { width: 100%; padding: 9px 12px; font-family: 'DM Sans', sans-serif; font-size: 13px; border: 1px solid #FCD34D; border-radius: 8px; background: white; color: var(--ink); outline: none; transition: border-color 0.15s; } .add-input:focus, .add-select:focus, .add-textarea:focus { border-color: #D97706; } .add-textarea { resize: vertical; min-height: 60px; } .add-submit-row { display: flex; gap: 10px; align-items: center; } .add-submit-btn { padding: 10px 22px; border-radius: 8px; background: #D97706; color: white; border: none; font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s; } .add-submit-btn:hover { background: #B45309; } .add-skip-btn { font-size: 12px; color: var(--ink-ghost); background: none; border: none; cursor: pointer; text-decoration: underline; font-family: 'DM Sans', sans-serif; } /* ADDED SUCCESS */ .result-added { background: linear-gradient(135deg, #EFF6FF, #DBEAFE); border: 1px solid #93C5FD; padding: 20px 24px; } .result-added-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; } .result-added-icon { font-size: 22px; } .result-added-title { font-size: 15px; font-weight: 700; color: #1E3A8A; } .result-added-card { background: white; border: 1px solid #93C5FD; border-left: 4px solid #2563EB; border-radius: 8px; padding: 12px 16px; margin-top: 10px; } .added-in-schema { font-size: 12px; color: #1D4ED8; margin-top: 8px; font-style: italic; } /* Added roles list at bottom */ .finder-added-list { margin-top: 20px; padding: 14px 18px; background: var(--bg); border-radius: 10px; border: 1px solid var(--line); } .finder-added-title { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 10px; } .added-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: white; border: 1px solid var(--line); border-radius: 20px; font-size: 11px; color: var(--ink); margin: 3px; cursor: pointer; transition: all 0.15s; } .added-pill:hover { border-color: #2563EB; color: #2563EB; } .added-pill-cat { font-family: 'DM Mono', monospace; font-size: 9px; color: var(--ink-ghost); } /* AI Classification card */ .ai-classification-card { background: white; border: 1px solid #E0E7FF; border-radius: 10px; padding: 18px 20px; margin: 14px 0; border-left: 4px solid #4F46E5; } .ai-class-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin-bottom: 10px; } .ai-class-field { display: flex; flex-direction: column; gap: 4px; } .ai-class-label { font-family: 'DM Mono', monospace; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-ghost); } .ai-reasoning { display: flex; align-items: flex-start; gap: 8px; background: #F5F3FF; border: 1px solid #DDD6FE; border-radius: 7px; padding: 10px 12px; margin: 12px 0 10px; font-size: 12px; color: #4C1D95; line-height: 1.5; } .ai-reasoning-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; } .ai-summary-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .result-nf-analyzing { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-soft); margin-top: 6px; font-style: italic; } /* ── PRINT ───────────────────────────────────────────────────────── */ @media print { .controls { display: none; } .section-block.open .roles-panel { display: flex; } } </style> </head> <body> <!-- HEADER --> <div class="page-header"> <div> <h1>Real Estate Management<br><em>Mapa stanowisk i taksonomia zawodów</em></h1> </div> <div class="meta"> PODZIAŁ CORE / NON-CORE<br> ZAMAWIAJĄCY / DOSTAWCA<br> JUNIOR → C-LEVEL<br> 16 KATEGORII · 80+ STANOWISK </div> </div> <!-- LEGEND --> <div class="legend-bar"> <span class="legend-item"><span class="legend-dot" style="background:#2D6A4F"></span><span class="legend-label">CORE — funkcje kluczowe</span></span> <span class="legend-item"><span class="legend-dot" style="background:#8B4513"></span><span class="legend-label">NON-CORE — funkcje wspierające</span></span> <span class="legend-item"><span class="legend-dot" style="background:#6B6BD4"></span><span class="legend-label">Perspektywa Zamawiającego</span></span> <span class="legend-item"><span class="legend-dot" style="background:#3AABAB"></span><span class="legend-label">Perspektywa Dostawcy</span></span> <span class="legend-item"><span class="legend-dot" style="background:#3C5AB4"></span><span class="legend-label">⚡ Funkcja crossfunkcyjna (Energy)</span></span> <span class="legend-item" style="margin-left:auto"><span class="legend-label" style="font-size:10px;font-family:'DM Mono',monospace">↓ kliknij sekcję aby rozwinąć stanowiska</span></span> </div> <!-- STATS --> <div class="stats-row" style="padding-top:24px"> <div class="stat"><span class="stat-num">6</span><span class="stat-label">kategorii CORE</span></div> <div class="stat-divider"></div> <div class="stat"><span class="stat-num">10</span><span class="stat-label">kategorii NON-CORE</span></div> <div class="stat-divider"></div> <div class="stat"><span class="stat-num">80+</span><span class="stat-label">stanowisk</span></div> <div class="stat-divider"></div> <div class="stat"><span class="stat-num">5</span><span class="stat-label">poziomów seniority</span></div> <div class="stat-divider"></div> <div class="stat"><span class="stat-num">3</span><span class="stat-label">poziomy zarządzania</span></div> <div class="stat-divider"></div> <div class="stat"><span class="stat-num">2</span><span class="stat-label">perspektywy (FM)</span></div> </div> <!-- SENIORITY SCALE --> <div class="seniority-bar"> <span class="seniority-bar-label">Seniority:</span> <span class="sen-pill">Junior</span> <span class="sen-pill">Mid</span> <span class="sen-pill">Senior</span> <span class="sen-pill">Director</span> <span class="sen-pill">C-level</span> <span style="font-family:'DM Mono',monospace;font-size:11px;color:#888;margin:0 4px">+</span> <span class="sen-pill" style="border-color:#B45309;color:#B45309;background:#FFFBEB">Wykonawczy ★</span> </div> <!-- STRATEGIC LEVEL FILTER PANEL --> <div class="level-panel" id="levelPanel"> <div class="level-panel-header"> <div class="level-panel-title"> <span class="level-panel-icon">◈</span> Poziomy zarządzania w Real Estate <span class="level-panel-sub">— kliknij poziom aby przefiltrować stanowiska na schemacie</span> </div> <button class="level-panel-toggle" onclick="toggleLevelPanel()">Zwiń ▲</button> </div> <div class="level-cards-row" id="levelCards"> <div class="level-card strategic active" data-level="S" onclick="filterLevel('S')"> <div class="level-card-badge">S</div> <div class="level-card-name">STRATEGICZNY</div> <div class="level-card-en">Strategic Level</div> <div class="level-card-seniority">C-level · Director</div> <div class="level-card-divider"></div> <div class="level-card-def"> Odpowiada na pytanie <strong>„Dlaczego?"</strong> i <strong>„Co?"</strong>. Ustala kierunek, alokuje kapitał, decyduje o portfelu i modelu biznesowym. Horyzont: 3–10 lat. </div> <div class="level-card-decisions"> <div class="level-card-decisions-label">Typowe decyzje w RE:</div> <div class="level-card-decision-item">→ Kupić czy wynająć nieruchomość?</div> <div class="level-card-decision-item">→ Outsourcować FM czy budować in-house?</div> <div class="level-card-decision-item">→ Jaką strategię ESG przyjąć dla portfela?</div> <div class="level-card-decision-item">→ W jakich rynkach geograficznych być?</div> <div class="level-card-decision-item">→ Jaki model workplace dla organizacji?</div> </div> <div class="level-card-examples"> <span class="level-ex-pill">CIO / CREO</span> <span class="level-ex-pill">Head of FM</span> <span class="level-ex-pill">Portfolio Director</span> <span class="level-ex-pill">MD Agency</span> </div> </div> <div class="level-card tactical active" data-level="T" onclick="filterLevel('T')"> <div class="level-card-badge">T</div> <div class="level-card-name">TAKTYCZNY</div> <div class="level-card-en">Tactical Level</div> <div class="level-card-seniority">Senior · Mid (wyższy)</div> <div class="level-card-divider"></div> <div class="level-card-def"> Odpowiada na pytanie <strong>„Jak?"</strong> i <strong>„Kiedy?"</strong>. Tłumaczy strategię na plany, projekty i procesy. Zarządza zasobami i dostawcami. Horyzont: 6 mies. – 3 lata. </div> <div class="level-card-decisions"> <div class="level-card-decisions-label">Typowe decyzje w RE:</div> <div class="level-card-decision-item">→ Jak skonstruować przetarg na IFM?</div> <div class="level-card-decision-item">→ Jak rozłożyć budżet FM na obiekty?</div> <div class="level-card-decision-item">→ Jak wdrożyć hot-desking w biurze?</div> <div class="level-card-decision-item">→ Jak renegocjować umowę najmu?</div> <div class="level-card-decision-item">→ Jak osiągnąć certyfikat BREEAM Excellent?</div> </div> <div class="level-card-examples"> <span class="level-ex-pill">Asset Manager</span> <span class="level-ex-pill">Contract Manager</span> <span class="level-ex-pill">Senior PM</span> <span class="level-ex-pill">ESG Manager</span> </div> </div> <div class="level-card operational active" data-level="O" onclick="filterLevel('O')"> <div class="level-card-badge">O</div> <div class="level-card-name">OPERACYJNY</div> <div class="level-card-en">Operational Level</div> <div class="level-card-seniority">Mid · Junior · Technik</div> <div class="level-card-divider"></div> <div class="level-card-def"> Odpowiada na pytanie <strong>„Co dokładnie?"</strong> i <strong>„Teraz?"</strong>. Realizuje zadania, obsługuje obiekty, przetwarza dane. Horyzont: dzienny – miesięczny. </div> <div class="level-card-decisions"> <div class="level-card-decisions-label">Typowe decyzje w RE:</div> <div class="level-card-decision-item">→ Kiedy zlecić przegląd instalacji HVAC?</div> <div class="level-card-decision-item">→ Jak obsłużyć zgłoszenie awarii od najemcy?</div> <div class="level-card-decision-item">→ Jak wypełnić raport occupancy za tydzień?</div> <div class="level-card-decision-item">→ Kiedy wygasa opcja przedłużenia najmu?</div> <div class="level-card-decision-item">→ Jak zebrać dane do raportu mediów?</div> </div> <div class="level-card-examples"> <span class="level-ex-pill">FM Coordinator</span> <span class="level-ex-pill">Technik utrzymania</span> <span class="level-ex-pill">Lease Admin</span> <span class="level-ex-pill">Space Planner</span> </div> </div> <div class="level-card wykonawczy active" data-level="W" onclick="filterLevel('W')"> <div class="level-card-badge">W</div> <div class="level-card-name">WYKONAWCZY</div> <div class="level-card-en">Field / Frontline Level</div> <div class="level-card-seniority">Pracownik · Brygadzista · Technik</div> <div class="level-card-divider"></div> <div class="level-card-def"> Odpowiada na pytanie <strong>„Robię to teraz."</strong> Bezpośrednia realizacja usługi na obiekcie — fizyczna obecność, praca zmianowa, kontakt z przestrzenią i użytkownikami. Horyzont: zmiana – dzień. </div> <div class="level-card-decisions"> <div class="level-card-decisions-label">Typowe działania w RE:</div> <div class="level-card-decision-item">→ Wykonać przegląd instalacji elektrycznej na piętrze</div> <div class="level-card-decision-item">→ Odpowiedzieć na zgłoszenie awarii w ciągu 15 min</div> <div class="level-card-decision-item">→ Przeprowadzić obchód ochroniarski o godz. 22:00</div> <div class="level-card-decision-item">→ Uprzątnąć powierzchnię biurową według harmonogramu</div> <div class="level-card-decision-item">→ Zgłosić brygadziście usterkę wymagającą eskalacji</div> </div> <div class="level-card-examples"> <span class="level-ex-pill">Technik HVAC</span> <span class="level-ex-pill">Sprzątaczka</span> <span class="level-ex-pill">Ochroniarz</span> <span class="level-ex-pill">Brygadzista</span> </div> </div> </div> <!-- MAPPING STRIP --> <div class="mapping-strip"> <div class="mapping-strip-label">Mapowanie poziomów ↔ seniority w RE:</div> <div class="mapping-items"> <div class="mapping-item strat"> <div class="mapping-badge S">S</div> <div class="mapping-text"> <div class="mapping-title">Strategiczny</div> <div class="mapping-roles">C-level · Director</div> </div> </div> <div class="mapping-arrow">→</div> <div class="mapping-item tact"> <div class="mapping-badge T">T</div> <div class="mapping-text"> <div class="mapping-title">Taktyczny</div> <div class="mapping-roles">Senior · Mid+</div> </div> </div> <div class="mapping-arrow">→</div> <div class="mapping-item oper"> <div class="mapping-badge O">O</div> <div class="mapping-text"> <div class="mapping-title">Operacyjny</div> <div class="mapping-roles">Mid · Junior</div> </div> </div> <div class="mapping-arrow">→</div> <div class="mapping-item exec"> <div class="mapping-badge W">W</div> <div class="mapping-text"> <div class="mapping-title">Wykonawczy</div> <div class="mapping-roles">Pracownik · Brygadzista</div> </div> </div> <div class="mapping-note"> ⚠ Poziom Wykonawczy (W) dotyczy wyłącznie ścieżki niebiurowej — dostawcy usług FM, soft services i technicznych. Brygadzista = najwyższy szczebel tej ścieżki. </div> </div> </div> <!-- ACTIVE FILTER INDICATOR --> <div class="filter-indicator" id="filterIndicator" style="display:none"> <span id="filterText"></span> <button class="filter-clear" onclick="clearFilter()">✕ Pokaż wszystkie</button> </div> </div> <!-- CONTROLS --> <div class="controls" style="padding-top:16px"> <button class="btn" onclick="expandAll()">Rozwiń wszystkie</button> <button class="btn" onclick="collapseAll()">Zwiń wszystkie</button> </div> <!-- BUILDING TYPE FILTER PANEL --> <div class="btype-panel" id="btypePanel"> <div class="btype-header"> <div class="btype-title"> <span class="btype-icon">🏢</span> Filtr według typu obiektu <span class="btype-sub">— wybierz typ aby zobaczyć tylko relevantne stanowiska i role unikalne dla tego obiektu</span> </div> <button class="level-panel-toggle" onclick="toggleBtypePanel()">Zwiń ▲</button> </div> <div class="btype-cards-row" id="btypeCards"> <div class="btype-card active" data-btype="ALL" onclick="filterBuilding('ALL')"> <div class="btype-emoji">◎</div> <div class="btype-name">Wszystkie typy</div> <div class="btype-en">All Building Types</div> </div> <div class="btype-card" data-btype="OFF" onclick="filterBuilding('OFF')"> <div class="btype-emoji">🏢</div> <div class="btype-name">Biurowy</div> <div class="btype-en">Office</div> </div> <div class="btype-card" data-btype="RET" onclick="filterBuilding('RET')"> <div class="btype-emoji">🛍️</div> <div class="btype-name">Handlowy</div> <div class="btype-en">Retail / Shopping Centre</div> </div> <div class="btype-card" data-btype="LOG" onclick="filterBuilding('LOG')"> <div class="btype-emoji">📦</div> <div class="btype-name">Magazynowy / Logistyczny</div> <div class="btype-en">Warehouse / Logistics</div> </div> <div class="btype-card" data-btype="IND" onclick="filterBuilding('IND')"> <div class="btype-emoji">🏭</div> <div class="btype-name">Przemysłowy / Fabryka</div> <div class="btype-en">Industrial / Manufacturing</div> </div> <div class="btype-card" data-btype="HOT" onclick="filterBuilding('HOT')"> <div class="btype-emoji">🏨</div> <div class="btype-name">Hotelowy / Hospitality</div> <div class="btype-en">Hotel / Hospitality</div> </div> <div class="btype-card" data-btype="MIX" onclick="filterBuilding('MIX')"> <div class="btype-emoji">🏙️</div> <div class="btype-name">Mieszany</div> <div class="btype-en">Mixed-use</div> </div> <div class="btype-card" data-btype="PUB" onclick="filterBuilding('PUB')"> <div class="btype-emoji">🎭</div> <div class="btype-name">Użyteczności Publicznej</div> <div class="btype-en">Public / Cultural / Transport</div> </div> </div> <!-- Unique roles panel — shown when a building type is selected --> <div class="btype-unique-panel" id="btypeUniquePanel" style="display:none"> <div class="btype-unique-header"> <span id="btypeUniqueTitle"></span> <span class="btype-unique-sub">Stanowiska unikalne lub charakterystyczne dla tego typu obiektu</span> </div> <div class="btype-unique-grid" id="btypeUniqueGrid"></div> </div> <div class="btype-active-bar" id="btypeActiveBar" style="display:none"> <span id="btypeActiveText"></span> <button class="filter-clear" onclick="filterBuilding('ALL')">✕ Pokaż wszystkie typy</button> </div> </div> <!-- MAIN CANVAS --> <div class="canvas" id="canvas"></div> <!-- FIND YOUR POSITION MODULE --> <div class="finder-module" id="finderModule"> <div class="finder-inner"> <div class="finder-header"> <div class="finder-title-block"> <div class="finder-eyebrow">MODUŁ INTERAKTYWNY</div> <h2 class="finder-title">Czy Twoje stanowisko jest na tej mapie?</h2> <p class="finder-subtitle">Wpisz swoją nazwę stanowiska — sprawdzimy gdzie się znajduje lub pomożemy je umieścić.</p> </div> </div> <!-- SEARCH FORM --> <div class="finder-form" id="finderForm"> <div class="finder-input-row"> <div class="finder-input-wrap"> <input type="text" id="finderInput" class="finder-input" placeholder="np. Facility Manager, Asset Manager, Brygadzista ochrony..." autocomplete="off" onkeydown="if(event.key==='Enter') runFinder()" /> <div class="finder-suggestions" id="finderSuggestions"></div> </div> <button class="finder-btn" onclick="runFinder()" id="finderBtn"> <span id="finderBtnText">Sprawdź →</span> </button> </div> <div class="finder-hint">Możesz wpisać nazwę po polsku lub angielsku</div> </div> <!-- RESULT PANEL --> <div class="finder-result" id="finderResult" style="display:none"></div> <!-- ADDED ROLES LIST --> <div class="finder-added-list" id="finderAddedList" style="display:none"> <div class="finder-added-title">📌 Stanowiska dodane przez użytkowników tej sesji:</div> <div id="finderAddedItems"></div> </div> </div> </div> <!-- TOOLTIP --> <div class="tooltip" id="tooltip"></div> <script> const DATA = { core: [ { id: 'C1', label: 'CORE 1', title: 'Asset & Portfolio Management', subtitle: 'Zarządzanie Aktywem i Portfelem Inwestycyjnym', desc: 'Nieruchomość jako produkt finansowy. Optymalizacja wartości, NOI, decyzje hold/sell, relacje z inwestorami.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor ds. Inwestycji RE', en:'Chief Investment Officer (RE)', scope:'Strategia inwestycyjna portfela, alokacja kapitału, raport do zarządu' }, { level:'Director', stl:'S', pl:'Dyrektor Portfela', en:'Portfolio Director', scope:'Nadzór nad Asset Managerami, relacje z inwestorami, hold/sell decisions' }, { level:'Senior', stl:'T', pl:'Starszy Zarządca Aktywów', en:'Senior Asset Manager', scope:'Samodzielne zarządzanie portfelem klasy A/B, capex, NOI' }, { level:'Mid', stl:'T', pl:'Zarządca Aktywów', en:'Asset Manager', scope:'Bieżące zarządzanie 2–5 nieruchomościami, budżety, najemcy' }, { level:'Junior', stl:'O', pl:'Analityk Inwestycyjny RE', en:'Real Estate Investment Analyst', scope:'Modele DCF, due diligence, raporty rynkowe' }, ] }, { id: 'C2', label: 'CORE 2', title: 'Property & Lease Management', subtitle: 'Zarządzanie Nieruchomością i Administracja Najmu', desc: 'Operacyjne zarządzanie budynkiem. Relacje właściciel–najemca, service charge, compliance z umowami.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor Zarządzania Nieruchomościami', en:'Head of Property Management', scope:'Strategia zarządzania całym portfelem, polityki i standardy' }, { level:'Director', stl:'S', pl:'Regionalny Dyrektor Zarządzania', en:'Regional Property Director', scope:'Nadzór nad PM w regionie, relacje z kluczowymi najemcami' }, { level:'Senior', stl:'T', pl:'Starszy Zarządca Nieruchomości', en:'Senior Property Manager', scope:'Budynek klasy A, serwis opłat, negocjacje, compliance' }, { level:'Mid', stl:'O', pl:'Zarządca Nieruchomości', en:'Property Manager', scope:'Codzienna operacja budynku, relacje z najemcami, budżet serwisowy' }, { level:'Junior', stl:'O', pl:'Specjalista ds. Administracji Najmu', en:'Lease Administrator', scope:'Śledzenie terminów, indeksacje, opcje, bazy danych najmu' }, ] }, { id: 'C3', label: 'CORE 3', title: 'Corporate Real Estate (CRE) Strategy', subtitle: 'Strategia Nieruchomości Korporacyjnych', desc: 'Wewnętrzna funkcja korporacyjna. Portfel jako zasób strategiczny — decyzje lokalizacyjne, optymalizacja kosztów.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor ds. Nieruchomości Korporacyjnych', en:'Head of CRE / CREO', scope:'Strategia portfela korporacji, alignment z CFO/COO' }, { level:'Director', stl:'S', pl:'Dyrektor CRE', en:'CRE Director / VP Real Estate', scope:'Globalny portfel, occupancy, workplace strategy' }, { level:'Senior', stl:'T', pl:'Starszy Menedżer CRE', en:'Senior CRE Manager', scope:'Transakcje najmu, negocjacje z landlordami, due diligence' }, { level:'Mid', stl:'T', pl:'Menedżer ds. Nieruchomości', en:'Real Estate / Transaction Manager', scope:'Umowy najmu, relacje z właścicielami, raporty kosztowe' }, { level:'Junior', stl:'O', pl:'Analityk Portfela RE', en:'Real Estate Portfolio Analyst', scope:'Analizy kosztów, benchmarking, wsparcie negocjacji' }, ] }, { id: 'C4', label: 'CORE 4', title: 'Facility & Technical Operations Management', subtitle: 'Zarządzanie Operacyjno-Techniczne FM', desc: 'Utrzymanie nieruchomości w sprawności. Podzielone na perspektywę Zamawiającego (Client) i Dostawcy (Supplier).', split: true, client: { label: 'ZAMAWIAJĄCY — Client Side / In-house FM', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor FM (Zamawiający)', en:'Head of FM (Client)', scope:'Strategia FM, budżet, governance kontraktów IFM' }, { level:'Director', stl:'S', pl:'Regionalny Dyrektor FM', en:'Regional FM Director (Client)', scope:'Nadzór nad kontraktami FM w regionie, KPI dostawcy' }, { level:'Senior', stl:'T', pl:'Starszy Menedżer Kontraktu FM', en:'Senior FM Contract Manager', scope:'Rozliczanie SLA/KPI, audyty dostawcy' }, { level:'Mid', stl:'T', pl:'Menedżer FM (Zamawiający)', en:'FM Manager (Client Side)', scope:'Współpraca z dostawcą, budżet FM, helpdesk' }, { level:'Junior', stl:'O', pl:'Koordynator FM', en:'FM Coordinator (Client)', scope:'Obsługa zgłoszeń, weryfikacja faktur, CAFM' }, ] }, supplier: { label: 'DOSTAWCA — Supplier Side / IFM Operator', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor Operacyjny (Dostawca FM)', en:'COO / FM Division Director', scope:'P&L działu FM, strategia kontraktów, oferta usługowa' }, { level:'Director', stl:'S', pl:'Dyrektor Klienta Kluczowego', en:'Account Director / Key Account Director', scope:'Relacja strategiczna z klientem, odnowienia kontraktów' }, { level:'Senior', stl:'T', pl:'Menedżer Kontraktu (Dostawca)', en:'Contract Manager / Senior Ops Manager', scope:'Zarządzanie kontraktem IFM, SLA, nadzór nad zespołem' }, { level:'Mid', stl:'O', pl:'Menedżer Obiektu', en:'Site Manager / Building Manager', scope:'Operacja obiektów klienta, hard/soft services' }, { level:'Junior', stl:'O', pl:'Koordynator / Technik FM', en:'FM Coordinator / Maintenance Technician', scope:'Realizacja zleceń, helpdesk, przeglądy techniczne' }, ], frontline: [ { level:'Brygadzista', stl:'W', pl:'Brygadzista Techniczny', en:'Technical Team Leader / Shift Supervisor', scope:'Nadzór nad technikami na zmianie, przydział zadań, pierwsza eskalacja awarii, odbiór prac' }, { level:'Pracownik', stl:'W', pl:'Technik Utrzymania Ruchu (wielobranżowy)', en:'Multi-skilled Maintenance Technician', scope:'Przeglądy prewencyjne, naprawy bieżące: elektryka, hydraulika, HVAC, drobne prace ogólnobudowlane' }, { level:'Pracownik', stl:'W', pl:'Technik HVAC / Chłodnictwa', en:'HVAC / Refrigeration Technician', scope:'Serwis central wentylacyjnych, klimatyzatorów, chillerów, urządzeń chłodniczych' }, { level:'Pracownik', stl:'W', pl:'Elektryk Utrzymania', en:'Electrical Maintenance Technician', scope:'Obsługa rozdzielni, instalacji elektrycznych, UPS, oświetlenia awaryjnego, BMS' }, { level:'Pracownik', stl:'W', pl:'Technik BMS / Automatyki', en:'BMS / Building Automation Technician', scope:'Monitoring i programowanie systemu zarządzania budynkiem, reakcja na alarmy systemowe' }, ] } }, { id: 'C5', label: 'CORE 5', title: 'Development & Project Management', subtitle: 'Deweloperstwo i Zarządzanie Projektami RE', desc: 'Tworzenie i adaptacja przestrzeni. Fit-out, relokacje, przebudowy, projekty deweloperskie.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor Rozwoju / Techniczny', en:'Development Director / Technical Director', scope:'Pipeline projektów deweloperskich, capex, nabycie gruntów' }, { level:'Director', stl:'S', pl:'Dyrektor Projektów RE', en:'Head of Project Management (RE)', scope:'Portfel projektów fit-out i budowlanych, standardy, budżety' }, { level:'Senior', stl:'T', pl:'Starszy Kierownik Projektu RE', en:'Senior Project Manager (RE/Fit-out)', scope:'Kompleksowe zarządzanie projektem aranżacji od A do Z' }, { level:'Mid', stl:'T', pl:'Kierownik Projektu Budowlanego', en:'Project Manager (Construction/Fit-out)', scope:'Koordynacja wykonawców, harmonogram, budżet, odbiory' }, { level:'Junior', stl:'O', pl:'Asystent Projektu / Kosztorysant', en:'Project Coordinator / Junior QS', scope:'Wsparcie PM, kosztorysy, zamówienia, dokumentacja' }, ] }, { id: 'C6', label: 'CORE 6', title: 'Transaction & Agency Services', subtitle: 'Usługi Transakcyjne i Agencyjne', desc: 'Rynek zewnętrzny — kupno, sprzedaż, najem, reprezentacja stron. Agencje: JLL, CBRE, Savills, Cushman, Knight Frank.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor ds. Rynków Kapitałowych', en:'Head of Capital Markets / MD', scope:'Strategia transakcyjna, relacje z inwestorami instytucjonalnymi' }, { level:'Director', stl:'S', pl:'Dyrektor Działu Transakcyjnego', en:'Director – Investment / Leasing', scope:'Zarządzanie zespołem brokerskim, kluczowe transakcje' }, { level:'Senior', stl:'T', pl:'Starszy Doradca Transakcyjny', en:'Senior Investment / Leasing Advisor', scope:'Samodzielne transakcje klasy A, reprezentacja klienta' }, { level:'Mid', stl:'T', pl:'Doradca ds. Wynajmu / Inwestycji', en:'Leasing / Investment Consultant', scope:'Komercjalizacja powierzchni, reprezentacja najemców' }, { level:'Junior', stl:'O', pl:'Analityk Rynkowy / Asystent', en:'Research Analyst / Junior Advisor', scope:'Raporty rynkowe, bazy transakcji, wsparcie due diligence' }, ] }, ], noncore: [ { id: 'N1', label: 'NON-CORE 1', title: 'Workplace Experience & Environment', subtitle: 'Doświadczenie i Środowisko Pracy', desc: 'Miękka warstwa FM — hospitality w biurze, community management, kultura miejsca pracy.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor ds. Środowiska Pracy', en:'Head of Workplace Experience', scope:'Strategia employee experience, hospitality, kultura miejsca pracy' }, { level:'Senior', stl:'T', pl:'Menedżer Doświadczeń Workplace', en:'Workplace Experience Manager', scope:'Programy amenity, onboarding do przestrzeni, events biurowe' }, { level:'Mid', stl:'O', pl:'Menedżer Front of House', en:'Front of House / Office Hospitality Mgr', scope:'Recepcja, concierge, standardy obsługi, dostawcy hospitality' }, { level:'Junior', stl:'O', pl:'Host / Koordynator Biura', en:'Workplace Host / Community Coordinator', scope:'Codzienna obsługa użytkowników, feedback, wsparcie eventów' }, ] }, { id: 'N2', label: 'NON-CORE 2', title: 'Workplace Strategy & Space Planning', subtitle: 'Strategia Miejsca Pracy i Planowanie Przestrzeni', desc: 'Projektowanie modelu użytkowania — hybrydowość, ABW, desk ratio, occupancy analytics.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor ds. Strategii Workplace', en:'Head of Workplace Strategy', scope:'Globalna strategia: hybrydowość, desk ratio, ABW' }, { level:'Senior', stl:'T', pl:'Starszy Strateg Workplace', en:'Senior Workplace Strategist', scope:'Projektowanie modeli pracy, occupancy badania, wdrożenie ABW' }, { level:'Mid', stl:'T', pl:'Planista Przestrzeni', en:'Space Planner / Space Manager', scope:'Plany CAD/CAFM, alokacje działów, raporty space utilization' }, { level:'Junior', stl:'O', pl:'Analityk Occupancy', en:'Occupancy / Workplace Data Analyst', scope:'Dane z sensorów IoT, dashboardy, benchmarking zajętości' }, ] }, { id: 'N3', label: 'NON-CORE 3 ★', title: 'Sustainability, ESG & Energy Management', subtitle: 'Zrównoważony Rozwój i Zarządzanie Energią', desc: 'Certyfikacje BREEAM/LEED/WELL, CSRD, EU Taxonomy, dekarbonizacja aktywów. Migruje do CORE.', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor ds. Zrównoważonego Rozwoju RE', en:'Head of ESG / Sustainability Director', scope:'Strategia ESG portfela, CSRD, EU Taxonomy, net zero roadmap' }, { level:'Senior', stl:'T', pl:'Menedżer ESG (RE)', en:'ESG Manager (Real Estate)', scope:'BREEAM/LEED/WELL, audyty CO₂, green lease, GHG reporting' }, { level:'Mid', stl:'T', pl:'Menedżer Energii', en:'Energy Manager', scope:'Zarządzanie zużyciem energii, umowy z dostawcami, M&V' }, { level:'Junior', stl:'O', pl:'Specjalista ds. Zrównoważonego Budownictwa', en:'Sustainability Analyst / Green Building Spec.', scope:'Dane ESG, wsparcie certyfikacji, raporty środowiskowe' }, ] }, { id: 'N4', label: 'NON-CORE 4', title: 'Soft Services Management', subtitle: 'Zarządzanie Usługami Miękkimi', desc: 'Ochrona, czystość, catering, recepcja, parking, waste. Typowo outsourcowane. Podział na Client/Supplier.', split: true, client: { label: 'ZAMAWIAJĄCY — Client Side', roles: [ { level:'Senior', stl:'T', pl:'Menedżer Usług Miękkich (Zamawiający)', en:'Soft Services Manager (Client)', scope:'Nadzór nad dostawcami ochrony, czystości, cateringu, SLA' }, { level:'Mid', stl:'O', pl:'Koordynator Usług Dodatkowych', en:'Soft Services Coordinator (Client)', scope:'Rozliczanie dostawców, inspekcje jakości, budżet' }, ] }, supplier: { label: 'DOSTAWCA — Supplier Side', roles: [ { level:'Director', stl:'S', pl:'Dyrektor Operacyjny – Usługi Miękkie', en:'Operations Director – Soft Services', scope:'Dywizja soft services (cleaning, security, catering) – P&L' }, { level:'Senior', stl:'T', pl:'Menedżer Kontraktu – Usługi Miękkie', en:'Contract Manager – Soft Services', scope:'Realizacja usług, SLA, nadzór nad zespołami' }, { level:'Mid', stl:'O', pl:'Kierownik Obiektu – Usługi Miękkie', en:'Site Supervisor – Cleaning / Security', scope:'Nadzór operacyjny, grafiki, kontrola jakości' }, { level:'Junior', stl:'O', pl:'Pracownik Serwisu', en:'Service Operative', scope:'Realizacja usługi na obiekcie wg harmonogramu' }, ], frontline: [ { level:'Brygadzista', stl:'W', pl:'Brygadzista Czystości / Shift Leader', en:'Cleaning Team Leader / Shift Leader', scope:'Nadzór nad pracownikami sprzątającymi na zmianie, kontrola jakości, przydział stref, raport do supervisora' }, { level:'Brygadzista', stl:'W', pl:'Dowódca Zmiany Ochrony', en:'Security Shift Commander / Head Guard', scope:'Zarządzanie posterunkami i patrolami podczas zmiany, eskalacja incydentów, raport pozmianowy' }, { level:'Pracownik', stl:'W', pl:'Pracownik Sprzątający / Serwis Czystości', en:'Cleaning Operative / Housekeeper', scope:'Sprzątanie stref biurowych, sanitariatów, przestrzeni wspólnych wg harmonogramu i standardu' }, { level:'Pracownik', stl:'W', pl:'Pracownik Ochrony Fizycznej', en:'Security Guard / Officer', scope:'Ochrona obiektu, kontrola dostępu, monitoring CCTV, obchody, reakcja na incydenty' }, { level:'Pracownik', stl:'W', pl:'Recepcjonista / Host Recepcji', en:'Receptionist / Front Desk Officer', scope:'Obsługa wejścia głównego, rejestracja gości, obsługa centrali, koordynacja kurierów' }, { level:'Pracownik', stl:'W', pl:'Pracownik Cateringu / Kelner Biurowy', en:'Catering Assistant / Corporate Hospitality Staff', scope:'Obsługa kuchni pracowniczej, serwis kawowy, cateringi na spotkania, obsługa sali jadalnej' }, ] } }, { id: 'N5', label: 'NON-CORE 5', title: 'Health, Safety & Compliance (HSE)', subtitle: 'BHP i Zgodność Regulacyjna', desc: 'Fire safety, Legionella, CDM, statutory inspections. Krytyczna prawnie, wspierająca operacyjnie.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor ds. BHP i Compliance RE', en:'Head of HSE & Compliance (RE)', scope:'Polityka BHP portfela, zarządzanie ryzykiem, regulatory compliance' }, { level:'Senior', stl:'T', pl:'Starszy Specjalista BHP', en:'Senior HSE Manager / Fire Safety Manager', scope:'Audyty bezpieczeństwa, ryzyko pożarowe, Legionella, CDM' }, { level:'Mid', stl:'O', pl:'Inspektor BHP / Compliance Manager', en:'HSE Officer / Statutory Compliance Mgr', scope:'Przeglądy, dokumentacja BHP, szkolenia, wymogi prawne' }, { level:'Junior', stl:'O', pl:'Asystent ds. Bezpieczeństwa', en:'HSE Coordinator / Safety Administrator', scope:'Rejestracja zdarzeń, wsparcie audytów, baza dokumentacji' }, ] }, { id: 'N6', label: 'NON-CORE 6 ★', title: 'Technology, Data & PropTech', subtitle: 'Technologie, Dane i PropTech', desc: 'CAFM/IWMS, IoT, smart building, digital twin, AI w FM. Rosnące znaczenie strategiczne — migruje do CORE.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor ds. Technologii RE / PropTech', en:'Head of PropTech / Digital RE Director', scope:'Transformacja cyfrowa RE/FM, strategia IWMS, IoT, AI' }, { level:'Senior', stl:'T', pl:'Menedżer Systemów CAFM/IWMS', en:'CAFM / IWMS System Manager', scope:'Wdrożenie Planon, Archibus, Tririga, ServiceNow – integracje' }, { level:'Mid', stl:'O', pl:'Specjalista Smart Building / IoT', en:'Smart Building Specialist / IoT Engineer', scope:'BMS, sensory, dashboardy danych budynkowych' }, { level:'Junior', stl:'O', pl:'Analityk Danych RE', en:'RE Data Analyst / PropTech Analyst', scope:'Analizy CAFM, occupancy, media; BI, raporty' }, ] }, { id: 'N7', label: 'NON-CORE 7', title: 'Finance, Reporting & Administration (RE)', subtitle: 'Finanse i Administracja Back-office RE', desc: 'Rachunkowość portfela, service charge, procurement FM/RE, administracja umów.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor Finansowy RE', en:'RE Finance Director / Controller', scope:'Raportowanie finansowe portfela, budżetowanie capex/opex' }, { level:'Senior', stl:'T', pl:'Starszy Księgowy / Kontroler RE', en:'Senior RE Accountant / Budget Controller', scope:'Service charge accounting, rozliczenia z najemcami' }, { level:'Mid', stl:'T', pl:'Specjalista ds. Zamówień FM/RE', en:'Procurement Manager (FM/RE)', scope:'Przetargi FM/RE, umowy ramowe, zarządzanie dostawcami' }, { level:'Junior', stl:'O', pl:'Administrator Kontraktów', en:'Contract / Lease Data Administrator', scope:'Bazy danych umów, faktury, dokumentacja RE' }, ] }, { id: 'N8', label: 'NON-CORE 8', title: 'Consultancy, Research & Advisory', subtitle: 'Doradztwo Zewnętrzne i Badania Rynku', desc: 'Audyty FM, specyfikacje usług, raporty rynkowe, doradztwo transakcyjne. Ekspertyza sprzedawana na rynek.', roles: [ { level:'Director', stl:'S', pl:'Dyrektor ds. Doradztwa RE', en:'Director – Real Estate Advisory', scope:'Strategiczne doradztwo dla korporacji i inwestorów' }, { level:'Senior', stl:'T', pl:'Starszy Konsultant FM/RE', en:'Senior Consultant (FM / Workplace / CRE)', scope:'Audyty FM, specyfikacje usług, przetargi po stronie klienta' }, { level:'Mid', stl:'O', pl:'Analityk Rynku Nieruchomości', en:'Market Research Analyst (RE)', scope:'Raporty rynkowe, vacancy rates, trendy, dane transakcyjne' }, { level:'Junior', stl:'O', pl:'Młodszy Konsultant / Analityk', en:'Junior Consultant / Research Associate', scope:'Dane, prezentacje, wsparcie projektów doradczych' }, ] }, { id: 'N9', label: 'NON-CORE 9', title: 'Facility Management (jako funkcja wspierająca)', subtitle: 'FM Outsourcowany / Niestrategiczny w modelu korporacyjnym', desc: 'W organizacjach, które traktują FM jako funkcję wyłącznie wspierającą i w pełni outsourcowaną — FM nie jest kompetencją wewnętrzną, lecz zarządzaną usługą zewnętrzną. Obejmuje całość operacji budynkowych oddanych dostawcy IFM.', split: true, client: { label: 'ZAMAWIAJĄCY — Minimalna funkcja nadzorcza (Client Lite)', roles: [ { level:'Director', stl:'S', pl:'Dyrektor Administracyjny / COO', en:'Head of Administration / COO', scope:'FM jako jeden z obszarów pod COO lub CFO, brak dedykowanego Head of FM' }, { level:'Senior', stl:'T', pl:'Menedżer ds. Usług Ogólnych', en:'General Services Manager / Office Manager', scope:'Nadzór nad jednym dostawcą IFM, budżet biurowy, relacja z landlord' }, { level:'Mid', stl:'O', pl:'Koordynator Administracyjno-FM', en:'Facilities Coordinator / Admin Manager', scope:'Punkt kontaktowy z dostawcą, helpdesk, odbiór faktur, raportowanie' }, { level:'Junior', stl:'O', pl:'Asystent Administracyjny ds. Biura', en:'Office Administrator / FM Assistant', scope:'Obsługa zgłoszeń, zaopatrzenie biura, wsparcie koordynatora' }, ] }, supplier: { label: 'DOSTAWCA — Pełna obsługa IFM (Total FM / Managed Services)', roles: [ { level:'C-level', stl:'S', pl:'Dyrektor Zarządzający (Dostawca IFM)', en:'Managing Director / CEO (FM Company)', scope:'Strategia i P&L całej firmy FM, pozyskiwanie portfela kontraktów' }, { level:'Director', stl:'S', pl:'Dyrektor Operacyjny / Account Director', en:'Operations Director / Senior Account Director', scope:'Zarządzanie kluczowymi kontraktami IFM, ekspansja klienta, eskalacje' }, { level:'Senior', stl:'T', pl:'Menedżer IFM / Menedżer Kontraktu', en:'IFM Manager / Contract Manager', scope:'Pełen zakres usług na kontrakcie: hard, soft, helpdesk, raportowanie' }, { level:'Mid', stl:'O', pl:'Menedżer Obiektu / Koordynator IFM', en:'Site Manager / IFM Coordinator', scope:'Codzienna operacja budynku, nadzór serwisów, kontakt z klientem' }, { level:'Junior', stl:'O', pl:'Technik / Specjalista Serwisu', en:'Multi-skilled Technician / Service Specialist', scope:'Realizacja przeglądów, napraw, interwencji technicznych i miękkich' }, ], frontline: [ { level:'Brygadzista', stl:'W', pl:'Brygadzista Utrzymania (IFM)', en:'IFM Shift Supervisor / Team Leader', scope:'Nadzór nad całym zespołem wykonawczym na zmianie (technika + serwisy miękkie), przydzielanie zadań, eskalacje' }, { level:'Pracownik', stl:'W', pl:'Technik Wielobranżowy (IFM)', en:'Multi-skilled IFM Technician', scope:'Realizacja zleceń wszystkich branż technicznych w ramach kontraktu IFM — elektryka, HVAC, hydraulika, BMS' }, { level:'Pracownik', stl:'W', pl:'Pracownik Sprzątający (IFM)', en:'IFM Cleaning Operative', scope:'Sprzątanie obiektu w ramach zintegrowanego kontraktu FM — koordynacja z techniką' }, { level:'Pracownik', stl:'W', pl:'Pracownik Ochrony (IFM)', en:'IFM Security Officer', scope:'Ochrona obiektu jako element zintegrowanego kontraktu FM — monitoring, dostęp, obchody' }, ] } }, { id: 'N10', label: 'NON-CORE 10 ⚡', title: 'Energy & Utilities Management', subtitle: 'Zarządzanie Energią i Mediami — funkcja crossfunkcyjna', desc: 'Funkcja crossfunkcyjna przecinająca kilka kategorii. Zależnie od kontekstu organizacyjnego zakorzeniona w ESG (dekarbonizacja), FM (media/koszty operacyjne), Finance (zakup energii) lub Asset Management (wartość aktywa). Poniżej pełna taksonomia stanowisk z oznaczeniem przynależności.', energy: true, crosslinks: ['CORE 1 — wartość aktywa / Green Premium', 'CORE 4 — media jako koszt FM', 'NON-CORE 3 — dekarbonizacja / ESG', 'NON-CORE 7 — zakup energii / Finance'], roles: [ { level:'C-level', stl:'S', pl:'Dyrektor ds. Energii i Dekarbonizacji', en:'Head of Energy & Decarbonisation', scope:'Strategia energetyczna portfela, net zero roadmap, raportowanie GHG Scope 1/2/3', context:'NON-CORE 3 / CORE 1' }, { level:'Director', stl:'S', pl:'Dyrektor ds. Zarządzania Energią', en:'Energy Director / Head of Energy Management', scope:'Zarządzanie programem energetycznym całej organizacji lub portfela nieruchomości', context:'NON-CORE 3' }, { level:'Senior', stl:'T', pl:'Starszy Menedżer Energii (Portfel)', en:'Senior Energy Manager (Portfolio)', scope:'Monitoring zużycia energii w portfelu, projekty efektywności, targety redukcji', context:'CORE 4 Client / NON-CORE 3' }, { level:'Senior', stl:'T', pl:'Menedżer Zakupów Energii', en:'Energy Procurement Manager', scope:'Przetargi na dostawy energii, hedging cen, umowy PPA, rozliczenia service charge', context:'NON-CORE 7' }, { level:'Senior', stl:'T', pl:'Menedżer EPC / Gwarantowanych Oszczędności', en:'Energy Performance Contract Manager', scope:'Energy Performance Contracting, M&V (Measurement & Verification), gwarantowane savings', context:'CORE 4 Supplier' }, { level:'Mid', stl:'T', pl:'Menedżer Energii (Obiekt)', en:'Energy / Utilities Manager (Site)', scope:'Zarządzanie mediami na obiekcie: energia, woda, gaz — monitoring, awarie, budżet', context:'CORE 4 Client lub Supplier' }, { level:'Mid', stl:'O', pl:'Analityk Zużycia Energii', en:'Energy Data Analyst / Metering Analyst', scope:'Submetering, dashboardy zużycia, anomalie, raporty ISO 50001, ESOS compliance', context:'CORE 4 / NON-CORE 6' }, { level:'Mid', stl:'T', pl:'Specjalista ds. Efektywności Energetycznej', en:'Energy Efficiency Specialist', scope:'Audyty energetyczne, modernizacje instalacji, LED, BMS, pompy ciepła, OZE', context:'NON-CORE 3' }, { level:'Junior', stl:'O', pl:'Koordynator ds. Energii i Mediów', en:'Energy Coordinator / Utilities Administrator', scope:'Zbieranie danych licznikowych, obsługa faktur za media, wsparcie raportowania', context:'CORE 4 / NON-CORE 7' }, ] }, ] }; function makeRoleCard(role) { const stlColors = { S:'#4F46E5', T:'#D97706', O:'#16A34A' }; const stlBadge = role.stl ? `<div class="role-level-badge ${role.stl}" style="background:${stlColors[role.stl]}">${role.stl}</div>` : ''; return ` <div class="role-card" data-stl="${role.stl||''}" onmouseenter="showTooltip(event, '${role.scope.replace(/'/g,"'")}')" onmouseleave="hideTooltip()"> ${stlBadge} <div class="role-level">${role.level}</div> <div class="role-title-pl">${role.pl}</div> <div class="role-title-en">${role.en}</div> </div>`; } function makeFrontlineCard(role) { const stlColors = { W:'#B45309' }; return ` <div class="role-card wykonawczy-card" data-stl="W" onmouseenter="showTooltip(event, '${role.scope.replace(/'/g,"'")}')" onmouseleave="hideTooltip()"> <div class="role-level-badge W" style="background:#B45309">W</div> <div class="role-level" style="color:#B45309">${role.level}</div> <div class="role-title-pl" style="color:#7C2D12">${role.pl}</div> <div class="role-title-en">${role.en}</div> </div>`; } function makeSplitSection(sec, isCore) { const cls = isCore ? 'core-block' : 'nc-block'; const tag = isCore ? 'CORE' : 'NON-CORE'; const tagStyle = isCore ? '' : ''; return ` <div class="section-block ${cls}" id="${sec.id}"> <div class="section-header" onclick="toggle('${sec.id}')"> <span class="section-num">${sec.label}</span> <div style="flex:1"> <div class="section-title">${sec.title}</div> <div class="section-subtitle">${sec.subtitle}</div> </div> <span class="section-tag">${tag}</span> <span style="font-family:'DM Mono',monospace;font-size:10px;opacity:0.5;margin:0 8px">CLIENT + SUPPLIER</span> <span class="chevron">▼</span> </div> <div class="roles-panel"> <div style="font-size:11px;color:#666;padding:4px 0 8px;font-style:italic">${sec.desc}</div> <div class="split-panel"> <div class="split-side client-side"> <div class="perspective-label">${sec.client.label}</div> <div class="split-roles-grid"> ${sec.client.roles.map(makeRoleCard).join('')} </div> </div> <div class="split-side supplier-side"> <div class="perspective-label">${sec.supplier.label}</div> <div class="split-roles-grid"> ${sec.supplier.roles.map(makeRoleCard).join('')} </div> ${sec.supplier.frontline ? ` <div class="wykonawczy-section"> <div class="perspective-label">↳ POZIOM WYKONAWCZY — Pracownicy i Brygadziści (Frontline)</div> <div class="split-roles-grid"> ${sec.supplier.frontline.map(makeFrontlineCard).join('')} </div> </div>` : ''} </div> </div> </div> </div>`; } function makeEnergySection(sec) { return ` <div class="section-block energy-block" id="${sec.id}"> <div class="section-header" onclick="toggle('${sec.id}')"> <span class="section-num">${sec.label}</span> <div style="flex:1"> <div class="section-title">${sec.title}</div> <div class="section-subtitle">${sec.subtitle}</div> </div> <span class="section-tag">NON-CORE</span> <span style="font-family:'DM Mono',monospace;font-size:10px;opacity:0.5;margin:0 8px">CROSSFUNKCYJNA</span> <span class="chevron">▼</span> </div> <div class="roles-panel"> <div style="font-size:11px;color:#555;padding:4px 0 8px;font-style:italic">${sec.desc}</div> <div class="crosslinks-bar"> <span class="crosslinks-label">⚡ Powiązania:</span> ${sec.crosslinks.map(l => `<span class="crosslink-pill">${l}</span>`).join('')} </div> <div class="energy-roles-grid"> ${sec.roles.map(r => { const stlColors = { S:'#4F46E5', T:'#D97706', O:'#16A34A' }; const stlBadge = r.stl ? `<div class="role-level-badge ${r.stl}" style="background:${stlColors[r.stl]}">${r.stl}</div>` : ''; return ` <div class="role-card" data-stl="${r.stl||''}" onmouseenter="showTooltip(event, '${r.scope.replace(/'/g,"'")}')" onmouseleave="hideTooltip()"> ${stlBadge} <div class="role-level">${r.level}</div> <div class="role-title-pl">${r.pl}</div> <div class="role-title-en">${r.en}</div> <div class="context-badge">${r.context}</div> </div>`;}).join('')} </div> </div> </div>`; } function makeSection(sec, isCore) { if (sec.energy) return makeEnergySection(sec); if (sec.split) return makeSplitSection(sec, isCore); const cls = isCore ? 'core-block' : 'nc-block'; const tag = isCore ? 'CORE' : 'NON-CORE'; return ` <div class="section-block ${cls}" id="${sec.id}"> <div class="section-header" onclick="toggle('${sec.id}')"> <span class="section-num">${sec.label}</span> <div style="flex:1"> <div class="section-title">${sec.title}</div> <div class="section-subtitle">${sec.subtitle}</div> </div> <span class="section-tag">${tag}</span> <span class="chevron">▼</span> </div> <div class="roles-panel"> <div style="font-size:11px;color:#666;padding:4px 0 8px;font-style:italic">${sec.desc}</div> <div class="roles-grid"> ${sec.roles.map(makeRoleCard).join('')} </div> </div> </div>`; } function render() { const canvas = document.getElementById('canvas'); let html = ''; // CORE divider html += `<div class="section-divider core-divider"> <div class="divider-line"></div> <div class="divider-label">CORE — Funkcje kluczowe</div> <div class="divider-line"></div> </div>`; DATA.core.forEach(sec => { html += makeSection(sec, true); }); // NON-CORE divider html += `<div class="section-divider nc-divider" style="margin-top:16px"> <div class="divider-line"></div> <div class="divider-label">NON-CORE — Funkcje wspierające</div> <div class="divider-line"></div> </div> <div style="padding:0 0 6px;font-size:11px;color:#999;font-style:italic;font-family:'DM Mono',monospace"> ★ = kategorie migrujące do CORE ze względu na regulacje ESG i cyfryzację · ⚡ = funkcja crossfunkcyjna (Energy) </div>`; DATA.noncore.forEach(sec => { html += makeSection(sec, false); }); canvas.innerHTML = html; } // ── LEVEL FILTER ──────────────────────────────────────────────────── let activeFilters = new Set(['S','T','O','W']); function filterLevel(lvl) { const card = document.querySelector(`.level-card[data-level="${lvl}"]`); if (activeFilters.has(lvl)) { // If all 3 active and we click one → solo filter that level if (activeFilters.size === 4) { activeFilters.clear(); activeFilters.add(lvl); } else if (activeFilters.size === 1) { // Re-enable all activeFilters = new Set(['S','T','O']); } else { activeFilters.delete(lvl); } } else { activeFilters.add(lvl); } applyFilter(); } function applyFilter() { const allActive = activeFilters.size === 4; // Update level cards visual state document.querySelectorAll('.level-card').forEach(c => { const lvl = c.dataset.level; c.classList.toggle('active', activeFilters.has(lvl)); }); // Dim/show role cards document.querySelectorAll('.role-card').forEach(card => { const stl = card.dataset.stl; if (allActive || !stl || activeFilters.has(stl)) { card.classList.remove('dimmed'); } else { card.classList.add('dimmed'); } }); // Auto-expand all sections when filter active if (!allActive) { document.querySelectorAll('.section-block').forEach(el => el.classList.add('open')); } // Update filter indicator const indicator = document.getElementById('filterIndicator'); const filterText = document.getElementById('filterText'); const labels = { S:'⬡ Strategiczny', T:'⬡ Taktyczny', O:'⬡ Operacyjny', W:'⬡ Wykonawczy' }; if (allActive) { indicator.style.display = 'none'; } else { indicator.style.display = 'flex'; const names = [...activeFilters].map(f => labels[f]).join(' + '); filterText.textContent = `Filtr aktywny: ${names} — widoczne tylko stanowiska tego poziomu`; } } function clearFilter() { activeFilters = new Set(['S','T','O','W']); applyFilter(); } function toggleLevelPanel() { const cards = document.getElementById('levelCards'); const btn = document.querySelector('.level-panel-toggle'); const hidden = cards.style.display === 'none'; cards.style.display = hidden ? 'grid' : 'none'; btn.textContent = hidden ? 'Zwiń ▲' : 'Rozwiń ▼'; } function toggle(id) { const el = document.getElementById(id); el.classList.toggle('open'); } function expandAll() { document.querySelectorAll('.section-block').forEach(el => el.classList.add('open')); } function collapseAll() { document.querySelectorAll('.section-block').forEach(el => el.classList.remove('open')); } // Tooltip const tooltip = document.getElementById('tooltip'); function showTooltip(e, text) { tooltip.textContent = text; tooltip.classList.add('visible'); moveTooltip(e); } function hideTooltip() { tooltip.classList.remove('visible'); } document.addEventListener('mousemove', e => { if (tooltip.classList.contains('visible')) moveTooltip(e); }); function moveTooltip(e) { tooltip.style.left = (e.clientX + 16) + 'px'; tooltip.style.top = (e.clientY - 8) + 'px'; } // ════════════════════════════════════════════════════════════════════ // BUILDING TYPE DATA // Dla każdej roli w DATA: dodajemy pole bt:[array of building codes] // Kody: OFF=biurowy, RET=handlowy, LOG=magazynowy, IND=przemysłowy, // HOT=hotelowy, MIX=mieszany, PUB=użyteczności publicznej // Brak pola bt = ALL (wszystkie typy) // ════════════════════════════════════════════════════════════════════ // Mapowanie ról na typy obiektów — bt field added inline below // Funkcje zarządcze portfelowe (C1,C3,C6) są relevantne dla wszystkich typów // FM (C4,N9) różni się znacznie między typami // Unikalne stanowiska per typ obiektu: const BUILDING_TYPES = { ALL: { name:'Wszystkie typy', emoji:'◎', color:'#1C1C1C' }, OFF: { name:'Biurowy', emoji:'🏢', color:'#2563EB', unique: [ { cat:'CORE 3', level:'Senior', stl:'T', pl:'Menedżer ds. Środowiska Pracy (CRE)', en:'Workplace Manager (Corporate Office)', scope:'Hybrydowy model pracy, desk booking, standardy biurowe, employee experience' }, { cat:'CORE 4', level:'Mid', stl:'O', pl:'Menedżer Recepcji Korporacyjnej', en:'Corporate Reception Manager', scope:'Zarządzanie recepcją, goście VIP, standardy obsługi w biurze klasy A' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Technik AV / Sal Konferencyjnych', en:'AV / Conference Room Technician', scope:'Obsługa systemów audio-video, sal spotkań, wsparcie techniczne prezentacji' }, { cat:'NON-CORE 2', level:'Mid', stl:'T', pl:'Koordynator Hot-Desk / Desk Booking', en:'Desk Booking Coordinator / Workplace App Admin', scope:'Administracja systemem rezerwacji biurek, analiza occupancy, wsparcie pracowników' }, { cat:'NON-CORE 4', level:'Pracownik', stl:'W', pl:'Barista / Kawiarnia Biurowa', en:'Corporate Barista / Coffee Bar Operative', scope:'Obsługa kawiarni lub punktu kawowego w biurze, przygotowanie napojów, obsługa gości' }, ] }, RET: { name:'Handlowy / Retail', emoji:'🛍️', color:'#DC2626', unique: [ { cat:'CORE 2', level:'Senior', stl:'T', pl:'Zarządca Centrum Handlowego', en:'Shopping Centre Manager / Mall Manager', scope:'Zarządzanie operacją centrum, tenant mix, marketing, footfall, relacje z najemcami retail' }, { cat:'CORE 2', level:'Mid', stl:'T', pl:'Menedżer ds. Najemców Retail', en:'Retail Leasing / Tenant Relations Manager', scope:'Relacje z najemcami sklepowymi, renegocjacje, egzekwowanie standardów handlowych' }, { cat:'CORE 6', level:'Senior', stl:'T', pl:'Doradca ds. Najmu Retail', en:'Retail Leasing Advisor', scope:'Komercjalizacja powierzchni handlowych, reprezentacja właściciela centrum, tenant mix strategy' }, { cat:'CORE 4', level:'Mid', stl:'O', pl:'Menedżer Operacji Centrum', en:'Mall Operations Manager', scope:'Koordynacja techniki, ochrony, czystości i logistyki dostaw w centrum handlowym' }, { cat:'NON-CORE 1', level:'Mid', stl:'O', pl:'Menedżer Marketingu Centrum', en:'Shopping Centre Marketing Manager', scope:'Kampanie, eventy, loyalty programme, media społecznościowe, ruch klientów' }, { cat:'CORE 4', level:'Brygadzista', stl:'W', pl:'Brygadzista Dostaw / Koordynator Rampy', en:'Delivery Bay Supervisor / Loading Dock Coordinator', scope:'Nadzór nad dostawami towarów do sklepów, harmonogram ramp, kontrola ruchu w strefie dostaw' }, { cat:'NON-CORE 4', level:'Pracownik', stl:'W', pl:'Pracownik Ochrony Retail (detektyw)', en:'Retail Loss Prevention Officer', scope:'Ochrona przed kradzieżami sklepowymi, monitoring CCTV, współpraca z policją' }, ] }, LOG: { name:'Magazynowy / Logistyczny', emoji:'📦', color:'#D97706', unique: [ { cat:'CORE 2', level:'Senior', stl:'T', pl:'Zarządca Parku Logistycznego', en:'Logistics Park Manager / Warehouse Estate Manager', scope:'Zarządzanie wielobudynkowym parkiem magazynowym, najem, utrzymanie, relacje z operatorami' }, { cat:'CORE 4', level:'Senior', stl:'T', pl:'Menedżer Techniczny – Obiekty Magazynowe', en:'Technical Manager (Warehouse / Industrial)', scope:'Utrzymanie bram, doków, posadzek przemysłowych, dachów, sprinklerów, instalacji specjalnych' }, { cat:'CORE 4', level:'Mid', stl:'O', pl:'Inspektor Techniczny Hali', en:'Warehouse Facilities Inspector / Site Surveyor', scope:'Przeglądy stanu technicznego hal, dachu, elewacji, infrastruktury zewnętrznej' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Technik Bram i Doków Załadunkowych', en:'Loading Dock / Dock Leveller Technician', scope:'Serwis bram przemysłowych, levelerów, uszczelnień dokowych, systemów sterowania' }, { cat:'NON-CORE 5', level:'Mid', stl:'T', pl:'Specjalista HSE – Obiekty Magazynowe', en:'HSE Specialist (Logistics / Warehouse)', scope:'BHP w obiektach wysokiego składowania, ATEX, praca wózków widłowych, ryzyko pożarowe' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Technik Sprinklerów / Instalacji Ppoż.', en:'Sprinkler / Fire Suppression Technician', scope:'Przeglądy i serwis systemów tryskaczowych, instalacji ppoż. wymaganych w halach magazynowych' }, ] }, IND: { name:'Przemysłowy / Fabryka', emoji:'🏭', color:'#6D28D9', unique: [ { cat:'CORE 4', level:'Director', stl:'S', pl:'Dyrektor Utrzymania Ruchu (Fabryka)', en:'Head of Maintenance / Plant Engineering Director', scope:'Strategia utrzymania ruchu zakładu, UR, TPM, budżet capex/opex infrastruktury' }, { cat:'CORE 4', level:'Senior', stl:'T', pl:'Inżynier Utrzymania Ruchu', en:'Maintenance Engineer / Plant Engineer', scope:'Planowanie i realizacja przeglądów maszyn i infrastruktury, zarządzanie CMMS, MTTR/MTBF' }, { cat:'CORE 4', level:'Mid', stl:'T', pl:'Planista Utrzymania Ruchu (CMMS)', en:'Maintenance Planner (CMMS / SAP PM)', scope:'Planowanie zleceń pracy, zarządzanie częściami zamiennymi, harmonogramy przeglądów' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Mechanik / Elektromechanik Maszyn', en:'Machine Mechanic / Electromechanical Technician', scope:'Naprawy i przeglądy maszyn produkcyjnych, instalacji przemysłowych, automatyki' }, { cat:'NON-CORE 5', level:'Senior', stl:'T', pl:'Inżynier BHP / Bezpieczeństwo Procesowe', en:'Process Safety Engineer / Industrial HSE Manager', scope:'Bezpieczeństwo procesów chemicznych lub produkcyjnych, ATEX, HAZOP, REACH' }, { cat:'NON-CORE 3', level:'Mid', stl:'T', pl:'Inżynier Środowiska / Emisji', en:'Environmental / Emissions Engineer', scope:'Monitoring emisji do atmosfery, ścieków, odpadów, pozwolenia środowiskowe, IPPC/IED' }, { cat:'CORE 4', level:'Brygadzista', stl:'W', pl:'Mistrz Utrzymania Ruchu / Shift UR', en:'Maintenance Shift Leader / UR Foreman', scope:'Nadzór nad zespołem mechaników i elektryków na zmianie, priorytety awaryjne' }, ] }, HOT: { name:'Hotelowy / Hospitality', emoji:'🏨', color:'#DB2777', unique: [ { cat:'CORE 2', level:'Senior', stl:'T', pl:'Menedżer Nieruchomości Hotelowej', en:'Hotel Property Manager / Asset Manager (Hospitality)', scope:'Zarządzanie aktywem hotelowym w imieniu właściciela, relacja z operatorem, KPI hotelowe' }, { cat:'CORE 4', level:'Senior', stl:'T', pl:'Chief Engineer / Dyrektor Techniczny Hotelu', en:'Chief Engineer (Hotel)', scope:'Zarządzanie całą infrastrukturą techniczną hotelu: HVAC, elektryka, hydraulika, windy, baseny' }, { cat:'NON-CORE 1', level:'Senior', stl:'T', pl:'Rooms Division Manager / Kierownik Pięter', en:'Rooms Division Manager / Housekeeping Manager', scope:'Zarządzanie piętrowymi, standardy czystości pokoi, zarządzanie bielizną, turnover' }, { cat:'NON-CORE 4', level:'Mid', stl:'O', pl:'Kierownik Służby Pięter', en:'Executive Housekeeper / Floor Supervisor', scope:'Nadzór nad pokojówkami, inspekcja pokoi, zarządzanie harmonogramem sprzątania' }, { cat:'NON-CORE 4', level:'Pracownik', stl:'W', pl:'Pokojówka / Housekeeper', en:'Room Attendant / Housekeeper', scope:'Sprzątanie i przygotowanie pokoi hotelowych według standardu marki' }, { cat:'NON-CORE 1', level:'Mid', stl:'O', pl:'Concierge / Guest Relations Manager', en:'Concierge / Guest Experience Manager', scope:'Obsługa gości VIP, rezerwacje, rekomendacje, wyjątkowe doświadczenia gości' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Technik Hotelowy (nocny)', en:'Hotel Night Maintenance Technician', scope:'Utrzymanie techniczne hotelu w godzinach nocnych, reakcja na usterki zgłoszone przez gości' }, ] }, MIX: { name:'Mieszany', emoji:'🏙️', color:'#0891B2', unique: [ { cat:'CORE 2', level:'Senior', stl:'T', pl:'Zarządca Obiektu Mixed-Use', en:'Mixed-Use Asset Manager / Property Manager', scope:'Zarządzanie portfelem mixed-use: biuro+retail+PRS/resi — różne reżimy najmu i standardy' }, { cat:'CORE 3', level:'Senior', stl:'T', pl:'Menedżer RE – Projekty Wielofunkcyjne', en:'CRE Manager (Mixed-Use / TOD Projects)', scope:'Koordynacja CRE w projektach mixed-use, transport-oriented development, joint ventures' }, { cat:'CORE 4', level:'Senior', stl:'T', pl:'Menedżer FM Mixed-Use (Multi-tenant)', en:'Multi-tenant FM Manager', scope:'FM dla obiektu obsługującego jednocześnie najemców biurowych, handlowych i mieszkalnych' }, { cat:'NON-CORE 1', level:'Mid', stl:'O', pl:'Community Manager (Mixed-Use)', en:'Community & Place Manager', scope:'Zarządzanie społecznością i tożsamością miejsca — eventy, relacje między najemcami różnych sektorów' }, { cat:'CORE 6', level:'Senior', stl:'T', pl:'Doradca ds. Transakcji Mixed-Use', en:'Mixed-Use Transaction Advisor', scope:'Due diligence i transakcje dla obiektów mixed-use — wycena różnych komponentów, complex leasing' }, ] }, PUB: { name:'Użyteczności Publicznej', emoji:'🎭', color:'#059669', unique: [ { cat:'CORE 2', level:'Senior', stl:'T', pl:'Zarządca Obiektu Kultury / Lotniska', en:'Public Venue / Airport Facility Manager', scope:'Zarządzanie obiektem użyteczności publicznej: muzeum, teatr, kino, arena, lotnisko, dworzec' }, { cat:'CORE 4', level:'Senior', stl:'T', pl:'Menedżer Operacji – Lotnisko / Arena', en:'Operations Manager (Airport / Arena / Stadium)', scope:'Koordynacja operacji dużego obiektu publicznego: przepływ ludzi, logistyka eventów, bezpieczeństwo' }, { cat:'NON-CORE 5', level:'Senior', stl:'T', pl:'Menedżer Bezpieczeństwa Masowego', en:'Crowd Safety / Mass Event Safety Manager', scope:'Planowanie ewakuacji, zarządzanie tłumem, bezpieczeństwo eventów masowych (SGSA, UEFA, FIFA)' }, { cat:'NON-CORE 4', level:'Mid', stl:'O', pl:'Kierownik Ochrony Obiektu Publicznego', en:'Security Manager (Public Venue)', scope:'Ochrona obiektu otwartego dla publiczności, kontrola wejść, procedury sytuacji kryzysowych' }, { cat:'NON-CORE 1', level:'Mid', stl:'O', pl:'Menedżer Obsługi Widzów / Visitor Experience', en:'Visitor Experience Manager', scope:'Zarządzanie doświadczeniem odwiedzających: przepływ, informacja, obsługa, dostępność' }, { cat:'CORE 4', level:'Pracownik', stl:'W', pl:'Technik Sceny / Obiekt Kulturalny', en:'Stage / Venue Technical Operative', scope:'Obsługa techniczna sceny, oświetlenia, nagłośnienia, riggingu w teatrach, kinach, salach widowiskowych' }, { cat:'NON-CORE 3', level:'Mid', stl:'T', pl:'Menedżer Zrównoważonego Transportu', en:'Sustainable Transport / Mobility Manager', scope:'Zarządzanie infrastrukturą mobilności: parkingi, rowery, EV charging, komunikacja publiczna dla obiektów' }, ] } }; // Mapa relevancji ról ogólnych do typów obiektów // Każda sekcja ma tablicę typów dla których jest relevantna // Role wspólne mają ALL, specyficzne mają subset const SECTION_BUILDING_MAP = { C1: ['ALL'], // Asset management — wszystkie typy C2: ['ALL'], // Property management — wszystkie typy C3: ['OFF','MIX','RET'], // CRE strategy — głównie biuro i mixed C4: ['ALL'], // FM — wszystkie typy C5: ['ALL'], // Development — wszystkie typy C6: ['ALL'], // Transaction — wszystkie typy N1: ['OFF','HOT','RET','MIX','PUB'], // Workplace experience — nie magazyn/fabryka N2: ['OFF','MIX'], // Workplace strategy — tylko biuro i mixed N3: ['ALL'], // ESG — wszystkie N4: ['ALL'], // Soft services — wszystkie N5: ['ALL'], // HSE — wszystkie N6: ['ALL'], // PropTech — wszystkie N7: ['ALL'], // Finance — wszystkie N8: ['ALL'], // Consultancy — wszystkie N9: ['ALL'], // FM non-core — wszystkie N10: ['ALL'], // Energy — wszystkie }; // Szczegółowe mapowanie ról w sekcjach do typów const ROLE_BUILDING_MAP = { // CORE 2 — Property Management specjalizacje 'Zarządca Centrum Handlowego': ['RET'], 'Zarządca Parku Logistycznego': ['LOG','IND'], 'Zarządca Obiektu Mixed-Use': ['MIX'], 'Zarządca Obiektu Kultury / Lotniska': ['PUB'], // CORE 4 — FM specjalizacje 'Menedżer Techniczny – Obiekty Magazynowe': ['LOG'], 'Chief Engineer / Dyrektor Techniczny Hotelu': ['HOT'], 'Dyrektor Utrzymania Ruchu (Fabryka)': ['IND'], 'Inżynier Utrzymania Ruchu': ['IND'], // NON-CORE 1 — Workplace 'Community Manager (Mixed-Use)': ['MIX'], 'Menedżer Obsługi Widzów / Visitor Experience': ['PUB'], }; let activeBuilding = 'ALL'; function filterBuilding(btype) { activeBuilding = btype; // Update card states document.querySelectorAll('.btype-card').forEach(c => { c.classList.toggle('active', c.dataset.btype === btype); }); if (btype === 'ALL') { // Show everything document.querySelectorAll('.role-card').forEach(c => c.classList.remove('btype-hidden')); document.querySelectorAll('.wykonawczy-card').forEach(c => c.classList.remove('btype-hidden')); document.querySelectorAll('.section-block').forEach(c => c.classList.remove('btype-all-hidden')); document.getElementById('btypeUniquePanel').style.display = 'none'; document.getElementById('btypeActiveBar').style.display = 'none'; return; } const btData = BUILDING_TYPES[btype]; // Show unique roles panel const uniquePanel = document.getElementById('btypeUniquePanel'); const uniqueGrid = document.getElementById('btypeUniqueGrid'); const uniqueTitle = document.getElementById('btypeUniqueTitle'); uniqueTitle.textContent = `${btData.emoji} ${btData.name} — stanowiska unikalne i charakterystyczne`; uniqueGrid.innerHTML = btData.unique.map(u => ` <div class="btype-unique-card" style="border-left-color:${btData.color}"> <div class="u-cat">${u.cat}</div> <div class="u-pl">${u.pl}</div> <div class="u-en">${u.en}</div> <div class="u-scope">${u.scope}</div> <span class="u-level">${u.level} · ${u.stl}</span> </div>`).join(''); uniquePanel.style.display = 'block'; // Active bar document.getElementById('btypeActiveBar').style.display = 'flex'; document.getElementById('btypeActiveText').textContent = `Filtr aktywny: ${btData.emoji} ${btData.name} — wyświetlane tylko stanowiska relevantne dla tego typu obiektu`; // Expand all sections so hidden/visible is visible document.querySelectorAll('.section-block').forEach(el => el.classList.add('open')); // Filter sections and roles document.querySelectorAll('.section-block').forEach(block => { const secId = block.id; const secMap = SECTION_BUILDING_MAP[secId]; const secRelevant = !secMap || secMap.includes('ALL') || secMap.includes(btype); // Show all role cards in relevant sections, hide in irrelevant block.querySelectorAll('.role-card, .wykonawczy-card').forEach(card => { if (!secRelevant) { card.classList.add('btype-hidden'); } else { card.classList.remove('btype-hidden'); } }); // Count visible roles const visible = block.querySelectorAll('.role-card:not(.btype-hidden), .wykonawczy-card:not(.btype-hidden)').length; block.classList.toggle('btype-all-hidden', visible === 0); }); } function toggleBtypePanel() { const cards = document.getElementById('btypeCards'); const btn = document.querySelectorAll('.level-panel-toggle')[1]; if (!btn) return; const hidden = cards.style.display === 'none'; cards.style.display = hidden ? 'flex' : 'none'; btn.textContent = hidden ? 'Zwiń ▲' : 'Rozwiń ▼'; } // ════════════════════════════════════════════════════════════════════ // FINDER MODULE // ════════════════════════════════════════════════════════════════════ // Collect all roles from DATA into flat searchable list function getAllRoles() { const all = []; const addRoles = (roles, secId, secLabel, secTitle, side) => { (roles || []).forEach(r => { all.push({ ...r, secId, secLabel, secTitle, side: side || '' }); }); }; const processSec = (sec) => { if (sec.split) { addRoles(sec.client?.roles, sec.id, sec.label, sec.title, 'Zamawiający'); addRoles(sec.client?.frontline, sec.id, sec.label, sec.title, 'Zamawiający — Wykonawczy'); addRoles(sec.supplier?.roles, sec.id, sec.label, sec.title, 'Dostawca'); addRoles(sec.supplier?.frontline, sec.id, sec.label, sec.title, 'Dostawca — Wykonawczy'); } else if (sec.energy) { addRoles(sec.roles, sec.id, sec.label, sec.title, ''); } else { addRoles(sec.roles, sec.id, sec.label, sec.title, ''); } }; DATA.core.forEach(processSec); DATA.noncore.forEach(processSec); return all; } // Also include BUILDING_TYPES unique roles function getAllUniqueRoles() { const all = []; Object.entries(BUILDING_TYPES).forEach(([code, bt]) => { if (code === 'ALL' || !bt.unique) return; bt.unique.forEach(u => { all.push({ pl: u.pl, en: u.en, level: u.level, stl: u.stl, scope: u.scope, secId: u.cat.replace(' ',''), secLabel: u.cat, secTitle: `Stanowisko unikalne — ${bt.name}`, side: bt.name, isUnique: true, btCode: code }); }); }); return all; } // Session-added roles let addedRoles = []; // Normalize string for fuzzy matching function normalize(s) { return s.toLowerCase() .replace(/[ąą]/g,'a').replace(/[ćc]/g,'c').replace(/[ęe]/g,'e') .replace(/[łl]/g,'l').replace(/[ńn]/g,'n').replace(/[óo]/g,'o') .replace(/[śs]/g,'s').replace(/[źżz]/g,'z') .replace(/[^a-z0-9\s]/g,' ').trim(); } function score(query, role) { const q = normalize(query); const pl = normalize(role.pl); const en = normalize(role.en || ''); if (pl === q || en === q) return 100; if (pl.includes(q) || en.includes(q)) return 80; const words = q.split(/\s+/); const matched = words.filter(w => w.length > 2 && (pl.includes(w) || en.includes(w))); if (matched.length === words.length) return 70; if (matched.length > 0) return 40 + (matched.length / words.length) * 30; return 0; } function searchRoles(query) { if (!query || query.length < 2) return []; const all = [...getAllRoles(), ...getAllUniqueRoles(), ...addedRoles]; return all .map(r => ({ ...r, _score: score(query, r) })) .filter(r => r._score >= 40) .sort((a, b) => b._score - a._score) .slice(0, 8); } // Autocomplete const input = document.getElementById('finderInput'); const sugBox = document.getElementById('finderSuggestions'); function highlight(text, query) { const norm = normalize(query); const re = new RegExp('(' + norm.split(/\s+/).filter(w=>w.length>1).join('|') + ')', 'gi'); return text.replace(re, '<span class="suggestion-highlight">$1</span>'); } input.addEventListener('input', () => { const q = input.value.trim(); if (q.length < 2) { sugBox.classList.remove('open'); return; } const results = searchRoles(q); if (!results.length) { sugBox.classList.remove('open'); return; } sugBox.innerHTML = results.slice(0,6).map(r => ` <div class="finder-suggestion-item" onclick="selectSuggestion('${r.pl.replace(/'/g,"'")}')"> <div class="suggestion-pl">${highlight(r.pl, q)}</div> <div class="suggestion-meta">${r.secLabel} · ${r.level}</div> </div>`).join(''); sugBox.classList.add('open'); }); document.addEventListener('click', e => { if (!e.target.closest('.finder-input-wrap')) sugBox.classList.remove('open'); }); function selectSuggestion(pl) { input.value = pl; sugBox.classList.remove('open'); runFinder(); } // ── MAIN FINDER LOGIC ───────────────────────────────────────────── async function runFinder() { const query = input.value.trim(); if (!query) return; sugBox.classList.remove('open'); const results = searchRoles(query); const resultPanel = document.getElementById('finderResult'); resultPanel.style.display = 'block'; if (results.length > 0 && results[0]._score >= 60) { // FOUND showFound(results, query); } else { // NOT FOUND — use AI to classify await showNotFound(query); } resultPanel.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function showFound(results, query) { const panel = document.getElementById('finderResult'); const top = results.filter(r => r._score >= 60); panel.innerHTML = ` <div class="result-found"> <div class="result-found-header"> <span class="result-found-icon">✅</span> <div> <div class="result-found-title">Znaleziono ${top.length} dopasowanie${top.length>1?'nia':''}!</div> <div class="result-found-sub">Kliknij kartę, aby podświetlić stanowisko na schemacie powyżej</div> </div> </div> <div class="result-location-cards"> ${top.map(r => ` <div class="result-location-card" onclick="highlightRole('${r.secId}','${r.pl.replace(/'/g,"'")}')"> <div class="rlc-category">${r.secLabel}${r.side ? ' · '+r.side : ''}</div> <div class="rlc-name">${r.pl}</div> <div class="rlc-en">${r.en || ''}</div> <div class="rlc-badges"> <span class="rlc-badge level">${r.level}</span> ${r.stl ? `<span class="rlc-badge stl-${r.stl}">${r.stl === 'S' ? 'Strategiczny' : r.stl === 'T' ? 'Taktyczny' : r.stl === 'O' ? 'Operacyjny' : 'Wykonawczy'}</span>` : ''} ${r.isUnique ? `<span class="rlc-badge" style="background:#FEF9C3;color:#713F12;border-color:#FCD34D">Unikalne dla: ${r.side}</span>` : ''} </div> </div>`).join('')} </div> </div>`; } async function showNotFound(query) { const panel = document.getElementById('finderResult'); // Show loading state immediately panel.innerHTML = ` <div class="result-notfound"> <div class="result-nf-header"> <span class="result-nf-icon">🔍</span> <div> <div class="result-nf-title">Nie znaleziono: <strong>"${query}"</strong></div> <div class="result-nf-analyzing"> <span class="spinner"></span> Analizuję stanowisko i szukam właściwego miejsca w taksonomii... </div> </div> </div> </div>`; // Build taxonomy summary for AI context const taxSummary = [ ...DATA.core.map(s => `${s.id} | ${s.label}: ${s.title} — ${s.desc}`), ...DATA.noncore.map(s => `${s.id} | ${s.label}: ${s.title} — ${s.desc}`) ].join('\n'); const prompt = `Jesteś ekspertem taksonomii stanowisk w branży Real Estate Management (RE, FM, CRE). Użytkownik wpisał stanowisko: "${query}" Oto dostępne kategorie taksonomii: ${taxSummary} Poziomy seniority: C-level, Director, Senior, Mid, Junior, Brygadzista, Pracownik Poziomy zarządzania: S (Strategiczny: C-level/Director), T (Taktyczny: Senior/Mid+), O (Operacyjny: Mid/Junior), W (Wykonawczy: Pracownik/Brygadzista) Typy obiektów: ALL, OFF (biurowy), RET (handlowy), LOG (magazynowy), IND (przemysłowy), HOT (hotelowy), MIX (mieszany), PUB (użyteczności publicznej) Odpowiedz TYLKO w formacie JSON (bez żadnego tekstu przed ani po): { "secId": "np. C4 lub N5", "secLabel": "pełna etykieta kategorii np. CORE 4", "secTitle": "tytuł kategorii", "level": "np. Senior", "stl": "S|T|O|W", "pl": "poprawiona/pełna polska nazwa stanowiska", "en": "angielska nazwa stanowiska", "scope": "1-2 zdania opisu zakresu obowiązków tego stanowiska w kontekście RE/FM", "btype": "kod typu obiektu lub ALL", "reasoning": "1 zdanie po polsku dlaczego ta kategoria" }`; try { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 1000, messages: [{ role: 'user', content: prompt }] }) }); const data = await response.json(); const raw = data.content?.map(b => b.text || '').join('') || ''; const clean = raw.replace(/```json|```/g, '').trim(); const ai = JSON.parse(clean); showAIClassification(query, ai); } catch (err) { // Fallback to manual form if AI fails showManualForm(query); } } function showAIClassification(query, ai) { const panel = document.getElementById('finderResult'); const stlLabel = { S:'Strategiczny', T:'Taktyczny', O:'Operacyjny', W:'Wykonawczy' }[ai.stl] || ai.stl; const stlColor = { S:'#4F46E5', T:'#D97706', O:'#16A34A', W:'#B45309' }[ai.stl] || '#666'; const btName = BUILDING_TYPES[ai.btype]?.name || 'Wszystkie typy'; const btEmoji = BUILDING_TYPES[ai.btype]?.emoji || '◎'; panel.innerHTML = ` <div class="result-notfound"> <div class="result-nf-header"> <span class="result-nf-icon">🤖</span> <div> <div class="result-nf-title">Stanowisko nieznalezione — AI zaproponowała klasyfikację</div> <div class="result-nf-sub">Sprawdź propozycję i zatwierdź lub edytuj przed dodaniem do schematu</div> </div> </div> <div class="ai-classification-card"> <div class="ai-class-row"> <div class="ai-class-field"> <div class="ai-class-label">Nazwa stanowiska (PL)</div> <input class="add-input" id="addPl" value="${ai.pl}" /> </div> <div class="ai-class-field"> <div class="ai-class-label">Nazwa (EN)</div> <input class="add-input" id="addEn" value="${ai.en}" /> </div> </div> <div class="ai-class-row"> <div class="ai-class-field"> <div class="ai-class-label">Kategoria taksonomii</div> <select class="add-select" id="addSec"> <option value="">— wybierz —</option> ${[...DATA.core, ...DATA.noncore].map(s => `<option value="${s.id}" ${s.id === ai.secId ? 'selected' : ''}>${s.label} — ${s.title}</option>` ).join('')} </select> </div> <div class="ai-class-field"> <div class="ai-class-label">Poziom seniority</div> <select class="add-select" id="addLevel"> ${['C-level','Director','Senior','Mid','Junior','Brygadzista','Pracownik'].map(l => `<option value="${l}" ${l === ai.level ? 'selected' : ''}>${l}</option>` ).join('')} </select> </div> <div class="ai-class-field"> <div class="ai-class-label">Poziom zarządzania</div> <select class="add-select" id="addStl"> ${[['S','Strategiczny'],['T','Taktyczny'],['O','Operacyjny'],['W','Wykonawczy']].map(([v,n]) => `<option value="${v}" ${v === ai.stl ? 'selected' : ''}>${v} — ${n}</option>` ).join('')} </select> </div> <div class="ai-class-field"> <div class="ai-class-label">Typ obiektu</div> <select class="add-select" id="addBtype"> ${Object.entries(BUILDING_TYPES).map(([k,v]) => `<option value="${k}" ${k === ai.btype ? 'selected' : ''}>${v.emoji || '◎'} ${v.name}</option>` ).join('')} </select> </div> </div> <div class="ai-class-field" style="margin-top:8px"> <div class="ai-class-label">Zakres obowiązków</div> <textarea class="add-textarea" id="addScope" rows="2">${ai.scope}</textarea> </div> <div class="ai-reasoning"> <span class="ai-reasoning-icon">💡</span> <span>${ai.reasoning}</span> </div> <div class="ai-summary-pills"> <span class="rlc-badge level">${ai.level}</span> <span class="rlc-badge stl-${ai.stl}" style="background:${stlColor}22;color:${stlColor};border-color:${stlColor}44">${stlLabel}</span> <span class="rlc-badge" style="background:#F0F9FF;color:#0369A1;border-color:#BAE6FD">${btEmoji} ${btName}</span> <span class="rlc-badge" style="background:#F5F3FF;color:#5B21B6;border-color:#DDD6FE">${ai.secLabel}</span> </div> </div> <div class="add-submit-row"> <button class="add-submit-btn" onclick="submitAddRole()">📌 Zatwierdź i dodaj do schematu</button> <button class="add-skip-btn" onclick="showManualForm('${query.replace(/'/g,"\\'")}')">✏️ Edytuj ręcznie</button> <button class="add-skip-btn" onclick="document.getElementById('finderResult').style.display='none'">Pomiń</button> </div> </div>`; } function showManualForm(query) { const panel = document.getElementById('finderResult'); const sectionOptions = [ ...DATA.core.map(s => `<option value="${s.id}">${s.label} — ${s.title}</option>`), ...DATA.noncore.map(s => `<option value="${s.id}">${s.label} — ${s.title}</option>`) ].join(''); panel.innerHTML = ` <div class="result-notfound"> <div class="result-nf-header"> <span class="result-nf-icon">✏️</span> <div class="result-nf-title">Uzupełnij ręcznie: <strong>"${query}"</strong></div> </div> <div class="result-nf-sub">Podaj szczegóły stanowiska aby dodać je do schematu.</div> <div class="ai-class-row" style="margin-top:16px"> <div class="ai-class-field"> <div class="ai-class-label">Nazwa stanowiska (PL)</div> <input class="add-input" id="addPl" value="${query}" /> </div> <div class="ai-class-field"> <div class="ai-class-label">Nazwa (EN)</div> <input class="add-input" id="addEn" placeholder="np. Facility Manager" /> </div> </div> <div class="ai-class-row"> <div class="ai-class-field"> <div class="ai-class-label">Kategoria</div> <select class="add-select" id="addSec"><option value="">— wybierz —</option>${sectionOptions}</select> </div> <div class="ai-class-field"> <div class="ai-class-label">Seniority</div> <select class="add-select" id="addLevel"> ${['C-level','Director','Senior','Mid','Junior','Brygadzista','Pracownik'].map(l => `<option value="${l}" ${l==='Mid'?'selected':''}>${l}</option>`).join('')} </select> </div> <div class="ai-class-field"> <div class="ai-class-label">Poziom zarządzania</div> <select class="add-select" id="addStl"> <option value="S">S — Strategiczny</option> <option value="T" selected>T — Taktyczny</option> <option value="O">O — Operacyjny</option> <option value="W">W — Wykonawczy</option> </select> </div> <div class="ai-class-field"> <div class="ai-class-label">Typ obiektu</div> <select class="add-select" id="addBtype"> ${Object.entries(BUILDING_TYPES).map(([k,v]) => `<option value="${k}">${v.emoji||'◎'} ${v.name}</option>`).join('')} </select> </div> </div> <div class="ai-class-field" style="margin-top:8px"> <div class="ai-class-label">Zakres obowiązków</div> <textarea class="add-textarea" id="addScope" rows="2" placeholder="Krótki opis roli..."></textarea> </div> <div class="add-submit-row" style="margin-top:12px"> <button class="add-submit-btn" onclick="submitAddRole()">📌 Dodaj do schematu</button> <button class="add-skip-btn" onclick="document.getElementById('finderResult').style.display='none'">Pomiń</button> </div> </div>`; } function submitAddRole() { const pl = document.getElementById('addPl')?.value?.trim(); const en = document.getElementById('addEn')?.value?.trim() || pl; const secId = document.getElementById('addSec')?.value; const level = document.getElementById('addLevel')?.value || 'Mid'; const stl = document.getElementById('addStl')?.value || 'T'; const scope = document.getElementById('addScope')?.value?.trim() || 'Stanowisko dodane przez użytkownika'; const btype = document.getElementById('addBtype')?.value || 'ALL'; if (!pl) return; // Find section info const allSecs = [...DATA.core, ...DATA.noncore]; const sec = allSecs.find(s => s.id === secId); const secLabel = sec ? sec.label : 'Nieprzypisane'; const secTitle = sec ? sec.title : ''; const newRole = { pl, en, level, stl, scope, secId, secLabel, secTitle, side: '', isAdded: true, btype }; addedRoles.push(newRole); // Add card to schema if section exists if (sec && !sec.split && !sec.energy) { sec.roles.push({ pl, en, level, stl, scope, isAdded: true }); render(); // re-render // Re-apply filters applyFilter(); if (activeBuilding !== 'ALL') filterBuilding(activeBuilding); } // Show success const panel = document.getElementById('finderResult'); const stlLabel = { S:'Strategiczny', T:'Taktyczny', O:'Operacyjny', W:'Wykonawczy' }[stl] || stl; panel.innerHTML = ` <div class="result-added"> <div class="result-added-header"> <span class="result-added-icon">📌</span> <div class="result-added-title">Stanowisko dodane do schematu!</div> </div> <div class="result-added-card"> <div class="rlc-category">${secLabel}${secTitle ? ' — '+secTitle : ''}</div> <div class="rlc-name">${pl}</div> <div class="rlc-en">${en}</div> <div class="rlc-badges" style="margin-top:6px"> <span class="rlc-badge level">${level}</span> <span class="rlc-badge stl-${stl}">${stlLabel}</span> </div> <div class="added-in-schema">${sec ? '✓ Widoczne w schemacie powyżej' : '✓ Dodane do listy stanowisk sesji'}</div> </div> </div>`; updateAddedList(); // Highlight in schema if added if (sec) setTimeout(() => highlightRole(secId, pl), 400); } function highlightRole(secId, pl) { // Open section const block = document.getElementById(secId); if (block) { block.classList.add('open'); // Find and flash the role card block.querySelectorAll('.role-card, .wykonawczy-card').forEach(card => { const titleEl = card.querySelector('.role-title-pl'); if (titleEl && normalize(titleEl.textContent).includes(normalize(pl.substring(0,10)))) { card.scrollIntoView({ behavior:'smooth', block:'center' }); card.style.transition = 'box-shadow 0.3s, transform 0.3s'; card.style.boxShadow = '0 0 0 3px #2563EB, 0 8px 24px rgba(37,99,235,0.3)'; card.style.transform = 'scale(1.04)'; setTimeout(() => { card.style.boxShadow = ''; card.style.transform = ''; }, 2200); } }); } } function updateAddedList() { if (!addedRoles.length) return; const list = document.getElementById('finderAddedList'); const items = document.getElementById('finderAddedItems'); list.style.display = 'block'; items.innerHTML = addedRoles.map(r => ` <span class="added-pill" onclick="highlightRole('${r.secId}','${r.pl.replace(/'/g,"'")}')"> <span class="added-pill-cat">${r.secLabel}</span> ${r.pl} </span>`).join(''); } render(); </script> </body> </html>