<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>안전보건조직도생성기 &#8211; 산업안전지원센터 주식회사</title>
	<atom:link href="https://safetysupport.co.kr/tag/%ec%95%88%ec%a0%84%eb%b3%b4%ea%b1%b4%ec%a1%b0%ec%a7%81%eb%8f%84%ec%83%9d%ec%84%b1%ea%b8%b0/feed/" rel="self" type="application/rss+xml" />
	<link>https://safetysupport.co.kr</link>
	<description>안전관리위탁부터 위험성평가, 중대재해예방까지 — 사업장 맞춤 안전보건 컨설팅</description>
	<lastBuildDate>Sun, 19 Apr 2026 10:10:21 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://safetysupport.co.kr/wp-content/uploads/2026/03/cropped-파비콘1-32x32.png</url>
	<title>안전보건조직도생성기 &#8211; 산업안전지원센터 주식회사</title>
	<link>https://safetysupport.co.kr</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>안전보건조직도 생성기</title>
		<link>https://safetysupport.co.kr/safe-health-org-chart-generator/</link>
		
		<dc:creator><![CDATA[mangjil]]></dc:creator>
		<pubDate>Sun, 19 Apr 2026 04:00:26 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[관리감독자]]></category>
		<category><![CDATA[보건관리자]]></category>
		<category><![CDATA[산업안전보건법]]></category>
		<category><![CDATA[산업안전보건위원회]]></category>
		<category><![CDATA[안전관리자]]></category>
		<category><![CDATA[안전보건관리책임자]]></category>
		<category><![CDATA[안전보건조직도]]></category>
		<category><![CDATA[안전보건조직도생성기]]></category>
		<category><![CDATA[안전보건총괄책임자]]></category>
		<guid isPermaLink="false">https://safetysupport.co.kr/?p=3052</guid>

					<description><![CDATA[안전보건조직도 생성기 &#124; 산업안전지원센터㈜ 안전보건조직도 생성기 SAFETY &#38; HEALTH ORGANIZATION CHART GENERATOR 산업안전지원센터㈜ safetysupport.co.kr 1 회사 정보 회사명 (제목 1줄) 부제 (제목 2줄) 작성일자 PDF 용지 방향 A4 세로A4 가로 2 라인 조직 (중앙) 3 스탭 조직 (우측) 4 도급·회의체 (좌측) 초기화 PDF 다운로드 🔒 워터마크 설정 해제 잠금 실시간 미리보기 서식 목록으로 맨 위로 [&#8230;]]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="3052" class="elementor elementor-3052" data-elementor-post-type="post">
				<div class="elementor-element elementor-element-3a6ad94 e-flex e-con-boxed e-con e-parent" data-id="3a6ad94" data-element_type="container" data-e-type="container">
					<div class="e-con-inner">
				<div class="elementor-element elementor-element-ae4a2de elementor-widget elementor-widget-html" data-id="ae4a2de" data-element_type="widget" data-e-type="widget" data-widget_type="html.default">
					<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>안전보건조직도 생성기 | 산업안전지원센터㈜</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/moonspam/NanumSquareNeo@latest/NanumSquareNeo.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
  :root {
    --deep-green: #0D3321;
    --deep-green-light: #3db86b;
    --blue: #1a3a6b;
    --blue-light: #4a9ee8;
    --burgundy: #7a1e2e;
    --orange: #C55A11;
    --gray-900: #1F2937;
    --gray-700: #374151;
    --gray-500: #6B7280;
    --gray-400: #9CA3AF;
    --gray-300: #D1D5DB;
    --gray-200: #E5E7EB;
    --gray-100: #F3F4F6;
    --gray-50: #F9FAFB;
    --white: #FFFFFF;
    --line-color: #374151;
  }

  * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'NanumSquareNeo', 'Noto Sans KR', sans-serif; }

  body {
    background: var(--gray-100);
    color: var(--gray-900);
    line-height: 1.5;
    padding: 20px;
  }

  .wrap { max-width: 1500px; margin: 0 auto; }

  .page-header {
    background: var(--deep-green);
    color: var(--white);
    padding: 24px 32px;
    border-radius: 10px 10px 0 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .page-header h1 {
    font-size: 22px;
    font-weight: 800;
    letter-spacing: -0.5px;
  }

  .page-header .sub {
    font-size: 12px;
    color: var(--deep-green-light);
    margin-top: 4px;
    letter-spacing: 2px;
  }

  .page-header .brand {
    font-size: 11px;
    color: rgba(255,255,255,0.7);
    text-align: right;
  }

  .main {
    display: grid;
    grid-template-columns: 420px 1fr;
    gap: 20px;
    background: var(--white);
    padding: 24px;
    border-radius: 0 0 10px 10px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.05);
  }

  @media (max-width: 1024px) {
    .main { grid-template-columns: 1fr; }
  }

  .control-panel {
    max-height: 85vh;
    overflow-y: auto;
    padding-right: 8px;
  }

  .control-panel::-webkit-scrollbar { width: 6px; }
  .control-panel::-webkit-scrollbar-thumb {
    background: var(--gray-300);
    border-radius: 3px;
  }

  .section { margin-bottom: 20px; }

  .section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 800;
    color: var(--deep-green);
    padding-bottom: 8px;
    margin-bottom: 12px;
    border-bottom: 2px solid var(--deep-green);
  }

  .section-title .num {
    display: inline-block;
    background: var(--deep-green);
    color: var(--white);
    width: 20px;
    height: 20px;
    text-align: center;
    line-height: 20px;
    border-radius: 4px;
    font-size: 11px;
  }

  .box-card {
    background: var(--gray-50);
    border: 1px solid var(--gray-200);
    border-radius: 6px;
    padding: 12px;
    margin-bottom: 8px;
    transition: background 0.15s, border-color 0.15s;
  }

  .box-card.active {
    background: var(--white);
    border-color: var(--deep-green);
  }

  .box-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
  }

  .box-head label {
    flex: 1;
    font-size: 13px;
    font-weight: 700;
    color: var(--gray-900);
    cursor: pointer;
  }

  .box-head .tag {
    font-size: 9px;
    padding: 2px 6px;
    border-radius: 3px;
    letter-spacing: 0.5px;
  }

  .tag.line { background: #d1fae5; color: #065f46; }
  .tag.staff { background: #dbeafe; color: #1e40af; }
  .tag.contract { background: #fee2e2; color: #991b1b; }
  .tag.committee { background: #fef3c7; color: #92400e; }

  .box-head input[type="checkbox"] {
    width: 16px;
    height: 16px;
    accent-color: var(--deep-green);
    cursor: pointer;
  }

  .fields {
    display: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease;
  }

  .box-card.active .fields {
    display: block;
    max-height: 500px;
  }

  .field-checks {
    display: flex;
    gap: 8px;
    margin-bottom: 6px;
    flex-wrap: wrap;
  }

  .field-checks label {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11px;
    color: var(--gray-700);
    cursor: pointer;
  }

  .field-checks input[type="checkbox"] {
    width: 12px;
    height: 12px;
    accent-color: var(--deep-green);
  }

  .inputs {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
  }

  .inputs input {
    padding: 6px 8px;
    border: 1px solid var(--gray-300);
    border-radius: 4px;
    font-size: 12px;
    font-family: inherit;
    width: 100%;
    transition: border-color 0.15s;
  }

  .inputs input:focus {
    outline: none;
    border-color: var(--deep-green);
  }

  .inputs input::placeholder { color: var(--gray-400); }
  .inputs input:disabled {
    background: var(--gray-100);
    color: var(--gray-400);
  }

  .supervisor-list {
    background: var(--white);
    border: 1px solid var(--gray-200);
    border-radius: 4px;
    padding: 8px;
    margin-top: 6px;
  }

  .supervisor-item {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 6px;
    align-items: center;
    margin-bottom: 6px;
  }

  .supervisor-item:last-child { margin-bottom: 0; }

  .supervisor-inputs {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
  }

  .supervisor-inputs input {
    padding: 5px 6px;
    border: 1px solid var(--gray-300);
    border-radius: 3px;
    font-size: 11px;
    font-family: inherit;
  }

  .supervisor-inputs input:focus {
    outline: none;
    border-color: var(--deep-green);
  }

  .btn-del {
    background: #fee2e2;
    color: #991b1b;
    border: none;
    border-radius: 3px;
    width: 22px;
    height: 22px;
    cursor: pointer;
    font-weight: 700;
    font-size: 12px;
  }

  .btn-add {
    width: 100%;
    background: var(--deep-green);
    color: var(--white);
    border: none;
    border-radius: 4px;
    padding: 8px;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
    margin-top: 6px;
    font-family: inherit;
  }

  .btn-add:hover { background: #0a2918; }

  .meta-grid { display: grid; gap: 8px; }

  .meta-grid input,
  .meta-grid select {
    padding: 8px 10px;
    border: 1px solid var(--gray-300);
    border-radius: 4px;
    font-size: 12px;
    font-family: inherit;
  }

  .meta-grid label {
    font-size: 11px;
    font-weight: 700;
    color: var(--gray-700);
    margin-bottom: 3px;
    display: block;
  }

  .action-bar {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 2px solid var(--gray-200);
  }

  .btn {
    padding: 12px;
    border: none;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 800;
    cursor: pointer;
    font-family: inherit;
    transition: all 0.15s;
  }

  .btn-primary {
    background: var(--deep-green);
    color: var(--white);
  }

  .btn-primary:hover { background: #0a2918; }

  .btn-primary:disabled {
    background: var(--gray-400);
    cursor: not-allowed;
  }

  .btn-secondary {
    background: var(--gray-200);
    color: var(--gray-700);
  }

  .btn-secondary:hover { background: var(--gray-300); }

  .preview-wrap {
    background: var(--gray-200);
    border-radius: 8px;
    padding: 20px;
    overflow: hidden;
    max-width: 100%;
  }

  .preview-label {
    font-size: 11px;
    font-weight: 700;
    color: var(--gray-500);
    margin-bottom: 12px;
    letter-spacing: 2px;
    text-align: center;
  }

  .chart-scaler {
    width: 100%;
    display: flex;
    justify-content: center;
    overflow: hidden;
  }

  .chart-scaler-inner {
    transform-origin: top center;
  }

  #chart-page {
    background: var(--white);
    margin: 0 auto;
     padding: 30px 16px 24px 16px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.08);
    transition: width 0.3s, min-height 0.3s;
    font-family: 'NanumSquareNeo', 'Noto Sans KR', sans-serif;
  }

  #chart-page.portrait { width: 794px; min-height: 1123px; }
  #chart-page.landscape { width: 1123px; min-height: 794px; }

  .doc-header {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: end;
    margin-bottom: 30px;
    padding-bottom: 16px;
    border-bottom: 3px double var(--deep-green);
  }

  .doc-title {
    font-size: 26px;
    font-weight: 800;
    color: var(--gray-900);
    margin-bottom: 6px;
    letter-spacing: -1px;
    line-height: 1.25;
  }

  .doc-subtitle {
    font-size: 20px;
    font-weight: 800;
    color: var(--deep-green);
    letter-spacing: -0.5px;
    line-height: 1.2;
  }

  .doc-meta {
    font-size: 11px;
    color: var(--gray-700);
    text-align: right;
  }

  .doc-meta .label {
    color: var(--gray-500);
    font-weight: 700;
    margin-right: 4px;
  }

  .chart-container {
    position: relative;
    width: 100%;
    padding: 20px 0;
  }

  .chart-svg-wrap {
    position: relative;
    width: 100%;
  }

  .chart-svg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1;
  }

  .chart-boxes {
    position: relative;
    z-index: 2;
  }

  .org-box {
    background: var(--white);
    border: 2px solid var(--gray-700);
    border-radius: 6px;
    padding: 8px 10px;
    text-align: center;
    box-shadow: 0 1px 3px rgba(0,0,0,0.08);
    position: absolute;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
  }

  .org-box .dept {
    font-size: 10px;
    color: var(--gray-500);
    font-weight: 600;
    margin-bottom: 2px;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .org-box .role {
    font-size: 13px;
    font-weight: 800;
    color: var(--gray-900);
    line-height: 1.2;
    white-space: normal;
    word-break: keep-all;
    overflow-wrap: anywhere;
    text-align: center;
  }

  .org-box .eng {
    display: block;
    width: 100%;
    font-size: 9px;
    color: var(--gray-500);
    font-weight: 500;
    margin-top: 3px;
    line-height: 1.2;
    text-align: center;
    white-space: normal;
    word-break: keep-all;
    overflow-wrap: anywhere;
  }

  .org-box .law {
    display: block;
    width: 100%;
    font-size: 8px;
    color: var(--gray-400);
    font-weight: 600;
    margin-top: 2px;
    line-height: 1.15;
    text-align: center;
    white-space: normal;
    word-break: keep-all;
    overflow-wrap: anywhere;
  }

  .org-box .name {
    font-size: 11px;
    color: var(--gray-700);
    margin-top: 2px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .org-box.primary {
    background: var(--deep-green);
    border-color: var(--deep-green);
  }

  .org-box.primary .dept { color: rgba(255,255,255,0.75); }
  .org-box.primary .role { color: var(--white); }
  .org-box.primary .eng { color: rgba(255,255,255,0.78); }
  .org-box.primary .law { color: rgba(255,255,255,0.62); }
  .org-box.primary .name { color: rgba(255,255,255,0.9); }

  .org-box.line {
    background: #ecfdf5;
    border-color: var(--deep-green);
  }

  .org-box.staff {
    background: #eff6ff;
    border-color: var(--blue);
  }

  .org-box.contract {
    background: #fef2f2;
    border-color: var(--burgundy);
  }

  .org-box.committee {
    background: #fffbeb;
    border-color: var(--orange);
    border-style: dashed;
  }


  .wm-box {
    background:#fff8f0;
    border:1px solid #f0d8b0;
    border-radius:10px;
    padding:12px 14px;
    margin-top:14px;
  }

  .wm-box .wm-title {
    font-size:12px;
    font-weight:700;
    color:#8B5A00;
    margin-bottom:8px;
    display:flex;
    align-items:center;
    gap:5px;
  }

  .wm-row {
    display:flex;
    gap:6px;
    align-items:center;
  }

  .wm-row input {
    flex:1;
    padding:6px 9px;
    font-size:12px;
    border:1px solid #ddd;
    border-radius:6px;
    font-family:inherit;
  }

  .wm-row input:focus {
    outline:none;
    border-color:#0D3321;
  }

  .wm-row button {
    padding:6px 12px;
    font-size:11px;
    font-weight:700;
    border:none;
    border-radius:6px;
    cursor:pointer;
    font-family:inherit;
    white-space:nowrap;
  }

  .wm-unlock-btn { background:#0D3321; color:#fff; }
  .wm-unlock-btn:hover { background:#1a5c3a; }
  .wm-lock-btn { background:#c0392b; color:#fff; }
  .wm-lock-btn:hover { background:#a93226; }

  .wm-status {
    font-size:11px;
    margin-top:6px;
    padding:4px 8px;
    border-radius:4px;
    display:none;
  }

  .wm-status.locked { background:#fde8e8; color:#c0392b; display:block; }
  .wm-status.unlocked { background:#e8f8ee; color:#1a7a3a; display:block; }

  #chart-page {
    position: relative;
  }

  .page-watermark {
    position:absolute;
    inset:140px 40px 40px 40px;
    display:flex;
    align-items:center;
    justify-content:center;
    pointer-events:none;
    z-index:3;
    overflow:hidden;
  }

  .page-watermark span {
    display:block;
    font-size:110px;
    font-weight:800;
    color:rgba(26,58,107,0.14);
    transform:rotate(-22deg);
    letter-spacing:2px;
    white-space:nowrap;
    user-select:none;
  }

  .legend {
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid var(--gray-300);
    display: flex;
    justify-content: center;
    gap: 20px;
    flex-wrap: wrap;
    font-size: 10px;
    color: var(--gray-500);
  }

  .legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .legend-color {
    width: 14px;
    height: 14px;
    border: 2px solid;
    border-radius: 2px;
  }

  .legend-color.l-line {
    background: #ecfdf5;
    border-color: var(--deep-green);
  }

  .legend-color.l-staff {
    background: #eff6ff;
    border-color: var(--blue);
  }

  .legend-color.l-contract {
    background: #fef2f2;
    border-color: var(--burgundy);
  }

  .legend-color.l-committee {
    background: #fffbeb;
    border-color: var(--orange);
    border-style: dashed;
  }
</style>
</head>
<body>
<div class="wrap">

  <div class="page-header">
    <div>
      <h1>안전보건조직도 생성기</h1>
      <div class="sub">SAFETY &amp; HEALTH ORGANIZATION CHART GENERATOR</div>
    </div>
    <div class="brand">
      산업안전지원센터㈜<br>
      safetysupport.co.kr
    </div>
  </div>

  <div class="main">
    <div class="control-panel">

      <div class="section">
        <div class="section-title"><span class="num">1</span> 회사 정보</div>
        <div class="meta-grid">
          <div>
            <label>회사명 (제목 1줄)</label>
            <input type="text" id="company1" placeholder="예: ○○○주식회사" value="○○○주식회사">
          </div>
          <div>
            <label>부제 (제목 2줄)</label>
            <input type="text" id="company2" placeholder="예: 안전보건조직도" value="안전보건조직도">
          </div>
          <div>
            <label>작성일자</label>
            <input type="date" id="docDate">
          </div>
          <div>
            <label>PDF 용지 방향</label>
            <select id="orientation">
              <option value="portrait">A4 세로</option>
              <option value="landscape" selected>A4 가로</option>
            </select>
          </div>
        </div>
      </div>

      <div class="section">
        <div class="section-title"><span class="num">2</span> 라인 조직 (중앙)</div>
        <div id="line-section"></div>
      </div>

      <div class="section">
        <div class="section-title"><span class="num">3</span> 스탭 조직 (우측)</div>
        <div id="staff-section"></div>
      </div>

      <div class="section">
        <div class="section-title"><span class="num">4</span> 도급·회의체 (좌측)</div>
        <div id="contract-section"></div>
      </div>

      <div class="action-bar">
        <button class="btn btn-secondary" onclick="resetAll()">초기화</button>
        <button class="btn btn-primary" id="btn-pdf" onclick="downloadPDF()">PDF 다운로드</button>
      </div>

      <div class="wm-box">
        <div class="wm-title">🔒 워터마크 설정</div>
        <div class="wm-row">
          <input type="password" id="chart-wm-pw" placeholder="비밀번호 입력" onkeydown="if(event.key==='Enter')unlockWatermark()">
          <button class="wm-unlock-btn" onclick="unlockWatermark()">해제</button>
          <button class="wm-lock-btn" onclick="lockWatermark()">잠금</button>
        </div>
        <div class="wm-status" id="chart-wm-status"></div>
      </div>
    </div>

    <div class="preview-wrap">
      <div class="preview-label">실시간 미리보기</div>
      <div class="chart-scaler">
        <div class="chart-scaler-inner" id="chart-scaler-inner">
          <div id="chart-page" class="landscape"></div>
        </div>
      </div>
    </div>
  </div>
</div>

<script>
const BOX_CONFIG = {
  board:             { label: '이사회',               tag: '라인', tagClass: 'line',      boxClass: 'line',      group: 'line',     hasDept: false, hasRole: true, hasName: false, defaultRole: '이사회',             defaultEng: 'Board of Directors', law: '(법 제14조)' },
  employer:          { label: '사업주',               tag: '라인', tagClass: 'line',      boxClass: 'primary',   group: 'line',     hasDept: false, hasRole: true, hasName: true,  defaultRole: '대표이사',           defaultEng: 'Chief Executive Officer', law: '' },
  safetyManager:     { label: '안전보건관리책임자',    tag: '라인', tagClass: 'line',      boxClass: 'line',      group: 'line',     hasDept: false, hasRole: true, hasName: true,  defaultRole: '안전보건관리책임자',  defaultEng: 'Chief Safety and Health Manager', law: '(법 제15조)' },
  supervisor:        { label: '관리감독자 (복수 가능)', tag: '라인', tagClass: 'line',      boxClass: 'line',      group: 'line',     multi: true,    hasDept: true,  hasRole: true, hasName: true,  defaultEng: 'Supervisor', law: '(법 제16조)' },
  worker:            { label: '근로자',               tag: '라인', tagClass: 'line',      boxClass: 'line',      group: 'line',     hasDept: false, hasRole: true, hasName: false, defaultRole: '근로자',             defaultEng: 'Worker', law: '' },
  safetyOfficer:     { label: '안전관리자',           tag: '스탭', tagClass: 'staff',     boxClass: 'staff',     group: 'staff',    hasDept: true,  hasRole: true, hasName: true,  defaultRole: '안전관리자',         defaultEng: 'Safety Officer', law: '(법 제17조)' },
  healthOfficer:     { label: '보건관리자',           tag: '스탭', tagClass: 'staff',     boxClass: 'staff',     group: 'staff',    hasDept: true,  hasRole: true, hasName: true,  defaultRole: '보건관리자',         defaultEng: 'Health Officer', law: '(법 제18조)' },
  safetyHealthStaff: { label: '안전보건관리담당자',    tag: '스탭', tagClass: 'staff',     boxClass: 'staff',     group: 'staff',    hasDept: true,  hasRole: true, hasName: true,  defaultRole: '안전보건관리담당자',  defaultEng: 'Safety and Health Coordinator', law: '(법 제19조)' },
  occupationalDoctor:{ label: '산업보건의',           tag: '스탭', tagClass: 'staff',     boxClass: 'staff',     group: 'staff',    hasDept: true,  hasRole: true, hasName: true,  defaultRole: '산업보건의',         defaultEng: 'Occupational Health Doctor', law: '(법 제22조)' },
  generalManager:    { label: '안전보건총괄책임자',    tag: '도급', tagClass: 'contract',  boxClass: 'contract',  group: 'contract', hasDept: false, hasRole: true, hasName: true,  defaultRole: '안전보건총괄책임자',  defaultEng: 'General Safety and Health Manager', law: '(법 제62조)', note: '도급사업장만 해당 · 안전보건관리책임자가 겸임' },
  contractCommittee: { label: '안전보건협의체',       tag: '도급', tagClass: 'contract',  boxClass: 'committee', group: 'contract', hasDept: false, hasRole: true, hasName: false, defaultRole: '안전보건협의체',      defaultEng: 'Safety and Health Council', law: '(법 제64조)', note: '도급사업장만 해당' },
  safetyCommittee:   { label: '산업안전보건위원회',    tag: '회의', tagClass: 'committee', boxClass: 'committee', group: 'staff',    hasDept: false, hasRole: true, hasName: false, defaultRole: '산업안전보건위원회',  defaultEng: 'Occupational Safety and Health Committee', law: '(법 제24조)' },
};

const state = {};
Object.keys(BOX_CONFIG).forEach(key => {
  const cfg = BOX_CONFIG[key];
  state[key] = {
    enabled: ['employer', 'safetyManager', 'supervisor', 'worker'].includes(key),
    showDept: cfg.hasDept || false,
    showRole: true,
    showName: cfg.hasName || false,
    dept: '',
    role: cfg.defaultRole || '',
    eng: cfg.defaultEng || '',
    name: '',
    supervisors: cfg.multi ? [{ dept: '생산1팀', role: '부장', name: '' }] : null,
  };
});
state.board.enabled = false;

function renderControlsInitial() {
  const lineEl = document.getElementById('line-section');
  const staffEl = document.getElementById('staff-section');
  const contractEl = document.getElementById('contract-section');

  lineEl.innerHTML = '';
  staffEl.innerHTML = '';
  contractEl.innerHTML = '';

  ['board', 'employer', 'safetyManager', 'supervisor', 'worker'].forEach(k => lineEl.appendChild(buildBoxCard(k)));
  ['safetyOfficer', 'healthOfficer', 'safetyHealthStaff', 'occupationalDoctor', 'safetyCommittee'].forEach(k => staffEl.appendChild(buildBoxCard(k)));
  ['generalManager', 'contractCommittee'].forEach(k => contractEl.appendChild(buildBoxCard(k)));
}

function buildBoxCard(key) {
  const cfg = BOX_CONFIG[key];
  const s = state[key];
  const card = document.createElement('div');
  card.className = 'box-card' + (s.enabled ? ' active' : '');
  card.id = `card-${key}`;

  const head = document.createElement('div');
  head.className = 'box-head';
  head.innerHTML = `
    <input type="checkbox" ${s.enabled ? 'checked' : ''} data-key="${key}" data-action="toggle">
    <label>${cfg.label}</label>
    <span class="tag ${cfg.tagClass}">${cfg.tag}</span>
  `;
  card.appendChild(head);

  const fields = document.createElement('div');
  fields.className = 'fields';
  fields.id = `fields-${key}`;

  if (cfg.multi) {
    const list = document.createElement('div');
    list.className = 'supervisor-list';
    list.id = `supervisor-list-${key}`;
    renderSupervisorList(list, key);
    fields.appendChild(list);

    const addBtn = document.createElement('button');
    addBtn.className = 'btn-add';
    addBtn.textContent = '+ 관리감독자 추가';
    addBtn.onclick = () => {
      if (state[key].supervisors.length >= 12) {
        alert('관리감독자는 최대 12명까지만 추가할 수 있습니다');
        return;
      }
      state[key].supervisors.push({ dept: '', role: '', name: '' });
      renderSupervisorList(list, key);
      renderChart();
    };
    fields.appendChild(addBtn);
  } else {
    const checks = document.createElement('div');
    checks.className = 'field-checks';
    const items = [];
    if (cfg.hasDept) items.push(`<label><input type="checkbox" ${s.showDept ? 'checked' : ''} data-key="${key}" data-action="showDept"> 부서명</label>`);
    if (cfg.hasRole) items.push(`<label><input type="checkbox" ${s.showRole ? 'checked' : ''} data-key="${key}" data-action="showRole"> 직책명</label>`);
    if (cfg.hasName) items.push(`<label><input type="checkbox" ${s.showName ? 'checked' : ''} data-key="${key}" data-action="showName"> 인명</label>`);
    checks.innerHTML = items.join('');
    fields.appendChild(checks);

    const inputs = document.createElement('div');
    inputs.className = 'inputs';
    inputs.innerHTML = `
      <input type="text" placeholder="부서명" value="${s.dept}" data-key="${key}" data-field="dept" ${!cfg.hasDept ? 'disabled' : ''}>
      <input type="text" placeholder="직책명" value="${s.role}" data-key="${key}" data-field="role" ${!cfg.hasRole ? 'disabled' : ''}>
      <input type="text" placeholder="인명" value="${s.name}" data-key="${key}" data-field="name" ${!cfg.hasName ? 'disabled' : ''}>
    `;
    fields.appendChild(inputs);
  }

  if (cfg.note) {
    const note = document.createElement('div');
    note.style.cssText = 'font-size:10px;color:var(--gray-500);margin-top:6px;font-style:italic;';
    note.textContent = '※ ' + cfg.note;
    fields.appendChild(note);
  }

  card.appendChild(fields);
  return card;
}

function renderSupervisorList(listEl, key) {
  listEl.innerHTML = '';
  state[key].supervisors.forEach((sup, idx) => {
    const row = document.createElement('div');
    row.className = 'supervisor-item';
    row.innerHTML = `
      <div class="supervisor-inputs">
        <input type="text" placeholder="부서" value="${sup.dept}" data-sup-key="${key}" data-sup-idx="${idx}" data-sup-field="dept">
        <input type="text" placeholder="직책" value="${sup.role}" data-sup-key="${key}" data-sup-idx="${idx}" data-sup-field="role">
        <input type="text" placeholder="인명" value="${sup.name}" data-sup-key="${key}" data-sup-idx="${idx}" data-sup-field="name">
      </div>
      <button class="btn-del" data-sup-key="${key}" data-sup-del="${idx}">×</button>
    `;
    listEl.appendChild(row);
  });
}

function updateCardActive(key) {
  const card = document.getElementById(`card-${key}`);
  if (!card) return;
  if (state[key].enabled) card.classList.add('active');
  else card.classList.remove('active');
}

document.addEventListener('change', (e) => {
  const t = e.target;
  if (t.dataset.action === 'toggle') {
    state[t.dataset.key].enabled = t.checked;
    updateCardActive(t.dataset.key);
    renderChart();
  } else if (t.dataset.action) {
    state[t.dataset.key][t.dataset.action] = t.checked;
    renderChart();
  } else if (t.id === 'orientation') {
    renderChart();
  }
});

document.addEventListener('input', (e) => {
  const t = e.target;
  if (t.dataset.field) {
    state[t.dataset.key][t.dataset.field] = t.value;
    renderChart();
  } else if (t.dataset.supField) {
    state[t.dataset.supKey].supervisors[+t.dataset.supIdx][t.dataset.supField] = t.value;
    renderChart();
  } else if (t.id === 'company1' || t.id === 'company2' || t.id === 'docDate') {
    renderChart();
  }
});

document.addEventListener('click', (e) => {
  const t = e.target;
  if (t.dataset.supDel !== undefined) {
    const key = t.dataset.supKey;
    state[key].supervisors.splice(+t.dataset.supDel, 1);
    if (state[key].supervisors.length === 0) {
      state[key].supervisors.push({ dept: '', role: '', name: '' });
    }
    const list = document.getElementById(`supervisor-list-${key}`);
    renderSupervisorList(list, key);
    renderChart();
  }
});

function makeBoxHTML(key, data) {
  const cfg = BOX_CONFIG[key];
  const s = state[key];
  const parts = [];

  if (data) {
    if (s.showDept && data.dept) parts.push(`<div class="dept">${escapeHtml(data.dept)}</div>`);
    if (s.showRole && data.role) parts.push(`<div class="role">${escapeHtml(data.role)}</div>`);
    if (cfg.defaultEng) parts.push(`<div class="eng">${escapeHtml(cfg.defaultEng)}</div>`);
    if (cfg.law) parts.push(`<div class="law">${escapeHtml(cfg.law)}</div>`);
    if (s.showName && data.name) parts.push(`<div class="name">${escapeHtml(data.name)}</div>`);
    if (parts.length === 0) parts.push(`<div class="role">관리감독자</div>`);
  } else {
    if (cfg.hasDept && s.showDept && s.dept) parts.push(`<div class="dept">${escapeHtml(s.dept)}</div>`);
    if (cfg.hasRole && s.showRole && s.role) parts.push(`<div class="role">${escapeHtml(s.role)}</div>`);
    if (s.eng) parts.push(`<div class="eng">${escapeHtml(s.eng)}</div>`);
    if (cfg.law) parts.push(`<div class="law">${escapeHtml(cfg.law)}</div>`);
    if (cfg.hasName && s.showName && s.name) parts.push(`<div class="name">${escapeHtml(s.name)}</div>`);
    if (parts.length === 0) parts.push(`<div class="role">${escapeHtml(cfg.label.split(' ')[0])}</div>`);
  }

  return parts.join('');
}

function escapeHtml(s) {
  return String(s).replace(/[&<>"']/g, m => ({
    '&':'&amp;',
    '<':'&lt;',
    '>':'&gt;',
    '"':'&quot;',
    "'":'&#39;'
  }[m]));
}


const WM_PW = 'ossc02854';
let chartWmOn = true;

function setWmStatus(unlocked) {
  const el = document.getElementById('chart-wm-status');
  if (!el) return;
  if (unlocked) {
    el.className = 'wm-status unlocked';
    el.textContent = '✓ 워터마크가 해제되었습니다';
  } else {
    el.className = 'wm-status locked';
    el.textContent = '🔒 워터마크가 적용 중입니다';
  }
}

function unlockWatermark() {
  const pwEl = document.getElementById('chart-wm-pw');
  const pw = pwEl ? pwEl.value : '';
  const el = document.getElementById('chart-wm-status');
  if (pw === WM_PW) {
    chartWmOn = false;
    if (pwEl) pwEl.value = '';
    setWmStatus(true);
    renderChart();
  } else if (el) {
    el.className = 'wm-status locked';
    el.textContent = '✗ 비밀번호가 올바르지 않습니다';
  }
}

function lockWatermark() {
  chartWmOn = true;
  const pwEl = document.getElementById('chart-wm-pw');
  if (pwEl) pwEl.value = '';
  setWmStatus(false);
  renderChart();
}

function renderChart() {
  const page = document.getElementById('chart-page');
  page.className = document.getElementById('orientation').value;
  const isLandscape = page.classList.contains('landscape');

  const company1 = document.getElementById('company1').value || '○○○주식회사';
  const company2 = document.getElementById('company2').value || '안전보건조직도';
  const date = document.getElementById('docDate').value;
  const dateStr = date ? formatDate(date) : '';

  const DEFAULT_BOX_W = 180;
  const DEFAULT_BOX_H = 84;
  const V_GAP = 34;
  const ROW_H = DEFAULT_BOX_H + V_GAP;

  // 패딩이 24px로 줄었으므로 가용 폭 재계산
  // landscape: 1123 - 48 = 1075, portrait: 794 - 48 = 746
 // 패딩이 16px로 줄었으므로 가용 폭 재계산
  // landscape: 1123 - 32 = 1091, portrait: 794 - 32 = 762
  const PAGE_INNER_W = isLandscape ? 1091 : 762;

  // 박스 폭 절반(90px) + 여유(4px) = 94px를 양끝에서 확보
  const COL_X = {
    contract: 94,
    center: PAGE_INNER_W / 2,
    staff: PAGE_INNER_W - 94,
  };

  const centerBoxX = COL_X.center - DEFAULT_BOX_W / 2;
  const staffBoxX = COL_X.staff - DEFAULT_BOX_W / 2;
  const contractBoxX = COL_X.contract - DEFAULT_BOX_W / 2;

  const pos = {};
  const Y_START = 40;
  let topY = Y_START;

  const SUPERVISOR_GAP_Y = 85;      // 안전보건관리책임자 ↔ 관리감독자 간격
  const WORKER_GAP_Y = 30;          // 관리감독자 ↔ 근로자 간격
  const LINE_BUS_OFFSET_Y = 48;     // 안전보건관리책임자 아래 분기선 높이
  const STAFF_BRANCH_GAP = 16;      // 분기선과 스탭 가로선 간격
  const COMMITTEE_LINE_GAP = 40;    // 스탭 가로선 바로 위로 위원회선 배치
  const STAFF_START_OFFSET_Y = 28;  // 첫 번째 스탭 박스 시작 높이
  const STAFF_ROW_GAP = 18;         // 스탭 박스들 사이 간격

  if (state.board.enabled) {
    pos.board = { x: centerBoxX, y: topY, w: DEFAULT_BOX_W, h: DEFAULT_BOX_H };
    topY += ROW_H;
  }

  if (state.employer.enabled) {
    pos.employer = { x: centerBoxX, y: topY, w: DEFAULT_BOX_W, h: DEFAULT_BOX_H };
    topY += ROW_H;
  }

  if (state.safetyManager.enabled) {
    pos.safetyManager = { x: centerBoxX, y: topY, w: DEFAULT_BOX_W, h: DEFAULT_BOX_H };
  }

  const staffActive = ['safetyOfficer', 'healthOfficer', 'safetyHealthStaff', 'occupationalDoctor'].filter(k => state[k].enabled);
  const committeeActive = state.safetyCommittee.enabled;
  const totalResp = state.generalManager.enabled;
  const contractComm = state.contractCommittee.enabled;

  const supervisors = state.supervisor.enabled ? state.supervisor.supervisors : [];
  const supCount = supervisors.length;

  const baseAnchor = pos.safetyManager || pos.employer || pos.board;
  const managerBottomY = baseAnchor ? (baseAnchor.y + baseAnchor.h) : (Y_START + DEFAULT_BOX_H);
  const lineBusY = managerBottomY + LINE_BUS_OFFSET_Y;
  const staffBranchY = lineBusY - STAFF_BRANCH_GAP;
  // 산업안전보건위원회는 안전보건관리책임자와 관리감독자 사이에 두되,
  // 스탭 연결선보다 약간 위에 위치하도록 설정
  const committeeLineY = staffBranchY - COMMITTEE_LINE_GAP;

  pos.lineBusY = lineBusY;
  pos.staffBranchY = staffBranchY;
  pos.committeeLineY = committeeLineY;

  if (state.supervisor.enabled && supCount > 0) {
    const supY = managerBottomY + SUPERVISOR_GAP_Y;

    const SUP_AREA_LEFT = contractBoxX + DEFAULT_BOX_W + 38;
    const SUP_AREA_RIGHT = staffBoxX - 40;
    const SUP_AREA_WIDTH = SUP_AREA_RIGHT - SUP_AREA_LEFT;

    const SUP_GAP = 10;
    const SUP_MIN_W = 88;
    const SUP_MAX_W = DEFAULT_BOX_W;
    const FIRST_ROW_LIMIT = 6;
    const SECOND_ROW_GAP_Y = 26;
    const SECOND_BLOCK_GAP_Y = 26;

    const firstRowCount = Math.min(FIRST_ROW_LIMIT, supCount);
    const secondRowCount = Math.max(0, supCount - FIRST_ROW_LIMIT);

    let supervisorBoxW = SUP_MAX_W;
    if (supCount <= FIRST_ROW_LIMIT) {
      supervisorBoxW = Math.floor((SUP_AREA_WIDTH - (firstRowCount - 1) * SUP_GAP) / firstRowCount);
    } else {
      supervisorBoxW = Math.floor((SUP_AREA_WIDTH - (FIRST_ROW_LIMIT - 1) * SUP_GAP) / FIRST_ROW_LIMIT);
    }
    supervisorBoxW = Math.max(SUP_MIN_W, Math.min(SUP_MAX_W, supervisorBoxW));

    const firstRowTotalW = firstRowCount * supervisorBoxW + (firstRowCount - 1) * SUP_GAP;

    let firstRowStartX = COL_X.center - firstRowTotalW / 2;
    if (firstRowStartX < SUP_AREA_LEFT) firstRowStartX = SUP_AREA_LEFT;
    if (firstRowStartX + firstRowTotalW > SUP_AREA_RIGHT) firstRowStartX = SUP_AREA_RIGHT - firstRowTotalW;

    const workerRow1Y = supY + DEFAULT_BOX_H + WORKER_GAP_Y;
    const supRow2Y = workerRow1Y + DEFAULT_BOX_H + SECOND_BLOCK_GAP_Y;
    const workerRow2Y = supRow2Y + DEFAULT_BOX_H + WORKER_GAP_Y;

    pos.supervisors = [];
    pos.workers = [];

    for (let i = 0; i < firstRowCount; i++) {
      const x = firstRowStartX + i * (supervisorBoxW + SUP_GAP);
      pos.supervisors.push({
        x,
        y: supY,
        w: supervisorBoxW,
        h: DEFAULT_BOX_H,
        row: 1,
        col: i
      });
      pos.workers.push({
        x,
        y: workerRow1Y,
        w: supervisorBoxW,
        h: DEFAULT_BOX_H,
        row: 1,
        col: i
      });
    }

    for (let i = 0; i < secondRowCount; i++) {
      const x = firstRowStartX + i * (supervisorBoxW + SUP_GAP);
      pos.supervisors.push({
        x,
        y: supRow2Y,
        w: supervisorBoxW,
        h: DEFAULT_BOX_H,
        row: 2,
        col: i
      });
      pos.workers.push({
        x,
        y: workerRow2Y,
        w: supervisorBoxW,
        h: DEFAULT_BOX_H,
        row: 2,
        col: i
      });
    }
  }

  if (committeeActive) {
    pos.safetyCommittee = {
      x: staffBoxX,
      y: committeeLineY - DEFAULT_BOX_H / 2,
      w: DEFAULT_BOX_W,
      h: DEFAULT_BOX_H
    };
  }

  const staffStepY = DEFAULT_BOX_H + STAFF_ROW_GAP;
  const staffStartY = staffBranchY + STAFF_START_OFFSET_Y;

  pos.staff = {};
  staffActive.forEach((k, i) => {
    pos.staff[k] = {
      x: staffBoxX,
      y: staffStartY + i * staffStepY,
      w: DEFAULT_BOX_W,
      h: DEFAULT_BOX_H
    };
  });

  if (totalResp && pos.safetyManager) {
    pos.generalManager = {
      x: contractBoxX,
      y: pos.safetyManager.y,
      w: DEFAULT_BOX_W,
      h: DEFAULT_BOX_H
    };
  } else if (totalResp && pos.employer) {
    pos.generalManager = {
      x: contractBoxX,
      y: pos.employer.y + ROW_H,
      w: DEFAULT_BOX_W,
      h: DEFAULT_BOX_H
    };
  }

  if (contractComm) {
    if (pos.generalManager) {
      pos.contractCommittee = {
        x: contractBoxX,
        y: pos.generalManager.y + ROW_H,
        w: DEFAULT_BOX_W,
        h: DEFAULT_BOX_H
      };
    } else if (pos.supervisors) {
      pos.contractCommittee = {
        x: contractBoxX,
        y: pos.supervisors[0].y,
        w: DEFAULT_BOX_W,
        h: DEFAULT_BOX_H
      };
    } else if (pos.safetyManager) {
      pos.contractCommittee = {
        x: contractBoxX,
        y: pos.safetyManager.y + ROW_H,
        w: DEFAULT_BOX_W,
        h: DEFAULT_BOX_H
      };
    }
  }

  let maxY = 0;
  [
    pos.board,
    pos.employer,
    pos.safetyManager,
    ...(pos.supervisors || []),
    ...(pos.workers || []),
    ...(pos.safetyCommittee ? [pos.safetyCommittee] : []),
    ...Object.values(pos.staff || {}),
    ...(pos.generalManager ? [pos.generalManager] : []),
    ...(pos.contractCommittee ? [pos.contractCommittee] : [])
  ].forEach(p => {
    if (p) maxY = Math.max(maxY, p.y + p.h);
  });
  maxY += 20;

  const chartWidth = PAGE_INNER_W;
  const chartHeight = maxY;

  page.innerHTML = `
    <div class="doc-header">
      <div class="doc-title-area">
        <div class="doc-title">${escapeHtml(company1)}</div>
        <div class="doc-subtitle">${escapeHtml(company2)}</div>
      </div>
      <div class="doc-meta">
        <span class="label">작성일자</span>${dateStr}
      </div>
    </div>
    ${chartWmOn ? `<div class="page-watermark"><span>산업안전지원센터㈜</span></div>` : ''}
    <div class="chart-container">
      <div class="chart-svg-wrap" style="height:${chartHeight}px;">
        <svg class="chart-svg" viewBox="0 0 ${chartWidth} ${chartHeight}" preserveAspectRatio="xMidYMin meet">
          ${buildConnectors(pos)}
        </svg>
        <div class="chart-boxes" style="height:${chartHeight}px; position:relative;">
          ${buildBoxes(pos, supervisors)}
        </div>
      </div>
    </div>
    <div class="legend">
      <div class="legend-item"><div class="legend-color l-line"></div>라인 조직</div>
      <div class="legend-item"><div class="legend-color l-staff"></div>스탭 조직</div>
      <div class="legend-item"><div class="legend-color l-contract"></div>도급 관련</div>
      <div class="legend-item"><div class="legend-color l-committee"></div>회의체</div>
    </div>
  `;

  updatePreviewScale();
  setWmStatus(!chartWmOn);
}

function buildBoxes(pos, supervisors) {
  const DEFAULT_BOX_W = 170;
  const DEFAULT_BOX_H = 84;
  const html = [];

  const boxStyle = (p) => `
    left:${p.x}px;
    top:${p.y}px;
    width:${p.w || DEFAULT_BOX_W}px;
    height:${p.h || DEFAULT_BOX_H}px;
  `;

  if (pos.board) html.push(`<div class="org-box ${BOX_CONFIG.board.boxClass}" style="${boxStyle(pos.board)}">${makeBoxHTML('board')}</div>`);
  if (pos.employer) html.push(`<div class="org-box ${BOX_CONFIG.employer.boxClass}" style="${boxStyle(pos.employer)}">${makeBoxHTML('employer')}</div>`);
  if (pos.safetyManager) html.push(`<div class="org-box ${BOX_CONFIG.safetyManager.boxClass}" style="${boxStyle(pos.safetyManager)}">${makeBoxHTML('safetyManager')}</div>`);

  if (pos.supervisors) {
    const sups = supervisors.length > 0 ? supervisors : [{ dept: '', role: '관리감독자', name: '' }];
    pos.supervisors.forEach((p, i) => {
      const data = sups[i] || { dept: '', role: '관리감독자', name: '' };
      html.push(`<div class="org-box ${BOX_CONFIG.supervisor.boxClass}" style="${boxStyle(p)}">${makeBoxHTML('supervisor', data)}</div>`);
    });
  }

  if (pos.workers) {
    pos.workers.forEach(p => {
      html.push(`<div class="org-box ${BOX_CONFIG.worker.boxClass}" style="${boxStyle(p)}">${makeBoxHTML('worker')}</div>`);
    });
  }

  if (pos.staff) {
    Object.keys(pos.staff).forEach(k => {
      html.push(`<div class="org-box ${BOX_CONFIG[k].boxClass}" style="${boxStyle(pos.staff[k])}">${makeBoxHTML(k)}</div>`);
    });
  }

  if (pos.safetyCommittee) html.push(`<div class="org-box ${BOX_CONFIG.safetyCommittee.boxClass}" style="${boxStyle(pos.safetyCommittee)}">${makeBoxHTML('safetyCommittee')}</div>`);
  if (pos.generalManager) html.push(`<div class="org-box ${BOX_CONFIG.generalManager.boxClass}" style="${boxStyle(pos.generalManager)}">${makeBoxHTML('generalManager')}</div>`);
  if (pos.contractCommittee) html.push(`<div class="org-box ${BOX_CONFIG.contractCommittee.boxClass}" style="${boxStyle(pos.contractCommittee)}">${makeBoxHTML('contractCommittee')}</div>`);

  return html.join('');
}

function buildConnectors(pos) {
  const lines = [];
  const STROKE = 'stroke="#374151" stroke-width="1.8" fill="none"';

  const pw = (p) => p.w || 170;
  const ph = (p) => p.h || 84;

  const cx = (p) => p.x + pw(p) / 2;
  const top = (p) => p.y;
  const bottom = (p) => p.y + ph(p);
  const left = (p) => p.x;
  const right = (p) => p.x + pw(p);
  const midY = (p) => p.y + ph(p) / 2;

  if (pos.board && pos.employer) {
    lines.push(`<line x1="${cx(pos.board)}" y1="${bottom(pos.board)}" x2="${cx(pos.employer)}" y2="${top(pos.employer)}" ${STROKE}/>`);
  } else if (pos.board && pos.safetyManager && !pos.employer) {
    lines.push(`<line x1="${cx(pos.board)}" y1="${bottom(pos.board)}" x2="${cx(pos.safetyManager)}" y2="${top(pos.safetyManager)}" ${STROKE}/>`);
  }

  if (pos.employer && pos.safetyManager) {
    lines.push(`<line x1="${cx(pos.employer)}" y1="${bottom(pos.employer)}" x2="${cx(pos.safetyManager)}" y2="${top(pos.safetyManager)}" ${STROKE}/>`);
  }

  if (pos.safetyManager && pos.supervisors && pos.supervisors.length > 0) {
    const midBusY = pos.lineBusY;
    const firstRowSupers = pos.supervisors.filter(sp => (sp.row || 1) === 1);
    const busStartX = cx(firstRowSupers[0]);
    const busEndX = cx(firstRowSupers[firstRowSupers.length - 1]);
    const centerGuideX = cx(pos.safetyManager);

    lines.push(`<line x1="${centerGuideX}" y1="${bottom(pos.safetyManager)}" x2="${centerGuideX}" y2="${midBusY}" ${STROKE}/>`);
    lines.push(`<line x1="${busStartX}" y1="${midBusY}" x2="${busEndX}" y2="${midBusY}" ${STROKE}/>`);
    firstRowSupers.forEach(sp => {
      lines.push(`<line x1="${cx(sp)}" y1="${midBusY}" x2="${cx(sp)}" y2="${top(sp)}" ${STROKE}/>`);
    });
  }

  if (pos.supervisors && pos.workers) {
    pos.supervisors.forEach((sp, i) => {
      const wk = pos.workers[i];
      if (wk) {
        lines.push(`<line x1="${cx(sp)}" y1="${bottom(sp)}" x2="${cx(wk)}" y2="${top(wk)}" ${STROKE}/>`);
      }
    });
  }

  if (pos.safetyManager && pos.generalManager) {
    const y = midY(pos.safetyManager);
    lines.push(`<line x1="${right(pos.generalManager)}" y1="${y}" x2="${left(pos.safetyManager)}" y2="${y}" ${STROKE} stroke-dasharray="4,3"/>`);
  }

  if (pos.safetyManager && pos.safetyCommittee) {
  const committeeY = pos.committeeLineY;          // 위원회 현재 높이
  const anchorY = pos.staffBranchY - 16;           // 중앙 세로선에 붙는 높이 (스탭선 바로 위)
  const trunkX = cx(pos.safetyManager);           // 중앙 세로선 X
  const endX = left(pos.safetyCommittee);         // 위원회 박스 왼쪽
  const elbowX = endX - 18;                       // 위원회 바로 왼쪽에서 아래로 꺾이기

  // 1) 중앙 세로선에서 오른쪽으로 (스탭선 바로 위 높이)
  lines.push(`<line x1="${trunkX}" y1="${anchorY}" x2="${elbowX}" y2="${anchorY}" ${STROKE} stroke-dasharray="4,3"/>`);

  // 2) 거기서 위원회 높이까지 위로/아래로 수직 꺾임
  lines.push(`<line x1="${elbowX}" y1="${anchorY}" x2="${elbowX}" y2="${committeeY}" ${STROKE} stroke-dasharray="4,3"/>`);

  // 3) 마지막으로 위원회 박스까지 수평 연결
  lines.push(`<line x1="${elbowX}" y1="${committeeY}" x2="${endX}" y2="${committeeY}" ${STROKE} stroke-dasharray="4,3"/>`);
}

  if (pos.contractCommittee) {
    if (pos.generalManager) {
      lines.push(`<line x1="${cx(pos.contractCommittee)}" y1="${top(pos.contractCommittee)}" x2="${cx(pos.generalManager)}" y2="${bottom(pos.generalManager)}" ${STROKE} stroke-dasharray="4,3"/>`);
    } else if (pos.safetyManager) {
      const y = midY(pos.contractCommittee);
      lines.push(`<line x1="${right(pos.contractCommittee)}" y1="${y}" x2="${left(pos.safetyManager)}" y2="${y}" ${STROKE} stroke-dasharray="4,3"/>`);
    }
  }

  if (pos.staff && Object.keys(pos.staff).length > 0 && pos.safetyManager) {
    const staffKeys = Object.keys(pos.staff);
    const firstStaff = pos.staff[staffKeys[0]];
    const lastStaff = pos.staff[staffKeys[staffKeys.length - 1]];

    const branchY = pos.staffBranchY;
    const busX = left(firstStaff) - 16;
    const busEndY = midY(lastStaff);

    lines.push(`<line x1="${cx(pos.safetyManager)}" y1="${branchY}" x2="${busX}" y2="${branchY}" ${STROKE}/>`);
    lines.push(`<line x1="${busX}" y1="${branchY}" x2="${busX}" y2="${busEndY}" ${STROKE}/>`);
    staffKeys.forEach(k => {
      const sp = pos.staff[k];
      lines.push(`<line x1="${busX}" y1="${midY(sp)}" x2="${left(sp)}" y2="${midY(sp)}" ${STROKE}/>`);
    });
  }

  return lines.join('');
}

function updatePreviewScale() {
  const inner = document.getElementById('chart-scaler-inner');
  const page = document.getElementById('chart-page');
  if (!inner || !page) return;

  const previewWrap = inner.closest('.preview-wrap');
  if (!previewWrap) return;

  const availableW = previewWrap.clientWidth - 40;
  const pageW = page.classList.contains('landscape') ? 1123 : 794;

  if (availableW < pageW) {
    const scale = availableW / pageW;
    inner.style.transform = `scale(${scale})`;
    const pageH = page.offsetHeight;
    inner.style.height = (pageH * scale) + 'px';
    inner.style.width = pageW + 'px';
  } else {
    inner.style.transform = 'none';
    inner.style.height = 'auto';
    inner.style.width = 'auto';
  }
}

window.addEventListener('resize', () => {
  clearTimeout(window._resizeTimer);
  window._resizeTimer = setTimeout(updatePreviewScale, 100);
});

function formatDate(d) {
  if (!d) return '';
  const [y, m, day] = d.split('-');
  return `${y}. ${String(parseInt(m)).padStart(2,'0')}. ${String(parseInt(day)).padStart(2,'0')}.`;
}

async function downloadPDF() {
  const page = document.getElementById('chart-page');
  const inner = document.getElementById('chart-scaler-inner');
  const orientation = document.getElementById('orientation').value;
  const btn = document.getElementById('btn-pdf');
  const originalText = btn.textContent;

  btn.textContent = 'PDF 생성 중...';
  btn.disabled = true;

  const originalTransform = inner.style.transform;
  const originalInnerH = inner.style.height;
  const originalInnerW = inner.style.width;

  inner.style.transform = 'none';
  inner.style.height = 'auto';
  inner.style.width = 'auto';

  try {
    await new Promise(r => setTimeout(r, 50));

    const canvas = await html2canvas(page, {
      scale: 2,
      backgroundColor: '#ffffff',
      useCORS: true,
    });

    const imgData = canvas.toDataURL('image/png');
    const { jsPDF } = window.jspdf;
    const pdf = new jsPDF({ orientation, unit: 'mm', format: 'a4' });

    const pdfWidth = pdf.internal.pageSize.getWidth();
    const pdfHeight = pdf.internal.pageSize.getHeight();

    // A4 용지 가장자리에 5mm 최소 여백만 두고 꽉 채우기
    const margin = 3;
    const availW = pdfWidth - margin * 2;
    const availH = pdfHeight - margin * 2;

    const imgRatio = canvas.width / canvas.height;
    const availRatio = availW / availH;

    let w, h;
    if (imgRatio > availRatio) {
      // 가로가 더 긴 이미지: 가로 기준으로 맞춤
      w = availW;
      h = availW / imgRatio;
    } else {
      // 세로가 더 긴 이미지: 세로 기준으로 맞춤
      h = availH;
      w = availH * imgRatio;
    }

    const x = (pdfWidth - w) / 2;
    const y = (pdfHeight - h) / 2;

    pdf.addImage(imgData, 'PNG', x, y, w, h);

    const company = document.getElementById('company1').value || '안전보건조직도';
    pdf.save(`${company}_안전보건조직도.pdf`);
  } catch (err) {
    alert('PDF 생성 중 오류가 발생했습니다: ' + err.message);
  } finally {
    inner.style.transform = originalTransform;
    inner.style.height = originalInnerH;
    inner.style.width = originalInnerW;
    btn.textContent = originalText;
    btn.disabled = false;
  }
}

function resetAll() {
  if (!confirm('입력한 모든 내용을 초기화하시겠습니까?')) return;

  Object.keys(BOX_CONFIG).forEach(key => {
    const cfg = BOX_CONFIG[key];
    state[key] = {
      enabled: ['employer', 'safetyManager', 'supervisor', 'worker'].includes(key),
      showDept: cfg.hasDept || false,
      showRole: true,
      showName: cfg.hasName || false,
      dept: '',
      role: cfg.defaultRole || '',
      eng: cfg.defaultEng || '',
      name: '',
      supervisors: cfg.multi ? [{ dept: '생산1팀', role: '부장', name: '' }] : null,
    };
  });

  state.board.enabled = false;
  chartWmOn = true;

  document.getElementById('company1').value = '○○○주식회사';
  document.getElementById('company2').value = '안전보건조직도';
  document.getElementById('docDate').value = new Date().toISOString().split('T')[0];

  renderControlsInitial();
  renderChart();
}

document.getElementById('docDate').value = new Date().toISOString().split('T')[0];
renderControlsInitial();
renderChart();
setWmStatus(false);
</script>
<!-- ═══════════════════════ 플로팅 네비게이션 ═══════════════════════ -->
<style>
.smart-float-nav{
  position:fixed;
  right:22px;
  top:50%;
  transform:translateY(-50%);
  z-index:9999;
  display:flex;
  flex-direction:column;
  gap:10px;
  padding:10px 8px;
  border-radius:24px;
  background:rgba(255,255,255,0.88);
  border:1px solid rgba(13,51,33,0.12);
  box-shadow:0 12px 32px rgba(13,51,33,0.14);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
}
.smart-float-btn{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:center;
  width:52px;
  height:52px;
  border:none;
  border-radius:18px;
  background:transparent;
  color:#0D3321;
  text-decoration:none;
  cursor:pointer;
  transition:all .22s ease;
  overflow:visible;
}
.smart-float-btn:hover{
  background:#0D3321;
  color:#fff;
  transform:translateX(-2px);
  box-shadow:0 8px 18px rgba(13,51,33,.22);
}
.smart-float-btn svg{
  width:22px;
  height:22px;
  stroke:currentColor;
  flex-shrink:0;
  transition:transform .22s ease;
}
.smart-float-btn:hover svg{
  transform:scale(1.05);
}
.smart-float-label{
  position:absolute;
  right:62px;
  top:50%;
  transform:translateY(-50%) translateX(6px);
  opacity:0;
  pointer-events:none;
  white-space:nowrap;
  padding:9px 12px;
  border-radius:12px;
  background:#0D3321;
  color:#fff;
  font-size:12.5px;
  font-weight:700;
  letter-spacing:-0.01em;
  box-shadow:0 8px 20px rgba(13,51,33,.22);
  transition:all .18s ease;
  font-family:'NanumSquareNeo','Noto Sans KR',sans-serif;
}
.smart-float-label::after{
  content:'';
  position:absolute;
  left:100%;
  top:50%;
  transform:translateY(-50%);
  border-left:6px solid #0D3321;
  border-top:5px solid transparent;
  border-bottom:5px solid transparent;
}
.smart-float-btn:hover .smart-float-label{
  opacity:1;
  transform:translateY(-50%) translateX(0);
}
.smart-float-divider{
  width:28px;
  height:1px;
  margin:1px auto;
  background:linear-gradient(to right, rgba(13,51,33,0), rgba(13,51,33,.18), rgba(13,51,33,0));
}
@media (max-width:768px){
  .smart-float-nav{
    right:12px;
    gap:8px;
    padding:8px 6px;
    border-radius:20px;
  }
  .smart-float-btn{
    width:42px;
    height:42px;
    border-radius:14px;
  }
  .smart-float-btn svg{
    width:18px;
    height:18px;
  }
  .smart-float-label{
    display:none;
  }
}
</style>

<div class="smart-float-nav">
  <a class="smart-float-btn" href="https://safetysupport.co.kr/appointment-form-index/">
    <span class="smart-float-label">서식 목록으로</span>
    <svg viewBox="0 0 24 24" fill="none" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
      <line x1="8" y1="6" x2="21" y2="6"/>
      <line x1="8" y1="12" x2="21" y2="12"/>
      <line x1="8" y1="18" x2="21" y2="18"/>
      <line x1="3" y1="6" x2="3.01" y2="6"/>
      <line x1="3" y1="12" x2="3.01" y2="12"/>
      <line x1="3" y1="18" x2="3.01" y2="18"/>
    </svg>
  </a>
  <div class="smart-float-divider"></div>
  <button class="smart-float-btn" onclick="window.scrollTo({top:0,behavior:'smooth'})">
    <span class="smart-float-label">맨 위로</span>
    <svg viewBox="0 0 24 24" fill="none" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
      <polyline points="18 15 12 9 6 15"/>
    </svg>
  </button>
  <button class="smart-float-btn" onclick="window.scrollTo({top:document.body.scrollHeight,behavior:'smooth'})">
    <span class="smart-float-label">맨 아래로</span>
    <svg viewBox="0 0 24 24" fill="none" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
      <polyline points="6 9 12 15 18 9"/>
    </svg>
  </button>
</div>
</body>
</html>				</div>
				<div class="elementor-element elementor-element-ea27f7a elementor-widget elementor-widget-html" data-id="ea27f7a" data-element_type="widget" data-e-type="widget" data-widget_type="html.default">
					<div style="border:1px solid #b2cae0; border-radius:8px; padding:8px; overflow:hidden; background:#fff;">
  <!-- safetysupport_콘텐츠하단_반응형 -->
  <ins class="adsbygoogle"
       style="display:block"
       data-ad-client="ca-pub-3106752057307696"
       data-ad-slot="5220315587"
       data-ad-format="auto"
       data-full-width-responsive="true"></ins>
  <script>
    (adsbygoogle = window.adsbygoogle || []).push({});
  </script>
</div>				</div>
					</div>
				</div>
				</div>
		]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
