diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..28e199c 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,862 @@ +:root { + --bg: #f3f4f6; + --surface: #ffffff; + --surface-soft: #f8fafc; + --text: #111827; + --muted: #5b6470; + --line: #d9dde4; + --line-strong: #c4cad3; + --accent: #111827; + --accent-soft: #eef2f6; + --success: #1f6b3b; + --danger: #b42318; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 18px; + --shadow-sm: 0 10px 24px rgba(15, 23, 42, 0.04); + --shadow-md: 0 18px 40px rgba(15, 23, 42, 0.08); + --container-max: 1200px; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; margin: 0; min-height: 100vh; + background: var(--bg); + color: var(--text); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.65; } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; +a { + color: var(--text); + text-decoration-color: rgba(17, 24, 39, 0.35); + text-underline-offset: 0.16em; } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +a:hover { + color: #000; } -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; -} - -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); -} - -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; -} - -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; -} - -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); -} - -.chat-input-area form { - display: flex; - gap: 0.75rem; -} - -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; -} - -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); -} - -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; -} - -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; -} - -.table td { - background: #fff; - padding: 1rem; - border: none; -} - -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { +img { + max-width: 100%; display: block; - margin-bottom: 0.5rem; +} + +main { + padding-bottom: 4rem; +} + +.container { + max-width: var(--container-max); +} + +.site-header { + position: sticky; + top: 0; + z-index: 1025; + background: rgba(243, 244, 246, 0.94); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--line); +} + +.navbar { + padding: 0.8rem 0; +} + +.navbar-brand { + font-weight: 700; + letter-spacing: -0.03em; + color: var(--text); +} + +.navbar-brand:hover { + color: #000; +} + +.nav-link { + color: var(--muted); + font-size: 0.95rem; + padding: 0.55rem 0.75rem !important; + border-radius: var(--radius-sm); + transition: background-color 0.2s ease, color 0.2s ease; +} + +.nav-link:hover, +.nav-link.active { + color: var(--text); + background: var(--accent-soft); +} + +.btn-refined { + border-radius: 10px; + padding: 0.72rem 1rem; + font-size: 0.95rem; font-weight: 600; - font-size: 0.9rem; + box-shadow: none; } -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; +.btn-refined:focus-visible, +.nav-link:focus-visible, +.link-button:focus-visible, +.text-link:focus-visible, +.cookie-reopen:focus-visible, +.form-check-input:focus-visible, +button:focus-visible, +a:focus-visible { + outline: 3px solid rgba(17, 24, 39, 0.18); + outline-offset: 2px; } -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; +.eyebrow { + display: inline-flex; align-items: center; + gap: 0.5rem; + padding: 0.42rem 0.72rem; + border-radius: 999px; + border: 1px solid var(--line); + background: var(--surface); + color: var(--muted); + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; } -.header-links { +.hero-section { + padding: 3.4rem 0 1.5rem; +} + +.hero-grid { + display: grid; + grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.95fr); + gap: 1rem; + align-items: start; +} + +.hero-copy h1, +.legal-shell h1 { + margin: 1rem 0 1.1rem; + font-size: clamp(2.3rem, 4.4vw, 4.45rem); + line-height: 1.05; + letter-spacing: -0.045em; + max-width: 14ch; +} + +.lead { + max-width: 64ch; + font-size: 1.03rem; + color: var(--muted); +} + +.hero-actions { display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin: 1.5rem 0 1.75rem; +} + +.hero-proof { + display: grid; + gap: 0.72rem; + max-width: 60rem; +} + +.proof-item { + display: flex; + gap: 0.72rem; + align-items: start; + padding: 0.82rem 0.95rem; + border-radius: var(--radius-md); + background: var(--surface); + border: 1px solid var(--line); + box-shadow: var(--shadow-sm); +} + +.proof-dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--text); + margin-top: 0.45rem; + flex: 0 0 10px; +} + +.stacked-panels { + display: grid; gap: 1rem; } -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); +.info-panel, +.article-panel, +.section-shell, +.legal-shell, +.channel-card, +.legal-card { + background: var(--surface); + border: 1px solid var(--line); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); } -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; +.compact-panel { + padding: 1rem; +} + +.panel-heading { + margin-bottom: 0.8rem; +} + +.panel-heading h2, +.info-panel h3, +.article-panel h2, +.channel-card__name, +.legal-card h3, +.section-heading h2 { + margin: 0; + letter-spacing: -0.03em; +} + +.panel-heading h2 { + font-size: 1.05rem; +} + +.panel-kicker { + display: inline-block; + margin-bottom: 0.28rem; + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); +} + +.panel-copy, +.section-heading p, +.info-panel p, +.channel-card p, +.legal-card p, +.article-panel p, +.article-like p, +.legal-list, +.legal-lead { + color: var(--muted); +} + +.status-list { + display: grid; + gap: 0.75rem; +} + +.status-list li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding-top: 0.72rem; + border-top: 1px solid var(--line); + font-size: 0.94rem; +} + +.status-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 118px; + padding: 0.34rem 0.65rem; + border-radius: 999px; + background: var(--accent-soft); + border: 1px solid var(--line); + font-size: 0.8rem; + font-weight: 600; + color: var(--text); +} + +.status-pill[data-state="on"], +.status-pill[data-state="locked"] { + background: #eef6f0; + color: var(--success); + border-color: rgba(31, 107, 59, 0.18); +} + +.status-pill[data-state="off"] { + background: #fff6f5; + color: var(--danger); + border-color: rgba(180, 35, 24, 0.12); +} + +.stat-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.75rem; +} + +.stat-grid div { + padding: 0.78rem; + border-radius: var(--radius-md); + border: 1px solid var(--line); + background: var(--surface-soft); +} + +.stat-grid strong { + display: block; + font-size: 1.05rem; + line-height: 1.1; +} + +.stat-grid span { + color: var(--muted); + font-size: 0.84rem; +} + +.section-block { + padding: 0.85rem 0; +} + +.section-shell, +.legal-shell { + padding: 1.45rem; +} + +.section-heading { + margin-bottom: 1.3rem; +} + +.section-heading.with-meta { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; +} + +.section-heading h2 { + margin-top: 0.8rem; + margin-bottom: 0.72rem; + font-size: clamp(1.55rem, 2vw, 2.15rem); +} + +.section-meta { + flex: 0 0 auto; + padding: 0.58rem 0.82rem; + border: 1px solid var(--line); + background: var(--surface-soft); + border-radius: var(--radius-md); + font-size: 0.86rem; + color: var(--muted); +} + +.widget-card { + border: 1px solid var(--line); + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--surface-soft); +} + +.widget-scroll { + overflow-x: auto; + overflow-y: hidden; + padding: 0.75rem 0.75rem 0.5rem; + -webkit-overflow-scrolling: touch; + scrollbar-color: #9aa4b2 #e8ebf0; +} + +.widget-scroll::-webkit-scrollbar { + height: 14px; +} + +.widget-scroll::-webkit-scrollbar-track { + background: #e8ebf0; + border-radius: 999px; +} + +.widget-scroll::-webkit-scrollbar-thumb { + background: #98a2b3; + border-radius: 999px; + border: 3px solid #e8ebf0; +} + +.widget-scroll__inner { + min-width: 860px; +} + +.widget-scroll iframe { + max-width: none; +} + +.tvp-widget-attribution { + margin: 0.55rem 0 0; + color: var(--muted); + font-size: 0.88rem; +} + +.scroll-note { + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: flex-start; + padding: 0.85rem 1rem; + border-top: 1px solid var(--line); + background: var(--surface); + font-size: 0.92rem; + color: var(--muted); +} + +.scroll-note button { + flex: 0 0 auto; + background: transparent; + border: none; + color: var(--text); + text-decoration: underline; + text-underline-offset: 0.16em; + padding: 0; +} + +.content-grid { + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.9fr); + gap: 1rem; +} + +.aside-panels { + display: grid; + gap: 1rem; +} + +.info-panel { + padding: 1rem; +} + +.article-panel { + padding: 1.2rem; +} + +.article-panel h2, +.article-like h3 { + font-size: 1.1rem; + margin-bottom: 0.75rem; +} + +.card-grid { + display: grid; + gap: 1rem; +} + +.card-grid--three { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.channel-card, +.legal-card { + padding: 1rem; +} + +.channel-card__name { + font-size: 1.02rem; + margin-bottom: 0.45rem; +} + +.keyword-cloud { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; +} + +.keyword-pill { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.52rem 0.8rem; + border-radius: 999px; + border: 1px solid var(--line); + background: var(--surface-soft); + color: var(--text); + font-size: 0.88rem; + white-space: nowrap; +} + +.article-like { + min-height: 100%; +} + +.ordered-steps { + margin: 0; + padding-left: 1.25rem; + color: var(--muted); +} + +.ordered-steps li + li { + margin-top: 0.42rem; +} + +.custom-accordion .accordion-item { + border: 1px solid var(--line); + border-radius: var(--radius-md); + overflow: hidden; + background: var(--surface); +} + +.custom-accordion .accordion-item + .accordion-item { + margin-top: 0.85rem; +} + +.custom-accordion .accordion-button { + background: var(--surface); + color: var(--text); + font-weight: 600; + box-shadow: none; +} + +.custom-accordion .accordion-button:not(.collapsed) { + background: var(--surface-soft); +} + +.custom-accordion .accordion-body { + color: var(--muted); +} + +.text-link, +.link-button { + display: inline-flex; + align-items: center; + gap: 0.35rem; + font-weight: 600; + color: var(--text); + text-decoration: underline; + text-underline-offset: 0.18em; +} + +.link-button { + padding: 0; + border: none; + background: transparent; +} + +.legal-body .site-main { + padding-top: 0.6rem; +} + +.legal-hero { + padding: 2rem 0 0.8rem; +} + +.legal-shell h1 { + max-width: none; +} + +.legal-lead { + max-width: 70ch; +} + +.breadcrumb-wrap { + font-size: 0.88rem; +} + +.breadcrumb { + margin: 0; +} + +.legal-grid { + grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.8fr); +} + +.legal-list { + padding-left: 1.2rem; +} + +.legal-list li + li { + margin-top: 0.42rem; +} + +.legal-table { + margin-bottom: 0; +} + +.legal-table th { + font-size: 0.88rem; + color: var(--muted); + font-weight: 600; +} + +.site-footer { + padding: 1.2rem 0 2rem; + border-top: 1px solid var(--line); +} + +.footer-grid { + display: grid; + grid-template-columns: 1.25fr 0.9fr 0.9fr 0.9fr; + gap: 1rem; +} + +.footer-brand, +.footer-title { font-weight: 700; + letter-spacing: -0.02em; } -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; +.footer-copy, +.footer-links a { + color: var(--muted); + font-size: 0.95rem; +} + +.footer-links li + li { + margin-top: 0.42rem; +} + +.cookie-floating { + position: fixed; + left: 1rem; + bottom: 1rem; + z-index: 1050; +} + +.cookie-reopen { + display: inline-flex; + align-items: center; + gap: 0.62rem; + padding: 0.78rem 1rem; + border-radius: 999px; + border: 1px solid rgba(17, 24, 39, 0.08); + background: var(--accent); + color: #fff; + font-size: 0.9rem; + font-weight: 600; + box-shadow: var(--shadow-md); +} + +.cookie-reopen:hover { + background: #000; +} + +.cookie-reopen__dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: #dff0d8; +} + +.cookie-banner { + position: fixed; + right: 1rem; + bottom: 1rem; + z-index: 1045; + width: min(480px, calc(100vw - 2rem)); + padding: 1rem; + border-radius: 20px; + border: 1px solid var(--line-strong); + background: rgba(255, 255, 255, 0.98); + box-shadow: var(--shadow-md); +} + +.cookie-banner[hidden] { + display: none !important; +} + +.cookie-banner__eyebrow { + font-size: 0.78rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted); +} + +.cookie-banner__title { + margin: 0; + font-size: 1.2rem; + letter-spacing: -0.03em; +} + +.cookie-banner__intro { + margin: 0.75rem 0 1rem; + color: var(--muted); + font-size: 0.94rem; +} + +.cookie-banner__toggles { + border-top: 1px solid var(--line); + border-bottom: 1px solid var(--line); +} + +.cookie-switch-row { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.85rem 0; +} + +.cookie-switch-row + .cookie-switch-row { + border-top: 1px solid var(--line); +} + +.cookie-switch-row p { + margin: 0.2rem 0 0; + color: var(--muted); + font-size: 0.86rem; +} + +.cookie-switch-label { + font-weight: 600; +} + +.form-check-input { + width: 3rem; + height: 1.6rem; + margin: 0; cursor: pointer; } -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; +.form-check-input:checked { + background-color: var(--accent); + border-color: var(--accent); +} + +.form-check-input:focus { + box-shadow: 0 0 0 0.2rem rgba(17, 24, 39, 0.12); +} + +.cookie-banner__actions { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; margin-top: 1rem; } -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; +.cookie-banner__links { + display: flex; + gap: 0.5rem; + align-items: center; + margin-top: 0.8rem; + font-size: 0.88rem; + color: var(--muted); } -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; +.toast-stack { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 1080; + display: grid; + gap: 0.6rem; } -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); +.app-toast { + min-width: 260px; + max-width: 360px; + padding: 0.82rem 0.95rem; + border-radius: var(--radius-md); + border: 1px solid rgba(17, 24, 39, 0.08); + background: rgba(17, 24, 39, 0.96); + color: #fff; + box-shadow: var(--shadow-md); + opacity: 0; + transform: translateY(-6px); + transition: opacity 0.2s ease, transform 0.2s ease; } -.history-table { - width: 100%; +.app-toast.is-visible { + opacity: 1; + transform: translateY(0); } -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; +[hidden] { + display: none !important; } -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; +@media (max-width: 1199px) { + .footer-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; +@media (max-width: 991px) { + .hero-grid, + .content-grid, + .legal-grid, + .card-grid--three { + grid-template-columns: 1fr; + } + + .section-heading.with-meta { + flex-direction: column; + } + + .widget-scroll__inner { + min-width: 780px; + } } -.no-messages { - text-align: center; - color: #777; -} \ No newline at end of file +@media (max-width: 767px) { + .hero-section { + padding-top: 2.4rem; + } + + .section-shell, + .legal-shell, + .info-panel, + .article-panel, + .channel-card, + .legal-card { + padding: 1rem; + } + + .stat-grid { + grid-template-columns: 1fr; + } + + .widget-scroll__inner { + min-width: 700px; + } + + .cookie-banner { + width: calc(100vw - 1rem); + right: 0.5rem; + bottom: 0.5rem; + } + + .cookie-floating { + left: 0.5rem; + bottom: 0.5rem; + } + + .scroll-note { + flex-direction: column; + } +} + +@media (max-width: 575px) { + .navbar { + padding: 0.65rem 0; + } + + .hero-copy h1, + .legal-shell h1 { + max-width: none; + font-size: clamp(2rem, 11vw, 2.85rem); + } + + .lead { + font-size: 0.98rem; + } + + .widget-scroll__inner { + min-width: 640px; + } + + .footer-grid { + grid-template-columns: 1fr; + } + + .cookie-banner__actions { + flex-direction: column; + } + + .cookie-banner__actions .btn { + width: 100%; + } +} diff --git a/assets/js/main.js b/assets/js/main.js index d349598..fe96fb5 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,397 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); - - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; + const COOKIE_NAME = 'ptcs_consent'; + const COOKIE_MAX_AGE = 60 * 60 * 24 * 180; + const STORAGE_KEYS = { + personalization: 'ptcs_ui_preferences', + audience: 'ptcs_local_audience' }; - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; + const banner = document.getElementById('cookie-banner'); + const reopenButton = document.getElementById('cookie-reopen'); + const reopenLabel = document.getElementById('cookie-reopen-label'); + const footerCookieSettings = document.getElementById('footer-cookie-settings'); + const dismissScrollHint = document.getElementById('scroll-hint-dismiss'); + const scrollHint = document.getElementById('scroll-hint'); + const toastStack = document.getElementById('toast-stack'); + const personalizationControls = Array.from(document.querySelectorAll('[data-consent-control="personalization"]')); + const audienceControls = Array.from(document.querySelectorAll('[data-consent-control="audience"]')); + const consentActionButtons = Array.from(document.querySelectorAll('[data-cookie-action]')); + const trackedSections = Array.from(document.querySelectorAll('[data-track-section]')); - appendMessage(message, 'visitor'); - chatInput.value = ''; + const state = { + prefs: { + essential: true, + personalization: false, + audience: false, + timestamp: null + }, + audienceCountedThisSession: false, + visibleSections: new Set() + }; - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); + function safeParse(rawValue) { + if (!rawValue) { + return null; } + try { + return JSON.parse(rawValue); + } catch (error) { + return null; + } + } + + function storageGet(key) { + try { + return window.localStorage.getItem(key); + } catch (error) { + return null; + } + } + + function storageSet(key, value) { + try { + window.localStorage.setItem(key, value); + } catch (error) { + // no-op + } + } + + function storageRemove(key) { + try { + window.localStorage.removeItem(key); + } catch (error) { + // no-op + } + } + + function getCookie(name) { + const prefix = `${name}=`; + const cookies = document.cookie ? document.cookie.split('; ') : []; + for (const row of cookies) { + if (row.startsWith(prefix)) { + return decodeURIComponent(row.substring(prefix.length)); + } + } + return null; + } + + function setCookie(name, value, maxAge) { + document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; SameSite=Lax`; + } + + function normalizePrefs(rawPrefs = {}) { + return { + essential: true, + personalization: Boolean(rawPrefs.personalization), + audience: Boolean(rawPrefs.audience), + timestamp: rawPrefs.timestamp || null + }; + } + + function readSavedPrefs() { + const parsed = safeParse(getCookie(COOKIE_NAME)); + return parsed ? normalizePrefs(parsed) : null; + } + + function setControlState(controls, value) { + controls.forEach((control) => { + control.checked = Boolean(value); + }); + } + + function setBadge(key, label, status) { + document.querySelectorAll(`[data-consent-badge="${key}"]`).forEach((badge) => { + badge.textContent = label; + badge.dataset.state = status; + }); + } + + function formatDate(dateString) { + if (!dateString) { + return '—'; + } + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return '—'; + } + return new Intl.DateTimeFormat('fr-FR', { + dateStyle: 'short', + timeStyle: 'short' + }).format(date); + } + + function updateSummaryText() { + const summary = state.prefs.personalization || state.prefs.audience + ? `Réglages actifs : essentiels, ${state.prefs.personalization ? 'personnalisation' : 'personnalisation coupée'} et ${state.prefs.audience ? 'mesure locale activée' : 'mesure locale désactivée'}.` + : 'Le site conserve uniquement l\'essentiel tant que vous n\'avez pas choisi d\'options supplémentaires.'; + + document.querySelectorAll('[data-consent-summary]').forEach((node) => { + node.textContent = summary; + }); + } + + function updateReminderLabel() { + if (!reopenLabel) { + return; + } + + if (state.prefs.personalization && state.prefs.audience) { + reopenLabel.textContent = 'Cookies : choix personnalisés'; + return; + } + + if (state.prefs.personalization || state.prefs.audience) { + reopenLabel.textContent = 'Cookies : options partielles'; + return; + } + + reopenLabel.textContent = 'Cookies : essentiels'; + } + + function updateAudiencePanel(store) { + const audienceEnabled = state.prefs.audience; + const visits = audienceEnabled && store ? Number(store.visits || 0) : 0; + const sections = audienceEnabled && store && Array.isArray(store.sections) ? store.sections.length : 0; + const lastVisit = audienceEnabled && store ? formatDate(store.lastVisit || null) : '—'; + const statusText = audienceEnabled + ? 'La mesure locale d\'audience est activée sur cet appareil. Les compteurs ci-dessous sont stockés uniquement dans votre navigateur.' + : 'La mesure locale d\'audience est désactivée. Aucun service tiers n\'est utilisé.'; + + document.querySelectorAll('[data-audience-status]').forEach((node) => { + node.textContent = statusText; + }); + document.querySelectorAll('[data-audience-visits]').forEach((node) => { + node.textContent = String(visits); + }); + document.querySelectorAll('[data-audience-sections]').forEach((node) => { + node.textContent = String(sections); + }); + document.querySelectorAll('[data-audience-last-visit]').forEach((node) => { + node.textContent = lastVisit; + }); + } + + function showToast(message) { + if (!toastStack) { + return; + } + + const toast = document.createElement('div'); + toast.className = 'app-toast'; + toast.setAttribute('role', 'status'); + toast.textContent = message; + toastStack.appendChild(toast); + + requestAnimationFrame(() => { + toast.classList.add('is-visible'); + }); + + window.setTimeout(() => { + toast.classList.remove('is-visible'); + window.setTimeout(() => { + toast.remove(); + }, 220); + }, 3200); + } + + function readAudienceStore() { + const parsed = safeParse(storageGet(STORAGE_KEYS.audience)); + if (!parsed || typeof parsed !== 'object') { + return { + visits: 0, + sections: [], + lastVisit: null + }; + } + return { + visits: Number(parsed.visits || 0), + sections: Array.isArray(parsed.sections) ? parsed.sections : [], + lastVisit: parsed.lastVisit || null + }; + } + + function saveAudienceStore(store) { + storageSet(STORAGE_KEYS.audience, JSON.stringify(store)); + } + + function refreshPersonalizationUI() { + const stored = safeParse(storageGet(STORAGE_KEYS.personalization)) || {}; + if (!scrollHint) { + return; + } + + if (!state.prefs.personalization) { + scrollHint.hidden = false; + return; + } + + scrollHint.hidden = stored.scrollHintDismissed === true; + } + + function applyAudienceState() { + if (!state.prefs.audience) { + storageRemove(STORAGE_KEYS.audience); + state.audienceCountedThisSession = false; + updateAudiencePanel(null); + return; + } + + const store = readAudienceStore(); + if (!state.audienceCountedThisSession) { + store.visits += 1; + store.lastVisit = new Date().toISOString(); + state.audienceCountedThisSession = true; + } + + if (state.visibleSections.size > 0) { + const merged = new Set([...(store.sections || []), ...state.visibleSections]); + store.sections = Array.from(merged); + } + + saveAudienceStore(store); + updateAudiencePanel(store); + } + + function applyPrefs(prefs) { + state.prefs = normalizePrefs(prefs); + document.body.dataset.consentPersonalization = state.prefs.personalization ? 'on' : 'off'; + document.body.dataset.consentAudience = state.prefs.audience ? 'on' : 'off'; + + setControlState(personalizationControls, state.prefs.personalization); + setControlState(audienceControls, state.prefs.audience); + + setBadge('essential', 'Toujours actif', 'locked'); + setBadge('personalization', state.prefs.personalization ? 'Activée' : 'Désactivée', state.prefs.personalization ? 'on' : 'off'); + setBadge('audience', state.prefs.audience ? 'Activée' : 'Désactivée', state.prefs.audience ? 'on' : 'off'); + + if (!state.prefs.personalization) { + storageRemove(STORAGE_KEYS.personalization); + } + + refreshPersonalizationUI(); + applyAudienceState(); + updateSummaryText(); + updateReminderLabel(); + } + + function openBanner() { + if (!banner) { + return; + } + banner.hidden = false; + if (reopenButton) { + reopenButton.setAttribute('aria-expanded', 'true'); + } + } + + function closeBanner() { + if (!banner) { + return; + } + banner.hidden = true; + if (reopenButton) { + reopenButton.setAttribute('aria-expanded', 'false'); + } + } + + function savePrefs(nextPrefs, notice) { + const normalized = normalizePrefs(nextPrefs); + normalized.timestamp = new Date().toISOString(); + setCookie(COOKIE_NAME, JSON.stringify(normalized), COOKIE_MAX_AGE); + applyPrefs(normalized); + closeBanner(); + showToast(notice); + } + + if (dismissScrollHint && scrollHint) { + dismissScrollHint.addEventListener('click', () => { + scrollHint.hidden = true; + if (state.prefs.personalization) { + storageSet(STORAGE_KEYS.personalization, JSON.stringify({ + scrollHintDismissed: true, + updatedAt: new Date().toISOString() + })); + showToast('Le rappel mobile a été masqué pour cet appareil.'); + } + }); + } + + consentActionButtons.forEach((button) => { + button.addEventListener('click', () => { + const action = button.dataset.cookieAction; + + if (action === 'accept') { + savePrefs({ + essential: true, + personalization: true, + audience: true + }, 'Toutes les options facultatives ont été activées.'); + return; + } + + if (action === 'reject') { + savePrefs({ + essential: true, + personalization: false, + audience: false + }, 'Seuls les cookies essentiels sont conservés.'); + return; + } + + if (action === 'save') { + savePrefs({ + essential: true, + personalization: personalizationControls.some((control) => control.checked), + audience: audienceControls.some((control) => control.checked) + }, 'Vos préférences cookies ont été enregistrées.'); + } + }); }); + + [reopenButton, footerCookieSettings].forEach((trigger) => { + if (!trigger) { + return; + } + trigger.addEventListener('click', () => { + openBanner(); + }); + }); + + if ('IntersectionObserver' in window && trackedSections.length > 0) { + const observer = new IntersectionObserver((entries) => { + let changed = false; + entries.forEach((entry) => { + if (!entry.isIntersecting) { + return; + } + + const id = entry.target.id || entry.target.dataset.trackSection || ''; + if (!id) { + return; + } + + if (!state.visibleSections.has(id)) { + state.visibleSections.add(id); + changed = true; + } + }); + + if (changed && state.prefs.audience) { + applyAudienceState(); + } + }, { + threshold: 0.45 + }); + + trackedSections.forEach((section) => observer.observe(section)); + } + + const savedPrefs = readSavedPrefs(); + if (savedPrefs) { + applyPrefs(savedPrefs); + closeBanner(); + } else { + applyPrefs({ essential: true, personalization: false, audience: false }); + openBanner(); + } }); diff --git a/index.php b/index.php index 7205f3d..ee4ec75 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,378 @@ 'Voir le direct immédiatement', + 'copy' => 'Accédez au widget “en ce moment” pour savoir ce qui passe maintenant sur les chaînes les plus consultées.', + 'href' => '#widget-section', + ], + [ + 'title' => 'Comparer le prime time', + 'copy' => 'Parcourez les mots-clés, les chaînes et les catégories éditoriales pour préparer votre soirée TV.', + 'href' => '#guide', + ], + [ + 'title' => 'Repérer un film, une série ou un match', + 'copy' => 'Les sections éditoriales ciblent les recherches les plus fréquentes liées au programme TV ce soir.', + 'href' => '#recherches', + ], + [ + 'title' => 'Contrôler vos cookies', + 'copy' => 'Utilisez la bannière et le bouton flottant en bas à gauche pour modifier vos choix à tout moment.', + 'href' => '#legal', + ], +]; + +$benefits = [ + 'Programme TV ce soir et en ce moment sur une page simple, rapide et lisible.', + 'Accès prioritaire aux chaînes TNT, aux grandes chaînes nationales et aux rendez-vous de prime time.', + 'Lecture mobile optimisée avec défilement horizontal visible sous le widget TV.', + 'Conformité vie privée avec bannière de consentement, réglages persistants et documents légaux dédiés.', +]; + +$channelCards = [ + ['name' => 'TF1', 'copy' => 'Programme TF1 ce soir : divertissement, fiction populaire, sport et grands événements en prime time.'], + ['name' => 'France 2', 'copy' => 'Programme France 2 ce soir : séries, magazines, infos, culture et soirées événementielles.'], + ['name' => 'France 3', 'copy' => 'Programme France 3 ce soir : patrimoine, régions, cinéma français et documentaires accessibles.'], + ['name' => 'Canal+', 'copy' => 'Programme Canal+ ce soir : cinéma, créations originales, sport premium et événements exclusifs.'], + ['name' => 'M6', 'copy' => 'Programme M6 ce soir : divertissements, magazines, séries et rendez-vous familiaux.'], + ['name' => 'Arte', 'copy' => 'Programme Arte ce soir : films d’auteur, documentaires, culture, histoire et créations européennes.'], + ['name' => 'France 5', 'copy' => 'Programme France 5 ce soir : documentaires, débats, société, science et découverte.'], + ['name' => 'C8 / CStar', 'copy' => 'Programme C8 et CStar ce soir : magazines, talk-shows, divertissements et musique.'], + ['name' => 'TMC / TFX', 'copy' => 'Programme TMC et TFX ce soir : films, séries populaires, talks et télé-réalité.'], + ['name' => 'W9 / 6ter', 'copy' => 'Programme W9 et 6ter ce soir : cinéma, séries, clips, magazines et programmes feel-good.'], + ['name' => 'RMC Story / RMC Découverte', 'copy' => 'Programme RMC Story et RMC Découverte : enquêtes, découverte, mécanique, société et histoire.'], + ['name' => 'Gulli / Jeunesse', 'copy' => 'Programme jeunesse ce soir : dessins animés, films familiaux et rendez-vous enfants.'], +]; + +$keywordBadges = [ + 'programme tv ce soir', 'programme télé ce soir', 'ce soir à la télé', 'programme tv en ce moment', 'programme tnt ce soir', + 'film ce soir', 'série ce soir', 'match ce soir', 'sport à la tv', 'documentaire ce soir', 'émission ce soir', + 'prime time', 'deuxième partie de soirée', 'direct tv', 'grille tv', 'chaînes tnt', 'programme tf1 ce soir', + 'programme france 2 ce soir', 'programme m6 ce soir', 'programme arte ce soir', 'télé ce soir', 'que regarder ce soir', + 'programme canal+', 'programme france 3', 'programme france 5', 'programme tmc', 'programme w9', 'programme c8', +]; + +$faqItems = [ + [ + 'question' => 'Où voir rapidement le programme TV ce soir ?', + 'answer' => 'La zone “Programme TV en ce moment” placée en haut de la page donne un accès direct au widget, puis les sections éditoriales vous aident à comparer les chaînes, les genres et les créneaux du soir.', + ], + [ + 'question' => 'Comment savoir ce qu\'il y a à la télé en ce moment ?', + 'answer' => 'Le widget affiche le direct des chaînes les plus consultées. Sur mobile et tablette, un défilement horizontal est prévu sous le widget pour conserver une lecture confortable.', + ], + [ + 'question' => 'Quelles chaînes sont mises en avant sur la page ?', + 'answer' => 'La page couvre les grandes chaînes généralistes et TNT recherchées le plus souvent : TF1, France 2, France 3, M6, Arte, Canal+, France 5, TMC, W9, C8 et d’autres chaînes populaires.', + ], + [ + 'question' => 'Quels cookies sont utilisés sur programmetelecesoir.fr ?', + 'answer' => 'Cette première version dépose un cookie essentiel pour mémoriser vos choix de consentement. Les autres fonctions optionnelles sont désactivées par défaut et peuvent être activées ou refusées depuis la bannière ou le bouton flottant.', + ], + [ + 'question' => 'Qui contacter pour les questions de confidentialité ?', + 'answer' => 'Le DPO déclaré sur le site est M LORENTE CHRISTOPHE, 7 rue Lucien Deneau – 28300 Mainvilliers, téléphone 06 58 22 59 16. Les détails figurent aussi dans la politique de confidentialité.', + ], +]; + +$faqSchema = [ + '@context' => 'https://schema.org', + '@type' => 'FAQPage', + 'mainEntity' => array_map(static function (array $item): array { + return [ + '@type' => 'Question', + 'name' => $item['question'], + 'acceptedAnswer' => [ + '@type' => 'Answer', + 'text' => $item['answer'], + ], + ]; + }, $faqItems), +]; + +$organizationSchema = [ + '@context' => 'https://schema.org', + '@type' => 'Organization', + 'name' => $site['domain'], + 'url' => $site['base_url'] . '/', + 'description' => $fallbackDescription, + 'address' => [ + '@type' => 'PostalAddress', + 'streetAddress' => '7 rue Lucien Deneau', + 'postalCode' => '28300', + 'addressLocality' => 'Mainvilliers', + 'addressCountry' => 'FR', + ], + 'contactPoint' => [ + '@type' => 'ContactPoint', + 'contactType' => 'data protection officer', + 'name' => $site['dpo_name'], + 'telephone' => '+33 6 58 22 59 16', + 'availableLanguage' => ['fr'], + ], +]; ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + + + +
+
+
+
+
+ Programme TV • France • Mise à jour éditoriale du +

Programme TV ce soir et en ce moment : votre accès rapide aux chaînes et aux rendez-vous du soir

+

Programmetelecesoir.fr centralise l'intention de recherche la plus importante du secteur : savoir ce qu'il y a à la télé ce soir et ce qui passe en ce moment. La page met immédiatement le widget TV en avant, puis complète la lecture avec un guide éditorial pensé pour le prime time, les films, les séries, le sport, les documentaires et les chaînes TNT les plus consultées.

+ +
+ +
+ + +
+ +
+
+
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- Page updated: (UTC) -
+ + +
+
+
+
+
+ Accès direct +

Voir ce qu'il y a à la télé maintenant

+

Le widget est volontairement placé juste sous le titre pour répondre immédiatement à la requête “programme TV en ce moment”. Sur mobile et tablette, la zone est contenue dans un défilement horizontal visible en bas.

+
+ +
+ +
+
+
+ Programme TV

Source TV : Programme TV

+
+
+
+
+ Astuce mobile : si toute la largeur n'est pas visible, faites glisser horizontalement la zone du widget. Ce rappel peut être mémorisé uniquement si la personnalisation est activée. +
+ +
+
+
+
+
+ +
+
+
+
+ Guide TV du soir +

Une longue page SEO orientée sur les recherches réelles des internautes

+

Cette page cible les requêtes majeures du secteur : programme TV ce soir, programme télé ce soir, ce soir à la télé, programme TV TNT, film ce soir, série ce soir, match ce soir, documentaire ce soir et programme TV en ce moment.

+
+
+
+

Si vous cherchez quoi regarder ce soir, la logique de programmetelecesoir.fr est simple : offrir d'abord le direct, puis un accompagnement éditorial lisible. En un seul endroit, vous pouvez surveiller le programme TV des grandes chaînes, comparer le prime time, repérer un film à la télévision ce soir, vérifier la présence d'une série, d'un documentaire, d'un match, d'une émission d'information, d'un magazine ou d'un divertissement familial.

+

La structure éditoriale de la page reprend les formulations les plus recherchées autour du programme télé ce soir : programme TV TNT ce soir, programme TV maintenant, télé ce soir, programme des chaînes en soirée, directs TV, grille TV du soir, deuxième partie de soirée, films et séries du prime time. L'objectif n'est pas d'encombrer la lecture, mais de répondre précisément aux intentions qui reviennent le plus souvent sur mobile comme sur desktop.

+

Les internautes qui tapent programme TF1 ce soir, programme France 2 ce soir, programme M6 ce soir ou programme Arte ce soir recherchent généralement un accès immédiat, sans détour, avec une page rapide, claire et fiable. C'est pour cette raison que le site priorise la lisibilité, le widget en tête de page, des cartes chaînes synthétiques, une FAQ structurée et des documents légaux facilement accessibles.

+
+ +
+
+
+
+ +
+
+
+
+ Chaînes recherchées +

Programme TV du soir chaîne par chaîne

+

La page reprend les grandes intentions liées aux principales chaînes consultées en France pour le programme de ce soir, le direct actuel, les films, les séries, les magazines, la culture et le sport.

+
+
+ +
+
+

+
+ +
+
+
+
+ +
+
+
+
+ Intentions fréquentes +

Mots-clés éditoriaux et besoins de lecture

+

Pour couvrir un maximum d'intentions, la page déploie un champ lexical riche mais propre, sans agressivité visuelle. Les badges ci-dessous représentent les expressions que le visiteur recherche le plus souvent avant de choisir son programme.

+
+
+ + + +
+
+
+

Comment choisir rapidement le bon programme ce soir ?

+
    +
  1. Commencez par le widget “en ce moment” pour identifier l'offre en direct.
  2. +
  3. Repérez ensuite la chaîne qui correspond à votre envie : film, série, documentaire, sport, culture ou divertissement.
  4. +
  5. Consultez les cartes chaînes pour accélérer la comparaison entre TF1, France 2, M6, Arte, Canal+ et TNT.
  6. +
  7. Utilisez la FAQ et les liens légaux si vous souhaitez comprendre la confidentialité, les cookies et les règles d'utilisation.
  8. +
+
+
+

Pourquoi cette page fonctionne bien sur mobile ?

+

Le design a été allégé pour rester très lisible : navigation compacte, sections régulières, contraste fort, cartes sobres, bouton flottant pour les cookies et zone horizontale spécifique sous le widget afin d'éviter toute coupure sur smartphone ou tablette. Le résultat est plus confortable pour les utilisateurs qui cherchent vite “ce soir à la télé” depuis leur mobile.

+

Le site évite aussi les scripts marketing et les effets visuels inutiles. Cela aide à conserver une expérience rapide, rassurante et facile à parcourir, ce qui renforce la consultation répétée lorsqu'on veut simplement savoir quel programme TV regarder ce soir.

+
+
+
+
+
+ +
+
+
+
+ FAQ +

Questions fréquentes sur le programme TV ce soir

+

Les réponses ci-dessous renforcent l'intention utilisateur tout en gardant une lecture utile, concise et structurée.

+
+
+ $item): ?> +
+

