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…
+
+
+
+
+
JavaScript est nécessaire pour la gestion fine des cookies et pour le chargement du widget TV.
+
+
+
+
+
+
+
+
Programme TV • France • Mise à jour éditoriale du = e($updatedAt) ?>
+
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.
+
+
+
+
+
+ = e($benefit) ?>
+
+
+
+
+
+
+
+ Préférences
+
Consentement en direct
+
+ Le site conserve uniquement l'essentiel tant que vous n'avez pas choisi d'options supplémentaires.
+
+ Essentiels Toujours actif
+ Personnalisation Désactivée
+ Audience locale Désactivée
+
+
+
+
+ Mesure locale
+
Effet visible des toggles
+
+ La mesure locale d'audience est désactivée. Aucun service tiers n'est utilisé.
+
+
+ 0
+ visites locales
+
+
+ 0
+ sections vues
+
+
+ —
+ dernière activité
+
+
+
+
+
+ Conformité
+
DPO et hébergement
+
+ = e($site['dpo_name']) ?> = e($site['dpo_address']) ?> Tél. = e($site['dpo_phone']) ?>
+ Hébergeur indiqué sur le site : = e($site['host_name']) ?> .
+
+
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
-
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
+
+
+
+
+
+
+
+
+
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.
+
+
+
+
+ = e($channel['name']) ?>
+ = e($channel['copy']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
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.
+
+
+
+ = e($keyword) ?>
+
+
+
+
+ Comment choisir rapidement le bon programme ce soir ?
+
+ Commencez par le widget “en ce moment” pour identifier l'offre en direct.
+ Repérez ensuite la chaîne qui correspond à votre envie : film, série, documentaire, sport, culture ou divertissement.
+ Consultez les cartes chaînes pour accélérer la comparaison entre TF1, France 2, M6, Arte, Canal+ et TNT.
+ Utilisez la FAQ et les liens légaux si vous souhaitez comprendre la confidentialité, les cookies et les règles d'utilisation.
+
+
+
+ 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): ?>
+
+
+
+
+ = e($item['answer']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Confiance & conformité
+
Politique de cookies, confidentialité et règlement
+
Les documents juridiques ont été séparés en pages dédiées pour rester clairs et faciles à consulter. Le bouton flottant en bas à gauche rouvre les préférences cookies à tout moment.
+
+
+
+ Politique de cookies
+ Détail du cookie essentiel de consentement, des options facultatives et du rôle du widget TV nécessaire au service.
+ Lire la politique de cookies
+
+
+ Politique de confidentialité
+ Présentation du responsable, du DPO, de l'hébergement, des données traitées et des droits des personnes.
+ Lire la politique de confidentialité
+
+
+ Règlement d'utilisation
+ Cadre d'usage du site, responsabilité, disponibilité, propriété intellectuelle et rappel des règles de consultation.
+ Lire le règlement
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Accueil
+ Politique de confidentialité
+
+
+
Protection des données
+
Politique de confidentialité
+
Cette politique décrit les traitements de données susceptibles d'intervenir lors de la consultation de programmetelecesoir.fr, les finalités poursuivies, les durées de conservation, les destinataires et vos droits.
+
+
+
+
+
+
+
+
+
+ 1. Responsable du traitement
+ Le responsable du traitement et DPO déclaré pour le site est = e($site['dpo_name']) ?> , domicilié au = e($site['dpo_address']) ?> , joignable au = e($site['dpo_phone']) ?> .
+
+ 2. Hébergement
+ Le site est hébergé par = e($site['host_name']) ?> . L'hébergement technique implique le traitement de journaux serveur et d'informations de connexion nécessaires à la sécurité, à la disponibilité du service et à la résolution d'éventuels incidents.
+
+ 3. Catégories de données susceptibles d'être traitées
+ Le site a été conçu pour limiter au strict nécessaire la collecte de données. Dans cette version, les traitements potentiels concernent principalement :
+
+ les données techniques de connexion indispensables au fonctionnement du site et à la sécurité de l'infrastructure (adresse IP, user-agent, requêtes HTTP, horodatage, erreurs techniques) ;
+ vos préférences de consentement, enregistrées dans le cookie ptcs_consent ;
+ les informations échangées avec le prestataire du widget TV, nécessaires à l'affichage du service “en ce moment” lorsque vous consultez la page d'accueil ;
+ les préférences locales optionnelles d'interface ou le compteur local d'audience, si vous activez ces catégories dans la bannière.
+
+
+ 4. Finalités et bases juridiques
+
+
+
+
+ Traitement
+ Finalité
+ Base juridique
+
+
+
+
+ Logs techniques et sécurité
+ Garantir la stabilité, la sécurité et la disponibilité du site.
+ Intérêt légitime du responsable et nécessité opérationnelle.
+
+
+ Cookie de consentement
+ Mémoriser vos préférences et prouver le choix exprimé.
+ Intérêt légitime / conformité en matière de gestion du consentement.
+
+
+ Chargement du widget TV
+ Afficher le service principal de programme TV demandé par l'utilisateur.
+ Nécessité liée au service expressément sollicité.
+
+
+ Personnalisation et audience locale
+ Améliorer le confort de navigation uniquement si vous l'autorisez.
+ Consentement.
+
+
+
+
+
+ 5. Destinataires
+ Les destinataires potentiels des données techniques sont le responsable du site, l'hébergeur = e($site['host_name']) ?> pour l'exploitation de l'infrastructure et, pour le service TV embarqué, le fournisseur du widget nécessaire à l'affichage du programme.
+
+ 6. Durées de conservation
+ Le cookie de consentement est conservé 6 mois. Les préférences purement locales et facultatives sont effacées si vous désactivez la catégorie correspondante. Les durées de conservation des journaux techniques dépendent des règles d'exploitation et de sécurité de l'hébergement.
+
+
+
+ Vos droits
+ Vous disposez des droits d'accès, de rectification, d'effacement, de limitation, d'opposition et, le cas échéant, de portabilité. Vous pouvez également retirer votre consentement pour les options facultatives à tout moment.
+
+
+ Réclamation
+ En cas de difficulté persistante, vous pouvez contacter le DPO puis, si nécessaire, saisir l'autorité de contrôle compétente.
+
+
+ Mesures de minimisation
+ Le site ne propose ni espace membre, ni formulaire de collecte marketing, ni profilage publicitaire dans cette version initiale. L'objectif est de maintenir un traitement réduit et lisible.
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Accueil
+ Politique de cookies
+
+
+
Document légal
+
Politique de cookies de programmetelecesoir.fr
+
Cette politique explique de manière détaillée quels cookies et traceurs sont utilisés sur le site, à quoi ils servent, combien de temps ils sont conservés et comment vous pouvez modifier vos préférences à tout moment.
+
+
+
+
+
+
+
+
+
Inventaire
+
Liste des cookies maîtrisés par le site
+
La présente version du site limite volontairement les cookies first-party à l'essentiel. Aucun cookie publicitaire n'est mis en place par programmetelecesoir.fr.
+
+
+
+
+
+ Nom
+ Type
+ Finalité
+ Durée
+ Nécessaire
+
+
+
+
+ ptcs_consent
+ Cookie first-party
+ Mémoriser vos choix de consentement (essentiels, personnalisation, audience locale) et éviter de vous redemander vos préférences à chaque visite.
+ 6 mois
+ Oui
+
+
+ Aucun autre cookie first-party
+ —
+ La version actuelle ne déploie ni cookie publicitaire, ni cookie de retargeting, ni solution d'analyse tierce.
+ —
+ —
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1. Pourquoi une bannière cookies sur ce site ?
+ La bannière permet d'expliquer clairement la présence du cookie essentiel de consentement et de vous donner la main sur les traitements optionnels disponibles dans cette première version. Le bouton “Continuer sans accepter” enregistre un réglage minimal : seuls les éléments essentiels restent actifs. Le bouton “Tout accepter” active les préférences optionnelles prévues. Le bouton “Enregistrer mes choix” tient compte précisément de vos toggles.
+
+ 2. À quoi correspondent les catégories proposées ?
+
+ Essentiels : ils assurent la mémorisation de votre choix de confidentialité et le fonctionnement minimum de l'interface de consentement. Cette catégorie ne peut pas être désactivée.
+ Personnalisation : elle permet de mémoriser localement des préférences d'interface sur votre appareil, par exemple le rappel concernant le défilement horizontal du widget sur mobile.
+ Mesure locale d'audience : elle active uniquement un compteur local dans votre navigateur pour démontrer l'effet du réglage. Aucun service tiers d'analyse n'est branché dans cette version.
+
+
+ 3. Et le widget TV externe ?
+ Le service principal du site consiste à afficher un programme TV “en ce moment”. Le widget TV est donc traité comme un élément nécessaire à la prestation expressément attendue sur la page d'accueil. Il est chargé depuis un service tiers, tv-programme.com , afin d'afficher les grilles et contenus correspondants. Lorsqu'un service tiers est sollicité, il peut techniquement recevoir certaines informations de connexion liées à votre navigateur. Pour connaître les règles exactes applicables à ce prestataire, il convient également de consulter sa propre documentation.
+
+ 4. Comment modifier vos choix plus tard ?
+ Un bouton flottant en bas à gauche de l'écran permet de rouvrir à tout moment le centre de préférences. Les toggles sont resynchronisés avec votre choix enregistré afin de vous permettre de l'ajuster immédiatement, sans rechargement complexe ni perte de navigation.
+
+
+
+ Durée de conservation
+ Le cookie de consentement est conservé pendant 6 mois , puis vos préférences peuvent être redemandées. Les traces locales purement optionnelles sont effacées lorsque vous désactivez la catégorie correspondante.
+
+
+ Base légale
+ Les traceurs essentiels reposent sur l'intérêt légitime et la nécessité de conserver la preuve de votre choix. Les fonctionnalités optionnelles ne sont activées qu'après votre action explicite.
+
+
+ Contact DPO
+ = e($site['dpo_name']) ?> = e($site['dpo_address']) ?> Tél. = e($site['dpo_phone']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reglement.php b/reglement.php
new file mode 100644
index 0000000..d22547b
--- /dev/null
+++ b/reglement.php
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Accueil
+ Règlement d'utilisation
+
+
+
Cadre d'usage
+
Règlement d'utilisation du site
+
Le présent règlement encadre l'utilisation de programmetelecesoir.fr, précise l'objet du service, les responsabilités respectives, les règles de disponibilité et les bonnes pratiques applicables à la consultation du contenu.
+
+
+
+
+
+
+
+
+
+ 1. Objet du site
+ Programmetelecesoir.fr est une page éditoriale conçue pour faciliter l'accès au programme TV ce soir et au direct en ce moment. Le site met en avant un widget externe nécessaire au service principal, complété par des contenus éditoriaux, des sections d'aide et des documents juridiques.
+
+ 2. Conditions d'accès
+ L'accès au site est libre, sous réserve d'une connexion internet compatible. L'utilisateur s'engage à ne pas détourner l'usage du site, à ne pas perturber son bon fonctionnement et à respecter les lois et règlements applicables lors de sa navigation.
+
+ 3. Propriété intellectuelle
+ Les textes, compositions, éléments graphiques, structures de mise en page et contenus créés pour programmetelecesoir.fr sont protégés par les règles applicables à la propriété intellectuelle. Les marques, logos, titres d'émissions, programmes ou contenus tiers restent la propriété de leurs ayants droit respectifs.
+
+ 4. Services tiers et sources externes
+ Le site s'appuie sur un widget TV embarqué fourni par un service externe afin d'afficher l'information “en ce moment”. La disponibilité, l'exactitude et l'éventuelle évolution de ce service tiers relèvent également de son éditeur. Le site présente clairement la source du widget au sein de la page d'accueil.
+
+ 5. Disponibilité et maintenance
+ Le responsable s'efforce de maintenir le site accessible et fonctionnel. Des interruptions temporaires peuvent intervenir pour maintenance, mise à jour, incident technique, saturation du réseau ou indisponibilité du fournisseur tiers. Aucune garantie de disponibilité continue absolue n'est donnée.
+
+ 6. Responsabilité
+ Le site a une vocation d'information et d'accès rapide. L'utilisateur reste libre de vérifier le programme final auprès des diffuseurs concernés. Le responsable ne pourra être tenu responsable d'un changement de grille, d'une annulation de programme, d'une erreur provenant d'une source tierce ou d'une indisponibilité ponctuelle indépendante de sa volonté.
+
+ 7. Données personnelles et cookies
+ L'utilisation du site emporte l'application des documents dédiés à la confidentialité et aux cookies. La bannière de consentement, le bouton flottant de rappel et les pages légales sont conçus pour permettre à l'utilisateur de comprendre et d'ajuster ses choix simplement.
+
+ 8. Contact
+ Pour toute question relative au fonctionnement du site, à la confidentialité ou à l'exercice de vos droits, vous pouvez contacter le DPO déclaré : = e($site['dpo_name']) ?> , = e($site['dpo_address']) ?>, tél. = e($site['dpo_phone']) ?>.
+
+ 9. Hébergement
+ L'hébergement du site est assuré par = e($site['host_name']) ?> .
+
+ 10. Droit applicable
+ Le présent règlement est rédigé en français et s'interprète conformément au droit applicable au site et à son lieu d'exploitation. En cas de litige, une résolution amiable sera privilégiée avant toute démarche contentieuse.
+
+
+
+ Usage recommandé
+ Utilisez les ancres de navigation pour atteindre rapidement le widget, la FAQ et les documents légaux sans perdre le fil de lecture.
+
+
+ Vie privée
+ Le bouton flottant de rappel des cookies reste affiché en bas à gauche afin que le réglage soit toujours disponible.
+
+
+ Transparence
+ La source du widget TV est visible dans la page principale et les informations de contact du DPO sont répétées dans les documents juridiques.
+
+
+
+
+
+
+
+
+
+
+
+
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
+{
+ ?>
+
+
+
+
+
+
+
+ Cookies : essentiels
+
+
+
+
+
+
+
Préférences cookies
+
Gérez votre confidentialité
+
+
+
Le widget TV nécessaire au service reste actif pour afficher le programme en temps réel. Les traceurs optionnels sont désactivés par défaut tant que vous n'avez pas choisi.
+
+
+
+
Essentiels
+
Conservent votre choix de consentement et la sécurité minimale du site.
+
+
+
+
+
+
+
+
Personnalisation
+
Mémorise vos préférences d'interface sur cet appareil, comme le rappel de lecture mobile.
+
+
+
+
+
+
+
+
Mesure locale d'audience
+
Active un simple compteur local dans votre navigateur, sans service tiers ni publicité.
+
+
+
+
+
+
+
+ Continuer sans accepter
+ Enregistrer mes choix
+ Tout accepter
+
+
+
+
+
+
+
+
+
+
+
+ https://programmetelecesoir.fr/
+ daily
+ 1.0
+
+