Files
citrus/resources/views/proposals/show.blade.php
T

1480 lines
83 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="tr" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=device-width, initial-scale=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $proposal->title }} - Trunçgil Teknoloji</title>
<!-- Google Fonts: Inter (Body), Outfit (Headings), Caveat (Signature Script) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
signature: ['Caveat', 'cursive'],
},
colors: {
theme: {
indigo: '#4f46e5',
emerald: '#059669',
rose: '#f43f5e',
orange: '#ea580c',
amber: '#d97706',
}
}
}
}
}
</script>
<!-- Styles for interactive elements -->
<style type="text/css">
/* Glassmorphism custom styles */
.glass-header {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.dark .glass-header {
background: rgba(15, 23, 42, 0.7);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.glass-panel {
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.dark .glass-panel {
background: rgba(30, 41, 59, 0.4);
border: 1px solid rgba(255, 255, 255, 0.05);
}
/* Active TOC indicators */
.toc-item.active {
color: var(--accent-color);
font-weight: 600;
}
.toc-item.active .toc-bullet {
transform: scale(1.3);
background-color: var(--accent-color);
box-shadow: 0 0 8px var(--accent-color-glow);
}
/* Rendered Markdown Styling */
.proposal-content h1 {
font-family: 'Outfit', sans-serif;
font-size: 2.25rem;
font-weight: 800;
color: #0f172a;
margin-top: 2rem;
margin-bottom: 1rem;
line-height: 1.2;
}
.dark .proposal-content h1 { color: #f8fafc; }
.proposal-content h2 {
font-family: 'Outfit', sans-serif;
font-size: 1.6rem;
font-weight: 700;
color: #1e293b;
margin-top: 2.5rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
line-height: 1.3;
}
.dark .proposal-content h2 {
color: #f1f5f9;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.proposal-content h3 {
font-family: 'Outfit', sans-serif;
font-size: 1.25rem;
font-weight: 600;
color: #334155;
margin-top: 1.8rem;
margin-bottom: 0.75rem;
}
.dark .proposal-content h3 { color: #e2e8f0; }
.proposal-content p {
font-size: 1rem;
line-height: 1.7;
color: #475569;
margin-bottom: 1.25rem;
}
.dark .proposal-content p { color: #94a3b8; }
/* Tables inside Markdown styling */
.proposal-content table {
width: 100%;
margin: 2rem 0;
border-collapse: collapse;
font-size: 0.925rem;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
}
.dark .proposal-content table {
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: none;
}
.proposal-content th {
background-color: #f8fafc;
color: #0f172a;
font-weight: 600;
padding: 12px 16px;
text-align: left;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
}
.dark .proposal-content th {
background-color: #1e293b;
color: #f8fafc;
border-bottom: 2px solid rgba(255, 255, 255, 0.05);
}
.proposal-content td {
padding: 14px 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
color: #475569;
}
.dark .proposal-content td {
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
color: #cbd5e1;
}
.proposal-content tr:last-child td {
border-bottom: none;
}
.proposal-content tr:nth-child(even) td {
background-color: rgba(248, 250, 252, 0.4);
}
.dark .proposal-content tr:nth-child(even) td {
background-color: rgba(30, 41, 59, 0.2);
}
/* Mermaid container custom styled */
.mermaid-container {
background: #ffffff;
border-radius: 16px;
margin: 2rem 0;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05);
overflow: hidden;
position: relative;
}
.dark .mermaid-container {
background: #0f172a;
border: 1px solid rgba(255, 255, 255, 0.05);
box-shadow: none;
}
/* Mermaid zoom toolbar */
.mermaid-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
background: rgba(248, 250, 252, 0.9);
backdrop-filter: blur(8px);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px 16px 0 0;
}
.dark .mermaid-toolbar {
background: rgba(15, 23, 42, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.mermaid-toolbar-label {
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #94a3b8;
margin-right: 4px;
flex: 1;
}
.mermaid-zoom-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.08);
background: #fff;
color: #475569;
cursor: pointer;
font-size: 15px;
transition: all 0.15s ease;
user-select: none;
flex-shrink: 0;
}
.dark .mermaid-zoom-btn {
background: #1e293b;
border-color: rgba(255, 255, 255, 0.08);
color: #94a3b8;
}
.mermaid-zoom-btn:hover {
background: var(--accent-color, #4f46e5);
border-color: var(--accent-color, #4f46e5);
color: #fff;
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.3);
}
.mermaid-zoom-btn:active {
transform: scale(0.95);
}
.mermaid-zoom-pct {
font-size: 0.7rem;
font-weight: 700;
color: #64748b;
min-width: 38px;
text-align: center;
font-family: 'Inter', monospace;
}
.dark .mermaid-zoom-pct { color: #64748b; }
/* Mermaid svg canvas area */
.mermaid-canvas {
width: 100%;
height: 520px;
cursor: grab;
position: relative;
overflow: hidden;
}
.mermaid-canvas:active { cursor: grabbing; }
.mermaid-canvas .mermaid {
width: 100%;
height: 100%;
}
.mermaid-canvas svg {
display: block;
}
/* Fullscreen overlay */
.mermaid-fullscreen-overlay {
display: none;
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(2, 6, 23, 0.92);
backdrop-filter: blur(12px);
flex-direction: column;
align-items: center;
justify-content: center;
}
.mermaid-fullscreen-overlay.active {
display: flex;
}
.mermaid-fullscreen-inner {
background: #fff;
border-radius: 20px;
overflow: hidden;
width: 92vw;
max-width: 1400px;
height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 40px 80px rgba(0,0,0,0.5);
}
.dark .mermaid-fullscreen-inner {
background: #0f172a;
}
.mermaid-fullscreen-canvas {
flex: 1;
overflow: hidden;
position: relative;
cursor: grab;
}
.mermaid-fullscreen-canvas:active { cursor: grabbing; }
.mermaid-fullscreen-canvas svg {
display: block;
}
/* Bullet lists */
.proposal-content ul, .proposal-content ol {
padding-left: 1.5rem;
margin-bottom: 1.5rem;
color: #475569;
}
.dark .proposal-content ul, .dark .proposal-content ol {
color: #94a3b8;
}
.proposal-content ul {
list-style-type: disc;
}
.proposal-content ol {
list-style-type: decimal;
}
.proposal-content li {
margin-bottom: 0.5rem;
line-height: 1.6;
}
/* Print styles */
@media print {
body {
background: white !important;
color: black !important;
font-size: 11pt !important;
}
.no-print {
display: none !important;
}
.print-container {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
box-shadow: none !important;
}
.proposal-content h1, .proposal-content h2, .proposal-content h3 {
color: black !important;
page-break-after: avoid;
}
.proposal-content h2 {
margin-top: 30pt !important;
page-break-before: always;
}
.proposal-content table {
page-break-inside: avoid !important;
}
.alert-card {
border-color: #ccc !important;
background: #f9f9f9 !important;
color: black !important;
page-break-inside: avoid !important;
}
/* Add custom header/footer for print */
@page {
margin: 2cm;
@top-center {
content: "Trunçgil Teknoloji A.Ş. Resmi Teklif Dökümanı";
font-size: 8pt;
color: #666;
}
@bottom-right {
content: counter(page);
font-size: 8pt;
}
}
}
</style>
@php
$accent = $proposal->meta['accent_color'] ?? 'indigo';
$accentColor = match($accent) {
'emerald' => '#059669',
'cyberpunk' => '#ec4899',
'coral' => '#ea580c',
'amber' => '#d97706',
default => '#4f46e5',
};
$accentColorGlow = match($accent) {
'emerald' => 'rgba(5, 150, 105, 0.2)',
'cyberpunk' => 'rgba(236, 72, 153, 0.2)',
'coral' => 'rgba(234, 88, 12, 0.2)',
'amber' => 'rgba(217, 119, 6, 0.2)',
default => 'rgba(79, 70, 229, 0.2)',
};
$themeClass = match($accent) {
'emerald' => 'theme-emerald bg-emerald-600 hover:bg-emerald-700 focus:ring-emerald-500 text-emerald-600 dark:text-emerald-400 border-emerald-500/20',
'cyberpunk' => 'theme-rose bg-rose-500 hover:bg-rose-600 focus:ring-rose-400 text-rose-500 dark:text-rose-400 border-rose-500/20',
'coral' => 'theme-orange bg-orange-600 hover:bg-orange-700 focus:ring-orange-500 text-orange-600 dark:text-orange-400 border-orange-500/20',
'amber' => 'theme-amber bg-amber-600 hover:bg-amber-700 focus:ring-amber-500 text-amber-600 dark:text-amber-400 border-amber-500/20',
default => 'theme-indigo bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500 text-indigo-600 dark:text-indigo-400 border-indigo-500/20',
};
$gradient = match($accent) {
'emerald' => 'from-emerald-600 to-teal-600 dark:from-emerald-500 dark:to-teal-500',
'cyberpunk' => 'from-rose-500 to-cyan-500 dark:from-rose-400 dark:to-cyan-400',
'coral' => 'from-orange-600 to-rose-600 dark:from-orange-500 dark:to-rose-500',
'amber' => 'from-amber-500 to-yellow-600 dark:from-amber-400 dark:to-yellow-500',
default => 'from-indigo-600 to-purple-600 dark:from-indigo-500 dark:to-purple-500',
};
$glowGradients = match($accent) {
'emerald' => 'shadow-emerald-500/20 dark:shadow-emerald-500/10',
'cyberpunk' => 'shadow-rose-500/20 dark:shadow-rose-500/10',
'coral' => 'shadow-orange-500/20 dark:shadow-orange-500/10',
'amber' => 'shadow-amber-500/20 dark:shadow-amber-500/10',
default => 'shadow-indigo-500/20 dark:shadow-indigo-500/10',
};
@endphp
<script>
// Set dynamic theme color variables
document.documentElement.style.setProperty('--accent-color', '{{ $accentColor }}');
document.documentElement.style.setProperty('--accent-color-glow', '{{ $accentColorGlow }}');
// Dark Mode Logic
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body class="bg-slate-50 dark:bg-slate-900 text-slate-800 dark:text-slate-200 min-h-screen font-sans transition-colors duration-300">
<!-- Standalone Premium Navbar -->
<header class="no-print sticky top-0 z-40 w-full glass-header transition-colors duration-300">
<div class="w-full px-4 sm:px-8 lg:px-12 h-18 flex items-center justify-between py-4">
<!-- Branding: Trunçgil & Client -->
<div class="flex items-center gap-3">
<a href="https://www.truncgil.com.tr" target="_blank" class="flex items-center gap-2">
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Teknoloji" class="h-8 w-auto dark:hidden">
<img src="{{ asset('logos/truncgil-yatay-dark.svg') }}" alt="Trunçgil Teknoloji" class="h-8 w-auto hidden dark:block">
</a>
<!-- Glowing Pulse Line Separator -->
<div class="h-6 w-px bg-slate-300 dark:bg-slate-700 relative flex items-center">
<span class="absolute w-2 h-2 rounded-full bg-theme-{{ $accent }} animate-ping opacity-75 -left-[3.5px]"></span>
</div>
<!-- Client Info -->
<div class="flex items-center gap-2">
<span class="text-xs bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 font-semibold px-2.5 py-1 rounded-md border border-slate-200/50 dark:border-slate-700/50">
{{ $proposal->client_name }}
</span>
</div>
</div>
<!-- Header Actions -->
<div class="flex items-center gap-3">
<!-- Theme Toggle Button -->
<button id="theme-toggle" type="button" class="text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 focus:outline-none focus:ring-4 focus:ring-slate-200 dark:focus:ring-slate-700 rounded-lg text-sm p-2.5">
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
<svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.707.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464a1 1 0 10-1.414 1.414l.707.707a1 1 0 001.414-1.414l-.707-.707zm1.414 8.486a1 1 0 01-1.414 0l-.707-.707a1 1 0 011.414-1.414l.707.707a1 1 0 010 1.414zM4 11a1 1 0 100-2H3a1 1 0 100 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
</button>
<!-- Print/PDF Button -->
<button onclick="window.print()" class="flex items-center gap-1.5 px-4 py-2 border border-slate-300 dark:border-slate-700 text-sm font-semibold rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.2" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.72 13.82l-.24-2.073a.75.75 0 00-.73-.667H3.75a.75.75 0 00-.707.501l-1.121 3.364a.75.75 0 00.16.806l2.121 2.122a.75.75 0 00.805.159l3.364-1.121a.75.75 0 00.502-.707V13.82zm0 0h6.562m-6.562 0L8.4 18.06M17.28 13.82l.24-2.073a.75.75 0 01.73-.667h2.003a.75.75 0 01.707.501l1.121 3.364a.75.75 0 01-.16.806l-2.121 2.122a.75.75 0 01-.805.159l-3.364-1.121a.75.75 0 01-.502-.707V13.82zm0 0H10.72m6.56 0l-1.68 4.24" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v13.5m0-13.5L8.25 6.75M12 3l3.75 3.75" />
</svg>
<span>Yazdır / PDF</span>
</button>
</div>
</div>
</header>
<!-- Main Container -->
<main class="w-full px-4 sm:px-8 lg:px-12 py-8 md:py-12 print-container">
<!-- Executive Metadata & Cover Card -->
<section class="mb-10 p-6 sm:p-8 rounded-2xl bg-white dark:bg-slate-800 border border-slate-200/60 dark:border-slate-700/50 shadow-sm relative overflow-hidden transition-all duration-300">
<!-- Background Glow Accent -->
<div class="absolute top-0 right-0 w-80 h-80 bg-gradient-to-br {{ $gradient }} rounded-full blur-3xl opacity-[0.08] dark:opacity-[0.05] pointer-events-none"></div>
<div class="relative z-10">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-slate-100 dark:border-slate-700/50 pb-6 mb-6">
<div>
<span class="text-xs uppercase tracking-wider font-extrabold text-theme-{{ $accent }} dark:text-theme-{{ $accent }} inline-flex items-center gap-1.5 mb-2">
<span class="w-1.5 h-1.5 rounded-full bg-theme-{{ $accent }} animate-pulse"></span>
TEKNİK VE MALİ TEKLİF DOKÜMANI
</span>
<h1 class="text-2xl sm:text-3xl font-display font-extrabold text-slate-900 dark:text-white leading-tight">
{{ $proposal->title }}
</h1>
</div>
<!-- Dynamic Status Badge -->
<div class="flex items-center gap-2">
@php
$statusLabel = match($proposal->status) {
'draft' => 'Taslak',
'sent' => 'İletildi / Değerlendirmede',
'accepted' => 'Onaylandı',
'rejected' => 'Reddedildi',
'revised' => 'Revizyon Talep Edildi',
default => 'Taslak',
};
$statusColor = match($proposal->status) {
'draft' => 'bg-slate-100 text-slate-700 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700',
'sent' => 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/20 dark:text-blue-400 dark:border-blue-800',
'accepted' => 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/20 dark:text-emerald-400 dark:border-emerald-800',
'rejected' => 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/20 dark:text-rose-400 dark:border-rose-800',
'revised' => 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/20 dark:text-amber-400 dark:border-amber-800',
default => 'bg-slate-100 text-slate-700 border-slate-200',
};
@endphp
<span class="px-3.5 py-1.5 text-xs font-bold rounded-full border {{ $statusColor }} shadow-sm">
{{ $statusLabel }}
</span>
</div>
</div>
<!-- Information Grid -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-6 text-sm">
<div>
<div class="text-slate-400 dark:text-slate-500 font-semibold mb-1">Referans Kodu</div>
<div class="font-bold text-slate-800 dark:text-slate-200 uppercase">{{ $proposal->slug }}</div>
</div>
<div>
<div class="text-slate-400 dark:text-slate-500 font-semibold mb-1">Teklif Tarihi</div>
<div class="font-bold text-slate-800 dark:text-slate-200">{{ $proposal->created_at->format('d.m.Y') }}</div>
</div>
<div>
<div class="text-slate-400 dark:text-slate-500 font-semibold mb-1">Son Geçerlilik</div>
<div class="font-bold text-slate-800 dark:text-slate-200">
{{ $proposal->valid_until ? $proposal->valid_until->format('d.m.Y') : 'Belirtilmedi' }}
</div>
</div>
<div>
<div class="text-slate-400 dark:text-slate-500 font-semibold mb-1">Okunma / İnceleme</div>
<div class="font-bold text-slate-800 dark:text-slate-200 inline-flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 text-slate-400">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>{{ $proposal->views_count }} kez</span>
</div>
</div>
</div>
</div>
</section>
<!-- Main Workspace: Sidebar & Content -->
<div class="flex flex-col lg:flex-row gap-8 items-start relative">
<!-- Dynamic Left Navigation (Sidebar) -->
<aside class="no-print w-full lg:w-72 shrink-0 lg:sticky lg:top-24 max-h-[calc(100vh-140px)] overflow-y-auto hidden lg:block scrollbar-thin">
<div class="p-5 rounded-2xl bg-white dark:bg-slate-800 border border-slate-200/60 dark:border-slate-700/50 shadow-sm flex flex-col gap-5">
<div class="font-display font-bold text-sm tracking-wider uppercase text-slate-400 dark:text-slate-500">
Teklif İçindekiler
</div>
<!-- Dynamic TOC List (Populated via JS) -->
<nav id="toc-nav" class="flex flex-col gap-1.5 text-sm">
<!-- Filled dynamically -->
<div class="text-slate-400 dark:text-slate-500 italic py-2">Başlıklar çıkarılıyor...</div>
</nav>
<!-- Interactive Accents Panel inside Sidebar -->
@if($proposal->total_price)
<div class="border-t border-slate-100 dark:border-slate-700/50 pt-4 mt-2">
<div class="font-display font-bold text-xs tracking-wider uppercase text-slate-400 dark:text-slate-500 mb-3">
Finansal Özet
</div>
<!-- Sidebar Price Box -->
<div class="p-3.5 rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-100 dark:border-slate-800/80">
<div class="text-xs text-slate-400 dark:text-slate-500 font-semibold mb-1">Toplam Bütçe (KDV Hariç)</div>
<div class="text-xl font-display font-extrabold text-slate-900 dark:text-white">
{{ number_format($proposal->total_price, 2, ',', '.') }} {{ $proposal->currency === 'USD' ? 'USD' : ($proposal->currency === 'EUR' ? 'EUR' : 'TL') }}
</div>
</div>
</div>
@endif
<!-- Action Guide -->
<div class="text-xs text-slate-400 leading-relaxed border-t border-slate-100 dark:border-slate-700/50 pt-4">
Bu teklifi dijital olarak onaylamak veya revizyon talep etmek için sayfanın en altında bulunan **İş Birliği & Karar Terminalini** kullanabilirsiniz.
</div>
</div>
</aside>
<!-- Proposal Body (Main Area) -->
<div class="flex-1 w-full flex flex-col gap-8">
<!-- Main Content Card -->
<article class="p-6 sm:p-10 rounded-2xl bg-white dark:bg-slate-800 border border-slate-200/60 dark:border-slate-700/50 shadow-sm relative overflow-hidden transition-all duration-300">
<!-- Markdown Output Area -->
<div id="proposal-markdown" class="proposal-content leading-relaxed transition-colors duration-300">
<!-- Will be populated via Marked.js dynamically -->
<div class="flex flex-col items-center justify-center py-20 text-slate-400">
<div class="w-8 h-8 border-4 border-t-transparent border-theme-{{ $accent }} rounded-full animate-spin mb-4"></div>
<span class="font-medium text-sm">Teklif dökümanı güvenli olarak yükleniyor...</span>
</div>
</div>
</article>
<!-- Interactive pricing & Gaziantep Teknopark %0 KDV muafiyeti box -->
@if($proposal->total_price && ($proposal->meta['show_calculator'] ?? true))
<section class="p-6 sm:p-8 rounded-2xl bg-white dark:bg-slate-800 border border-slate-200/60 dark:border-slate-700/50 shadow-sm relative overflow-hidden transition-all duration-300">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r {{ $gradient }}"></div>
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
<!-- Copy Content -->
<div class="max-w-md">
<div class="flex items-center gap-2 mb-2 text-xs font-bold text-emerald-600 dark:text-emerald-400 uppercase tracking-widest">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z" />
</svg>
<span>Teknopark KDV Muafiyeti Avantajı</span>
</div>
<h3 class="text-xl font-display font-bold text-slate-900 dark:text-white mb-2">
4691 Sayılı Kanun KDV Muafiyeti
</h3>
<p class="text-sm text-slate-500 dark:text-slate-400 leading-relaxed mb-0">
Trunçgil Teknoloji, Gaziantep Üniversitesi Teknoparkı bünyesinde AR-GE yürüten tescilli bir kuruluştur. Kanun kapsamında geliştirdiğimiz yazılım çözümleri KDV'den (%0 KDV) istisnadır. Bu sayede maliyetlerinizde doğrudan avantaj sağlarsınız.
</p>
</div>
<!-- Interactive Box with Toggle Switch -->
<div class="w-full md:w-80 shrink-0 p-5 rounded-2xl bg-slate-50 dark:bg-slate-900 border border-slate-100 dark:border-slate-800 flex flex-col gap-4">
<div class="flex items-center justify-between">
<span class="text-xs text-slate-400 dark:text-slate-500 font-bold uppercase">KDV Muafiyeti Göster</span>
<!-- Toggle Switch -->
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="kdv-toggle" class="sr-only peer" checked>
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer dark:bg-slate-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-slate-600 peer-checked:bg-emerald-500"></div>
</label>
</div>
<div class="border-t border-slate-200/50 dark:border-slate-800/80 pt-3">
<div class="text-xs text-slate-400 dark:text-slate-500 font-semibold mb-1" id="kdv-label">Teknopark Avantajlı Fiyat (%0 KDV)</div>
<div class="text-2xl font-display font-extrabold text-slate-900 dark:text-white" id="kdv-price">
{{ number_format($proposal->total_price, 2, ',', '.') }} {{ $proposal->currency === 'USD' ? 'USD' : ($proposal->currency === 'EUR' ? 'EUR' : 'TL') }}
</div>
</div>
<div class="p-2.5 rounded-lg bg-emerald-50 dark:bg-emerald-950/20 text-emerald-800 dark:text-emerald-400 border border-emerald-200/20 text-xs font-semibold leading-relaxed flex items-start gap-2" id="kdv-benefit-box">
<i data-lucide="sparkles" class="w-4 h-4 shrink-0 text-emerald-500"></i>
<span>
%20 standart KDV oranına kıyasla toplamda <strong id="kdv-benefit-amount">{{ number_format($proposal->total_price * 0.20, 2, ',', '.') }}</strong> kazanç sağladınız.
</span>
</div>
</div>
</div>
</section>
@endif
<!-- Client Interaction Terminal (Approve & Feedback Form) -->
<section id="terminal-card" class="p-6 sm:p-10 rounded-2xl bg-white dark:bg-slate-800 border border-slate-200/60 dark:border-slate-700/50 shadow-sm relative overflow-hidden transition-all duration-300">
<div class="absolute top-0 right-0 w-96 h-96 bg-gradient-to-br {{ $gradient }} rounded-full blur-3xl opacity-[0.06] dark:opacity-[0.03] pointer-events-none"></div>
<div class="relative z-10" id="terminal-content">
<!-- Case: Already Accepted -->
@if($proposal->status === 'accepted')
<div class="flex flex-col items-center justify-center text-center py-6">
<div class="w-16 h-16 rounded-full bg-emerald-100 dark:bg-emerald-950/30 text-emerald-500 flex items-center justify-center mb-4 border border-emerald-500/20 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z" />
</svg>
</div>
<h3 class="text-2xl font-display font-extrabold text-slate-900 dark:text-white mb-2">
Teklif Başarıyla Onaylanmıştır
</h3>
<p class="text-sm text-slate-500 dark:text-slate-400 max-w-lg mb-6 leading-relaxed">
Bu döküman, yetkiliniz <strong class="text-slate-800 dark:text-slate-200">{{ $proposal->accepted_name }}</strong> tarafından <strong class="text-slate-800 dark:text-slate-200">{{ $proposal->accepted_at->format('d.m.Y H:i') }}</strong> tarihinde resmi olarak onaylanmış ve dijital olarak imzalanmıştır.
</p>
<!-- Digital Signature Stamp -->
<div class="px-6 py-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900 max-w-sm w-full relative">
<div class="absolute -top-2.5 left-4 px-2 bg-white dark:bg-slate-800 text-[10px] font-bold text-slate-400 tracking-wider rounded uppercase">Dijital İmza Mührü</div>
<div class="font-signature text-3xl text-theme-{{ $accent }} dark:text-theme-{{ $accent }} py-2 select-none">
{{ $proposal->accepted_name }}
</div>
<div class="text-[9px] text-slate-400 dark:text-slate-500 font-semibold border-t border-slate-200/50 dark:border-slate-800/80 pt-2 flex justify-between">
<span>DOKÜMAN DOĞRULAMA KODU:</span>
<span class="font-mono text-slate-500">{{ substr(sha1($proposal->uuid . $proposal->accepted_at), 0, 16) }}</span>
</div>
</div>
</div>
@else
<!-- Case: Not Accepted Yet -->
<div class="border-b border-slate-100 dark:border-slate-700 pb-5 mb-6">
<h3 class="text-xl font-display font-bold text-slate-900 dark:text-white mb-1">
İş Birliği & Karar Arayüzü
</h3>
<p class="text-sm text-slate-400 dark:text-slate-500 leading-relaxed mb-0">
Aşağıdaki butonları kullanarak teklifi doğrudan onaylayıp dijital olarak imzalayabilir ya da ekibimize revizyon notlarınızı bırakabilirsiniz.
</p>
</div>
<!-- Navigation tabs -->
<div class="flex gap-4 mb-6">
<button onclick="switchTab('approve')" id="tab-btn-approve" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border text-white bg-slate-900 dark:bg-white dark:text-slate-900 hover:shadow-lg focus:ring-4 focus:ring-slate-300">
<i data-lucide="check-circle" class="w-4 h-4"></i>
<span>Teklifi Onayla & İmzala</span>
</button>
<button onclick="switchTab('feedback')" id="tab-btn-feedback" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800">
<i data-lucide="message-square" class="w-4 h-4"></i>
<span>Revizyon / Görüş İlet</span>
</button>
</div>
<!-- Action Tab 1: Approval Form -->
<div id="tab-pane-approve" class="action-pane transition-opacity duration-300">
<form id="approve-form" class="space-y-4">
<div class="max-w-md">
<label for="approve-name" class="block text-sm font-semibold text-slate-600 dark:text-slate-300 mb-2">Ad Soyad / Unvan</label>
<input type="text" id="approve-name" name="name" required placeholder="Örn: Ümit Tunç (Kurucu)" class="w-full px-4 py-3 text-sm rounded-xl border border-slate-200 dark:border-slate-700 bg-transparent focus:ring-4 focus:ring-slate-200/50 dark:focus:ring-slate-700/50 focus:border-slate-500 outline-none transition-all">
</div>
<!-- Real-time Signature Preview -->
<div class="max-w-md p-4 rounded-xl border border-dashed border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-900/30 flex flex-col justify-center min-h-[90px] relative">
<span class="absolute top-2 left-3 text-[10px] font-bold text-slate-400 uppercase tracking-wider">İmza Önizleme</span>
<div id="signature-preview" class="font-signature text-3xl text-theme-{{ $accent }} dark:text-theme-{{ $accent }} text-center pt-2 select-none italic text-slate-300 dark:text-slate-600">
İmzanız burada belirecektir
</div>
</div>
<!-- Secure Checkbox -->
<div class="flex items-start gap-3 max-w-lg">
<input type="checkbox" id="approve-agree" required class="mt-1 w-4 h-4 rounded text-theme-{{ $accent }} focus:ring-theme-{{ $accent }} border-slate-300 dark:border-slate-700 dark:bg-slate-800">
<label for="approve-agree" class="text-xs text-slate-400 dark:text-slate-500 leading-normal">
Bu teklifi ve içeriğindeki mali/teknik şartları onaylayarak dijital imzamın döküman üzerinde tescil edilmesini, IP adresimin ve zaman damgamın doğruluk doğrulaması amacıyla kaydedilmesini kabul ediyorum.
</label>
</div>
<button type="submit" class="w-full sm:w-auto px-6 py-3.5 rounded-xl font-bold text-sm text-white bg-emerald-600 hover:bg-emerald-700 hover:shadow-lg hover:shadow-emerald-600/10 focus:ring-4 focus:ring-emerald-200 dark:focus:ring-emerald-950/50 transition-all flex items-center justify-center gap-2">
<i data-lucide="check" class="w-4 h-4"></i>
<span>Onay İşlemini Tamamla</span>
</button>
</form>
</div>
<!-- Action Tab 2: Feedback Form -->
<div id="tab-pane-feedback" class="action-pane hidden transition-opacity duration-300">
<form id="feedback-form" class="space-y-4">
<div>
<label for="feedback-text" class="block text-sm font-semibold text-slate-600 dark:text-slate-300 mb-2">Revizyon Notları veya Sorularınız</label>
<textarea id="feedback-text" name="feedback" rows="4" required placeholder="Teklif üzerinde talep ettiğiniz teknik veya mali güncellemeleri buraya yazabilirsiniz..." class="w-full px-4 py-3 text-sm rounded-xl border border-slate-200 dark:border-slate-700 bg-transparent focus:ring-4 focus:ring-slate-200/50 dark:focus:ring-slate-700/50 focus:border-slate-500 outline-none transition-all"></textarea>
</div>
<button type="submit" class="w-full sm:w-auto px-6 py-3.5 rounded-xl font-bold text-sm text-white bg-amber-600 hover:bg-amber-700 hover:shadow-lg hover:shadow-amber-600/10 focus:ring-4 focus:ring-amber-200 dark:focus:ring-amber-950/50 transition-all flex items-center justify-center gap-2">
<i data-lucide="send" class="w-4 h-4"></i>
<span>Revizyon Talebini İlet</span>
</button>
</form>
</div>
@endif
</div>
</section>
</div>
</div>
</main>
<!-- Standalone Premium Footer -->
<footer class="no-print border-t border-slate-200/60 dark:border-slate-800/80 mt-16 bg-white dark:bg-slate-900 transition-colors duration-300">
<div class="w-full px-4 sm:px-8 lg:px-12 py-8 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-slate-400 dark:text-slate-500">
<div>
© {{ date('Y') }} Trunçgil Teknoloji San. ve Tic. Ltd. Şti. Tüm hakları saklıdır.
</div>
<div class="flex items-center gap-4">
<a href="https://www.truncgil.com.tr" class="hover:text-slate-600 dark:hover:text-slate-300 font-semibold transition-colors">www.truncgil.com.tr</a>
<span>•</span>
<a href="mailto:info@truncgil.com.tr" class="hover:text-slate-600 dark:hover:text-slate-300 font-semibold transition-colors">info@truncgil.com.tr</a>
<span>•</span>
<a href="tel:+905512343695" class="hover:text-slate-600 dark:hover:text-slate-300 font-semibold transition-colors">+90 551 234 36 95</a>
</div>
</div>
</footer>
<!-- Raw Markdown Data Store (Escape-Proof container) -->
<textarea id="markdown-source" class="hidden">{{ $proposal->content }}</textarea>
<!-- Libraries (CDN) -->
<!-- Marked.js for markdown parsing -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Mermaid.js for drawing diagrams -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.min.js"></script>
<!-- SVG Pan Zoom for interactive Mermaid diagrams -->
<script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
<!-- Confetti for approval celebration -->
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
<!-- Lucide Premium Icons -->
<script src="https://cdn.jsdelivr.net/npm/lucide@0.321.0/dist/umd/lucide.min.js"></script>
<script>
// Init Lucide Icons
lucide.createIcons();
// 1. Theme Toggle Script
var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
// Adjust visibility of icons based on initial setting
if (document.documentElement.classList.contains('dark')) {
themeToggleLightIcon.classList.remove('hidden');
} else {
themeToggleDarkIcon.classList.remove('hidden');
}
var themeToggleBtn = document.getElementById('theme-toggle');
themeToggleBtn.addEventListener('click', function() {
// Toggle icons inside button
themeToggleDarkIcon.classList.toggle('hidden');
themeToggleLightIcon.classList.toggle('hidden');
// If set via local storage previously
if (localStorage.getItem('color-theme')) {
if (localStorage.getItem('color-theme') === 'light') {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
}
} else {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
}
}
// Re-render Mermaid charts dynamically on theme toggle to sync colors!
setTimeout(() => {
location.reload(); // Quick refresh is the most bulletproof way to re-render complex mermaid graphs
}, 100);
});
// 2. Markdown & Mermaid Parsing
const rawMarkdown = document.getElementById('markdown-source').value;
// Custom parser function for GitHub-Style Alerts
function parseGithubAlerts(text) {
const alertTypes = {
'NOTE': { class: 'bg-blue-50/50 dark:bg-blue-950/20 border-blue-500/30 text-blue-800 dark:text-blue-200', icon: 'info', title: 'Gizlilik Bildirimi / Not' },
'IMPORTANT': { class: 'bg-red-50/50 dark:bg-red-950/20 border-rose-500/30 text-rose-800 dark:text-rose-200', icon: 'alert-circle', title: 'Önemli Gereksinim' },
'TIP': { class: 'bg-emerald-50/50 dark:bg-emerald-950/20 border-emerald-500/30 text-emerald-800 dark:text-emerald-200', icon: 'sparkles', title: 'Faydalı Bilgi / Avantaj / İpucu' },
'WARNING': { class: 'bg-amber-50/50 dark:bg-amber-950/20 border-amber-500/30 text-amber-800 dark:text-amber-200', icon: 'alert-triangle', title: 'Uyarı / İstisna' },
'CAUTION': { class: 'bg-orange-50/50 dark:bg-orange-950/20 border-orange-500/30 text-orange-800 dark:text-orange-200', icon: 'zap', title: 'Dikkat' }
};
let lines = text.split('\n');
let inAlert = false;
let alertType = null;
let alertLines = [];
let result = [];
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
let match = line.match(/^>\s*\[!(NOTE|IMPORTANT|TIP|WARNING|CAUTION)\]\s*(.*)/i);
if (match) {
inAlert = true;
alertType = match[1].toUpperCase();
let rest = match[2];
if (rest.trim()) alertLines.push(rest);
} else if (inAlert && line.trim().startsWith('>')) {
// Extract text after the '>' symbol safely
let content = line.trim().substring(1).trim();
alertLines.push(content);
} else {
if (inAlert) {
// Assemble the alert box HTML
let typeInfo = alertTypes[alertType] || alertTypes['NOTE'];
let alertHtml = `
<div class="alert-card border-l-4 p-4 rounded-r-xl my-6 ${typeInfo.class}">
<div class="flex items-center gap-2 font-bold mb-2 text-sm uppercase tracking-wider">
<i data-lucide="${typeInfo.icon}" class="w-4 h-4"></i>
<span>${typeInfo.title}</span>
</div>
<div class="text-sm leading-relaxed">${marked.parse(alertLines.join('\n'))}</div>
</div>`;
result.push(alertHtml);
inAlert = false;
alertLines = [];
alertType = null;
}
result.push(line);
}
}
if (inAlert) {
let typeInfo = alertTypes[alertType] || alertTypes['NOTE'];
let alertHtml = `
<div class="alert-card border-l-4 p-4 rounded-r-xl my-6 ${typeInfo.class}">
<div class="flex items-center gap-2 font-bold mb-2 text-sm uppercase tracking-wider">
<i data-lucide="${typeInfo.icon}" class="w-4 h-4"></i>
<span>${typeInfo.title}</span>
</div>
<div class="text-sm leading-relaxed">${marked.parse(alertLines.join('\n'))}</div>
</div>`;
result.push(alertHtml);
}
return result.join('\n');
}
// Render Markdown content
const contentDiv = document.getElementById('proposal-markdown');
const preprocessedText = parseGithubAlerts(rawMarkdown);
contentDiv.innerHTML = marked.parse(preprocessedText);
// Render Mermaid Diagrams
const isDark = document.documentElement.classList.contains('dark');
mermaid.initialize({
startOnLoad: false,
theme: isDark ? 'dark' : 'default',
securityLevel: 'loose',
themeVariables: {
fontFamily: 'Inter, sans-serif'
}
});
// Mermaid zoom state store
const mermaidZoomInstances = {};
// Scan code blocks for mermaid and convert them
const codeElements = contentDiv.querySelectorAll('pre code.language-mermaid');
codeElements.forEach((codeEl, index) => {
const rawMermaid = codeEl.textContent.trim();
const preContainer = codeEl.parentNode;
// Build glass wrapper for diagram
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-container no-print transition-all duration-300';
wrapper.id = 'mermaid-wrapper-' + index;
// Zoom Toolbar
const toolbar = document.createElement('div');
toolbar.className = 'mermaid-toolbar';
toolbar.innerHTML = `
<span class="mermaid-toolbar-label">Diyagram Görünümü</span>
<button class="mermaid-zoom-btn" title="Yakınlaştır (Zoom In)" onclick="mermaidZoomIn(${index})">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<span class="mermaid-zoom-pct" id="mermaid-pct-${index}">100%</span>
<button class="mermaid-zoom-btn" title="Uzaklaştır (Zoom Out)" onclick="mermaidZoomOut(${index})">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<button class="mermaid-zoom-btn" title="Tam Boyut (1:1)" onclick="mermaidZoomReset(${index})">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
</button>
<button class="mermaid-zoom-btn" title="Tam Ekran" onclick="mermaidFullscreen(${index})">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
</button>
`;
// Canvas wrapper
const canvas = document.createElement('div');
canvas.className = 'mermaid-canvas';
canvas.id = 'mermaid-canvas-' + index;
const graphDiv = document.createElement('div');
graphDiv.className = 'mermaid w-full';
graphDiv.id = 'mermaid-graph-' + index;
graphDiv.textContent = rawMermaid;
canvas.appendChild(graphDiv);
wrapper.appendChild(toolbar);
wrapper.appendChild(canvas);
preContainer.parentNode.replaceChild(wrapper, preContainer);
});
// Fullscreen overlay (singleton)
const fsOverlay = document.createElement('div');
fsOverlay.className = 'mermaid-fullscreen-overlay';
fsOverlay.id = 'mermaid-fs-overlay';
fsOverlay.innerHTML = `
<div class="mermaid-fullscreen-inner" id="mermaid-fs-inner">
<div class="mermaid-toolbar" style="border-radius:20px 20px 0 0;">
<span class="mermaid-toolbar-label">Tam Ekran Görünüm</span>
<button class="mermaid-zoom-btn" title="Yakınlaştır" onclick="mermaidFsZoom('in')">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<span class="mermaid-zoom-pct" id="mermaid-fs-pct">100%</span>
<button class="mermaid-zoom-btn" title="Uzaklaştır" onclick="mermaidFsZoom('out')">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
</button>
<button class="mermaid-zoom-btn" title="1:1 Sıfırla" onclick="mermaidFsZoom('reset')">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
</button>
<button class="mermaid-zoom-btn" title="Kapat" onclick="closeMermaidFullscreen()" style="background:#ef4444;border-color:#ef4444;color:#fff;">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div class="mermaid-fullscreen-canvas" id="mermaid-fs-canvas"></div>
</div>
`;
document.body.appendChild(fsOverlay);
fsOverlay.addEventListener('click', function(e) { if (e.target === fsOverlay) closeMermaidFullscreen(); });
// Run mermaid parser
setTimeout(() => {
mermaid.run().then(() => {
// Re-init lucide icons inside alerts after marked rendering completes
lucide.createIcons();
// Initialize svg-pan-zoom for each mermaid diagram
codeElements.forEach((_, index) => {
initMermaidZoom(index);
});
}).catch(err => {
console.error("Mermaid drawing error:", err);
});
}, 100);
// Init svg-pan-zoom for a given mermaid index
function initMermaidZoom(index) {
const canvas = document.getElementById('mermaid-canvas-' + index);
if (!canvas) return;
const svg = canvas.querySelector('svg');
if (!svg) return;
// Ensure SVG has an ID for svg-pan-zoom
if (!svg.id) svg.id = 'mermaid-svg-' + index;
// Compute a good canvas height from SVG viewBox aspect ratio
const canvasW = canvas.clientWidth || canvas.offsetWidth || 800;
let canvasH = 520;
const vb = svg.getAttribute('viewBox');
if (vb) {
const parts = vb.split(/[\s,]+/);
if (parts.length >= 4) {
const svgW = parseFloat(parts[2]);
const svgH = parseFloat(parts[3]);
if (svgW > 0 && svgH > 0) {
canvasH = Math.max(400, Math.min(Math.round(svgH * (canvasW / svgW)), 900));
}
}
}
canvas.style.height = canvasH + 'px';
// svg-pan-zoom needs explicit width/height on the SVG element
svg.setAttribute('width', canvasW);
svg.setAttribute('height', canvasH);
svg.style.width = canvasW + 'px';
svg.style.height = canvasH + 'px';
try {
const panZoom = svgPanZoom('#' + svg.id, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.3,
maxZoom: 8,
zoomScaleSensitivity: 0.3,
onZoom: (zoom) => {
const pctEl = document.getElementById('mermaid-pct-' + index);
if (pctEl) pctEl.textContent = Math.round(zoom * 100) + '%';
}
});
mermaidZoomInstances[index] = panZoom;
} catch(e) {
console.warn('svg-pan-zoom init failed for mermaid-' + index, e);
}
}
// Zoom controls
function mermaidZoomIn(index) {
const pz = mermaidZoomInstances[index];
if (pz) { pz.zoomIn(); updatePct(index, pz.getZoom()); }
}
function mermaidZoomOut(index) {
const pz = mermaidZoomInstances[index];
if (pz) { pz.zoomOut(); updatePct(index, pz.getZoom()); }
}
function mermaidZoomReset(index) {
const pz = mermaidZoomInstances[index];
if (pz) { pz.resetZoom(); pz.center(); updatePct(index, pz.getZoom()); }
}
function updatePct(index, zoom) {
const pctEl = document.getElementById('mermaid-pct-' + index);
if (pctEl) pctEl.textContent = Math.round(zoom * 100) + '%';
}
// Fullscreen
let fsActivePanZoom = null;
function mermaidFullscreen(index) {
const canvas = document.getElementById('mermaid-canvas-' + index);
if (!canvas) return;
const svg = canvas.querySelector('svg');
if (!svg) return;
const fsCanvas = document.getElementById('mermaid-fs-canvas');
fsCanvas.innerHTML = '';
// Show overlay first so fsCanvas has layout dimensions
document.getElementById('mermaid-fs-overlay').classList.add('active');
document.body.style.overflow = 'hidden';
// Init pan-zoom on cloned SVG after layout is available
setTimeout(() => {
const svgClone = svg.cloneNode(true);
svgClone.id = 'mermaid-fs-svg-' + index;
// Get actual pixel dimensions of the fullscreen canvas
const fsW = fsCanvas.clientWidth || fsCanvas.offsetWidth || 1000;
const fsH = fsCanvas.clientHeight || fsCanvas.offsetHeight || 600;
// Set explicit dimensions on SVG for svg-pan-zoom
svgClone.setAttribute('width', fsW);
svgClone.setAttribute('height', fsH);
svgClone.style.width = fsW + 'px';
svgClone.style.height = fsH + 'px';
fsCanvas.appendChild(svgClone);
try {
fsActivePanZoom = svgPanZoom('#mermaid-fs-svg-' + index, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.2,
maxZoom: 10,
zoomScaleSensitivity: 0.3,
onZoom: (zoom) => {
const pctEl = document.getElementById('mermaid-fs-pct');
if (pctEl) pctEl.textContent = Math.round(zoom * 100) + '%';
}
});
document.getElementById('mermaid-fs-pct').textContent = Math.round(fsActivePanZoom.getZoom() * 100) + '%';
} catch(e) { console.warn('fs pan-zoom init error', e); }
}, 150);
}
function closeMermaidFullscreen() {
document.getElementById('mermaid-fs-overlay').classList.remove('active');
document.body.style.overflow = '';
if (fsActivePanZoom) { try { fsActivePanZoom.destroy(); } catch(e) {} fsActivePanZoom = null; }
}
function mermaidFsZoom(action) {
if (!fsActivePanZoom) return;
if (action === 'in') fsActivePanZoom.zoomIn();
else if (action === 'out') fsActivePanZoom.zoomOut();
else if (action === 'reset') { fsActivePanZoom.resetZoom(); fsActivePanZoom.center(); }
const pctEl = document.getElementById('mermaid-fs-pct');
if (pctEl) pctEl.textContent = Math.round(fsActivePanZoom.getZoom() * 100) + '%';
}
// Keyboard ESC closes fullscreen
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeMermaidFullscreen();
});
// 3. Dynamic Sidebar TOC Generator
const tocNav = document.getElementById('toc-nav');
const headings = contentDiv.querySelectorAll('h2, h3');
if (headings.length === 0) {
tocNav.innerHTML = `<span class="text-slate-400 italic">Başlık bulunamadı.</span>`;
} else {
tocNav.innerHTML = ''; // Clear loading indicator
headings.forEach((heading, idx) => {
const id = 'heading-' + idx;
heading.id = id; // Assign ID
const level = heading.tagName.toLowerCase();
const text = heading.textContent;
const a = document.createElement('a');
a.href = '#' + id;
a.className = `toc-item flex items-center gap-2 py-1.5 transition-all text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200 ${level === 'h3' ? 'pl-5 text-xs' : 'font-medium text-sm'}`;
// Bullet marker
const bullet = document.createElement('span');
bullet.className = 'toc-bullet w-1.5 h-1.5 rounded-full bg-slate-300 dark:bg-slate-600 transition-all shrink-0';
const label = document.createElement('span');
label.className = 'truncate';
label.textContent = text;
a.appendChild(bullet);
a.appendChild(label);
tocNav.appendChild(a);
});
}
// Active TOC tracking on scroll
const tocItems = tocNav.querySelectorAll('.toc-item');
window.addEventListener('scroll', () => {
let activeIdx = 0;
headings.forEach((heading, idx) => {
const rect = heading.getBoundingClientRect();
if (rect.top <= 140) {
activeIdx = idx;
}
});
tocItems.forEach((item, idx) => {
if (idx === activeIdx) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
});
// 4. KDV Switch Calculator Logic
const kdvToggle = document.getElementById('kdv-toggle');
const kdvLabel = document.getElementById('kdv-label');
const kdvPrice = document.getElementById('kdv-price');
const kdvBenefitBox = document.getElementById('kdv-benefit-box');
if (kdvToggle) {
const totalPrice = parseFloat('{{ $proposal->total_price }}');
const currencySymbol = '{{ $proposal->currency === 'USD' ? 'USD' : ($proposal->currency === 'EUR' ? 'EUR' : 'TL') }}';
kdvToggle.addEventListener('change', function() {
if (this.checked) {
// %0 KDV selected (Teknopark exemption)
kdvLabel.textContent = 'Teknopark Avantajlı Fiyat (%0 KDV)';
kdvPrice.textContent = new Intl.NumberFormat('tr-TR', { minimumFractionDigits: 2 }).format(totalPrice) + ' ' + currencySymbol;
kdvBenefitBox.classList.remove('hidden');
} else {
// %20 standard KDV added
const priceWithVat = totalPrice * 1.20;
kdvLabel.textContent = 'Standart Fiyat (+%20 KDV)';
kdvPrice.textContent = new Intl.NumberFormat('tr-TR', { minimumFractionDigits: 2 }).format(priceWithVat) + ' ' + currencySymbol;
kdvBenefitBox.classList.add('hidden');
}
});
}
// 5. Client Interaction Actions & Tabs switching
function switchTab(tab) {
const btnApprove = document.getElementById('tab-btn-approve');
const btnFeedback = document.getElementById('tab-btn-feedback');
const paneApprove = document.getElementById('tab-pane-approve');
const paneFeedback = document.getElementById('tab-pane-feedback');
if (tab === 'approve') {
btnApprove.className = "flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border text-white bg-slate-900 dark:bg-white dark:text-slate-900 hover:shadow-lg focus:ring-4 focus:ring-slate-300";
btnFeedback.className = "flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800";
paneApprove.classList.remove('hidden');
paneFeedback.classList.add('hidden');
} else {
btnFeedback.className = "flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border text-white bg-slate-900 dark:bg-white dark:text-slate-900 hover:shadow-lg focus:ring-4 focus:ring-slate-300";
btnApprove.className = "flex-1 sm:flex-initial flex items-center justify-center gap-2 px-5 py-3 rounded-xl font-semibold text-sm transition-all duration-200 border border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800";
paneFeedback.classList.remove('hidden');
paneApprove.classList.add('hidden');
}
}
// Signature Real-time typing script preview
const approveNameInput = document.getElementById('approve-name');
const signaturePreview = document.getElementById('signature-preview');
if (approveNameInput && signaturePreview) {
approveNameInput.addEventListener('input', function() {
const text = this.value.trim();
if (text) {
signaturePreview.textContent = text;
signaturePreview.className = "font-signature text-4xl text-theme-{{ $accent }} dark:text-theme-{{ $accent }} text-center pt-2 select-none";
} else {
signaturePreview.textContent = "İmzanız burada belirecektir";
signaturePreview.className = "font-signature text-3xl text-theme-{{ $accent }} dark:text-theme-{{ $accent }} text-center pt-2 select-none italic text-slate-300 dark:text-slate-600";
}
});
}
// AJAX Action Post Handlers
const approveForm = document.getElementById('approve-form');
const feedbackForm = document.getElementById('feedback-form');
const terminalContent = document.getElementById('terminal-content');
if (approveForm) {
approveForm.addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('approve-name').value.trim();
if (!name) return;
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Show loading state
const submitBtn = this.querySelector('button[type="submit"]');
const origHtml = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = `<span class="w-4 h-4 border-2 border-t-transparent border-white rounded-full animate-spin"></span> Onaylanıyor...`;
fetch('{{ route('proposals.action', $proposal->slug) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({
action_type: 'approve',
name: name
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Trigger Confetti Rain!
var duration = 4 * 1000;
var end = Date.now() + duration;
(function frame() {
confetti({
particleCount: 4,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: ['{{ $accentColor }}', '#10b981', '#3b82f6']
});
confetti({
particleCount: 4,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: ['{{ $accentColor }}', '#10b981', '#3b82f6']
});
if (Date.now() < end) {
requestAnimationFrame(frame);
}
}());
// Transform terminal HTML into secure signature verification layout dynamically
const verifyCode = Math.random().toString(36).substring(2, 10).toUpperCase() + Math.random().toString(36).substring(2, 10).toUpperCase();
const now = new Date();
const formattedDate = now.toLocaleDateString('tr-TR') + ' ' + now.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
setTimeout(() => {
terminalContent.innerHTML = `
<div class="flex flex-col items-center justify-center text-center py-6 animate-fade-in">
<div class="w-16 h-16 rounded-full bg-emerald-100 dark:bg-emerald-950/30 text-emerald-500 flex items-center justify-center mb-4 border border-emerald-500/20 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z" />
</svg>
</div>
<h3 class="text-2xl font-display font-extrabold text-slate-900 dark:text-white mb-2">
Teklif Başarıyla Onaylanmıştır
</h3>
<p class="text-sm text-slate-500 dark:text-slate-400 max-w-lg mb-6 leading-relaxed">
Bu döküman, yetkiliniz <strong class="text-slate-800 dark:text-slate-200">${name}</strong> tarafından <strong class="text-slate-800 dark:text-slate-200">${formattedDate}</strong> tarihinde resmi olarak onaylanmış ve dijital olarak imzalanmıştır.
</p>
<div class="px-6 py-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900 max-w-sm w-full relative">
<div class="absolute -top-2.5 left-4 px-2 bg-white dark:bg-slate-800 text-[10px] font-bold text-slate-400 tracking-wider rounded uppercase">Dijital İmza Mührü</div>
<div class="font-signature text-3xl text-theme-{{ $accent }} dark:text-theme-{{ $accent }} py-2 select-none">
${name}
</div>
<div class="text-[9px] text-slate-400 dark:text-slate-500 font-semibold border-t border-slate-200/50 dark:border-slate-800/80 pt-2 flex justify-between">
<span>DOKÜMAN DOĞRULAMA KODU:</span>
<span class="font-mono text-slate-500">${verifyCode.substring(0, 16)}</span>
</div>
</div>
</div>
`;
// Smooth scroll to the terminal card
document.getElementById('terminal-card').scrollIntoView({ behavior: 'smooth' });
// Re-update status badge in header
const headerBadge = document.querySelector('span.px-3.5.py-1.5');
if (headerBadge) {
headerBadge.className = 'px-3.5 py-1.5 text-xs font-bold rounded-full border bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/20 dark:text-emerald-400 dark:border-emerald-800 shadow-sm';
headerBadge.textContent = 'Onaylandı';
}
}, 800);
} else {
alert("Onay hatası: " + data.message);
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
}
})
.catch(err => {
console.error("AJAX Error:", err);
alert("Onay işlemi gerçekleştirilemedi. Lütfen bağlantınızı kontrol edin.");
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
});
});
}
if (feedbackForm) {
feedbackForm.addEventListener('submit', function(e) {
e.preventDefault();
const feedbackText = document.getElementById('feedback-text').value.trim();
if (!feedbackText) return;
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Show loading state
const submitBtn = this.querySelector('button[type="submit"]');
const origHtml = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = `<span class="w-4 h-4 border-2 border-t-transparent border-white rounded-full animate-spin"></span> İletiliyor...`;
fetch('{{ route('proposals.action', $proposal->slug) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'Accept': 'application/json'
},
body: JSON.stringify({
action_type: 'feedback',
feedback: feedbackText
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
setTimeout(() => {
terminalContent.innerHTML = `
<div class="flex flex-col items-center justify-center text-center py-8 animate-fade-in">
<div class="w-16 h-16 rounded-full bg-amber-100 dark:bg-amber-950/30 text-amber-500 flex items-center justify-center mb-4 border border-amber-500/20 shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.2" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18V6.125c0-.621.504-1.125 1.125-1.125H9.75M8.25 21h8.25" />
</svg>
</div>
<h3 class="text-xl font-display font-extrabold text-slate-900 dark:text-white mb-2">
Geri Bildiriminiz Alındı
</h3>
<p class="text-sm text-slate-500 dark:text-slate-400 max-w-md leading-relaxed mb-0">
Revizyon talebiniz ve notlarınız teknik ekibimize iletilmiştir. Teklifimiz en kısa sürede güncellenerek sizinle yeniden paylaşılacaktır.
</p>
</div>
`;
// Re-update status badge in header
const headerBadge = document.querySelector('span.px-3.5.py-1.5');
if (headerBadge) {
headerBadge.className = 'px-3.5 py-1.5 text-xs font-bold rounded-full border bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/20 dark:text-amber-400 dark:border-amber-800 shadow-sm';
headerBadge.textContent = 'Revizyon Talep Edildi';
}
}, 800);
} else {
alert("Gönderim hatası: " + data.message);
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
}
})
.catch(err => {
console.error("AJAX Error:", err);
alert("Geri bildirim iletilemedi. Lütfen bağlantınızı kontrol edin.");
submitBtn.disabled = false;
submitBtn.innerHTML = origHtml;
});
});
}
</script>
</body>
</html>