+ +

+
+
+ +
+
+
+ +
+
+
+
+ + +
+ + + + + diff --git a/politique-confidentialite.php b/politique-confidentialite.php new file mode 100644 index 0000000..0ecc770 --- /dev/null +++ b/politique-confidentialite.php @@ -0,0 +1,118 @@ + + + + + + + + +
+ + +
+
+ +
+
+
+ + + + + diff --git a/politique-cookies.php b/politique-cookies.php new file mode 100644 index 0000000..653f3e8 --- /dev/null +++ b/politique-cookies.php @@ -0,0 +1,119 @@ + + + + + + + + +
+ + +
+
+ +
+
+ +
+
+ +
+
+
+ + + + + diff --git a/reglement.php b/reglement.php new file mode 100644 index 0000000..d22547b --- /dev/null +++ b/reglement.php @@ -0,0 +1,92 @@ + + + + + + + + +
+ + +
+
+ +
+
+
+ + + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..5bb998e --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://programmetelecesoir.fr/sitemap.xml diff --git a/site.php b/site.php new file mode 100644 index 0000000..398d896 --- /dev/null +++ b/site.php @@ -0,0 +1,262 @@ + 'programmetelecesoir.fr', + 'project_name' => $_SERVER['PROJECT_NAME'] ?? 'programmetelecesoir.fr', + 'project_description' => $_SERVER['PROJECT_DESCRIPTION'] ?? '', + 'project_image_url' => $_SERVER['PROJECT_IMAGE_URL'] ?? '', + 'base_url' => $scheme . '://' . $host, + 'asset_version' => site_asset_version(), + 'owner_name' => 'M LORENTE CHRISTOPHE', + 'dpo_name' => 'M LORENTE CHRISTOPHE', + 'dpo_address' => '7 rue Lucien Deneau – 28300 Mainvilliers', + 'dpo_phone' => '06 58 22 59 16', + 'host_name' => 'FLATLOGIC.COM', + ]; + + return $settings; +} + +function e(mixed $value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +} + +function site_current_path(): string +{ + $requestUri = $_SERVER['REQUEST_URI'] ?? '/'; + $path = strtok($requestUri, '?'); + return $path !== false && $path !== '' ? $path : '/'; +} + +function site_asset_url(string $relativePath): string +{ + return $relativePath . '?v=' . rawurlencode(site_asset_version()); +} + +function render_site_head(string $pageTitle, string $fallbackDescription, string $keywords = '', bool $noindex = false): void +{ + $site = site_settings(); + $projectDescription = $site['project_description']; + $projectImageUrl = $site['project_image_url']; + $canonical = $site['base_url'] . site_current_path(); + ?> + + + <?= e($pageTitle) ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' . e($label) . ''; +} + +function render_site_nav(string $current = 'home'): void +{ + ?> + + + + + + + + +
+ + + + + + + https://programmetelecesoir.fr/ + daily + 1.0 + +