diff --git a/app.rb b/app.rb index b1fc481..a73725e 100644 --- a/app.rb +++ b/app.rb @@ -4,9 +4,7 @@ require 'yaml' # Configure Sinatra set :port, ENV['PORT'] || 4567 -set :bind, '0.0.0.0' -set :public_folder, 'public' -set :views, 'views' +set :bind, '::' # Load responses from YAML file RESPONSES = YAML.load_file('responses.yml') @@ -217,3 +215,244 @@ get '/*' do } end end + +__END__ + +@@index + + + + + + No as a Service + + + +
+

No as a Service

+

The definitive multilingual API for negative responses

+ +
+ Currently in: <%= language_name %> + <% if language == 'no' %>🇳🇴<% end %> + <% if language == 'sv' %>🇸🇪<% end %> + <% if language == 'da' %>🇩🇰<% end %> + <% if language == 'is' %>🇮🇸<% end %> + <% if language == 'fi' %>🇫🇮<% end %> + <% if language == 'fo' %>🇫🇴<% end %> +
+ +
+ <%= no_response %> +
+ +
+ + + +
+ +
+

API Usage

+

JSON API: GET /api/no or GET /api/no/:lang

+

Languages: GET /languages

+

URL Parameter: ?lang=<%= language %>

+

Accept-Language header supported

+

Health Check: GET /health

+
+
+ + + + diff --git a/public/css/styles.css b/public/css/styles.css deleted file mode 100644 index 5e81881..0000000 --- a/public/css/styles.css +++ /dev/null @@ -1,342 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - color: white; -} - -.container { - text-align: center; - max-width: 700px; - padding: 2rem; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(10px); - border-radius: 20px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); - border: 1px solid rgba(255, 255, 255, 0.2); -} - -h1 { - font-size: 3rem; - margin-bottom: 1rem; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); -} - -.subtitle { - font-size: 1.2rem; - margin-bottom: 1rem; - opacity: 0.9; -} - -.language-info { - font-size: 1rem; - margin-bottom: 2rem; - padding: 0.5rem 1rem; - background: rgba(255, 255, 255, 0.1); - border-radius: 10px; - display: inline-block; -} - -.logo-container { - position: relative; - display: inline-block; - margin-bottom: 2rem; -} - -.logo-container::before { - content: ''; - position: absolute; - top: -20px; - left: -20px; - right: -20px; - bottom: -20px; - background: radial-gradient( - circle at 50% 50%, - transparent 60px, - rgba(255, 107, 107, 0.1) 80px, - transparent 120px - ); - border-radius: 50%; - animation: pulseRing 3s ease-in-out infinite; - pointer-events: none; -} - -.logo { - width: 140px; - height: 140px; - background: radial-gradient(circle at 30% 30%, #ff6b6b, #ee5a52, #dc3545); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 5rem; - text-shadow: 3px 3px 8px rgba(0, 0, 0, 0.6); - box-shadow: - 0 8px 32px rgba(255, 107, 107, 0.4), - inset 0 2px 10px rgba(255, 255, 255, 0.3), - inset 0 -2px 10px rgba(0, 0, 0, 0.2); - border: 3px solid rgba(255, 255, 255, 0.4); - position: relative; - animation: logoFloat 4s ease-in-out infinite; - cursor: pointer; - transition: all 0.3s ease; - overflow: hidden; -} - -.logo::before { - content: ''; - position: absolute; - top: -5px; - left: -5px; - right: -5px; - bottom: -5px; - background: conic-gradient( - from 0deg, - #ff6b6b, - #ff8e8e, - #ffb3b3, - #ff6b6b, - #ee5a52, - #ff6b6b - ); - border-radius: 50%; - z-index: -1; - animation: rotate 4s linear infinite; - opacity: 0.8; -} - -.logo::after { - content: ''; - position: absolute; - top: 15%; - left: 25%; - width: 30px; - height: 30px; - background: radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, transparent 70%); - border-radius: 50%; - filter: blur(8px); - animation: shimmer 3s ease-in-out infinite alternate; -} - -.logo:hover { - transform: scale(1.1) rotate(5deg); - box-shadow: - 0 12px 40px rgba(255, 107, 107, 0.6), - inset 0 2px 15px rgba(255, 255, 255, 0.4), - inset 0 -2px 15px rgba(0, 0, 0, 0.3); - animation-play-state: paused; -} - -.logo:active { - transform: scale(0.95); - animation: logoShake 0.5s ease-in-out; -} - -.no-response { - font-size: 4rem; - font-weight: bold; - color: #ff6b6b; - text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5); - margin: 2rem 0; - padding: 1rem; - background: rgba(255, 255, 255, 0.1); - border-radius: 15px; - border: 2px solid rgba(255, 107, 107, 0.3); - word-break: break-word; -} - -.controls { - margin: 2rem 0; - display: flex; - flex-wrap: wrap; - gap: 1rem; - justify-content: center; - align-items: center; -} - -.refresh-btn { - background: linear-gradient(45deg, #ff6b6b, #ff8e8e); - color: white; - border: none; - padding: 1rem 2rem; - font-size: 1.1rem; - border-radius: 50px; - cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3); -} - -.refresh-btn:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4); -} - -.language-selector { - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 0.5rem 1rem; - border-radius: 25px; - font-size: 1rem; - cursor: pointer; -} - -.language-selector option { - background: #444; - color: white; -} - -.api-info { - margin-top: 2rem; - padding: 1rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 10px; - font-size: 0.9rem; -} - -.api-info code { - background: rgba(0, 0, 0, 0.2); - padding: 0.2rem 0.5rem; - border-radius: 4px; - font-family: 'Courier New', monospace; -} - -.nordic-flag { - display: inline-block; - margin-left: 0.5rem; - font-size: 1.2em; -} - -/* Animations */ - -/* Floating animation */ -@keyframes logoFloat { - 0%, 100% { - transform: translateY(0px) rotate(0deg); - } - 25% { - transform: translateY(-8px) rotate(1deg); - } - 50% { - transform: translateY(-12px) rotate(0deg); - } - 75% { - transform: translateY(-6px) rotate(-1deg); - } -} - -/* Enhanced rotation animation */ -@keyframes rotate { - from { - transform: rotate(0deg) scale(1); - } - 25% { - transform: rotate(90deg) scale(1.02); - } - 50% { - transform: rotate(180deg) scale(1); - } - 75% { - transform: rotate(270deg) scale(1.02); - } - to { - transform: rotate(360deg) scale(1); - } -} - -/* Shimmer effect for the highlight */ -@keyframes shimmer { - 0% { - opacity: 0.3; - transform: scale(0.8); - } - 100% { - opacity: 0.8; - transform: scale(1.2); - } -} - -/* Shake animation on click */ -@keyframes logoShake { - 0%, 100% { transform: translateX(0); } - 10% { transform: translateX(-5px) rotate(-2deg); } - 20% { transform: translateX(5px) rotate(2deg); } - 30% { transform: translateX(-5px) rotate(-1deg); } - 40% { transform: translateX(5px) rotate(1deg); } - 50% { transform: translateX(-3px); } - 60% { transform: translateX(3px); } - 70% { transform: translateX(-2px); } - 80% { transform: translateX(2px); } - 90% { transform: translateX(-1px); } -} - -/* Pulsing ring animation */ -@keyframes pulseRing { - 0%, 100% { - transform: scale(0.8); - opacity: 0.3; - } - 50% { - transform: scale(1.1); - opacity: 0.1; - } -} - -/* Sparkle animation */ -@keyframes sparkleFloat { - 0% { - opacity: 1; - transform: translateY(0) scale(0); - } - 50% { - opacity: 1; - transform: translateY(-30px) scale(1); - } - 100% { - opacity: 0; - transform: translateY(-60px) scale(0) rotate(180deg); - } -} - -/* Responsive Design */ -@media (max-width: 600px) { - .container { - margin: 1rem; - padding: 1.5rem; - } - - h1 { - font-size: 2rem; - } - - .logo { - width: 100px; - height: 100px; - font-size: 3.5rem; - } - - .logo::after { - width: 20px; - height: 20px; - top: 20%; - left: 30%; - } - - .no-response { - font-size: 2.5rem; - } - - .controls { - flex-direction: column; - gap: 0.5rem; - } -} diff --git a/public/js/scripts.js b/public/js/scripts.js deleted file mode 100644 index b0c0e57..0000000 --- a/public/js/scripts.js +++ /dev/null @@ -1,109 +0,0 @@ -// Language selector functionality -function changeLanguage(lang) { - const url = new URL(window.location); - url.searchParams.set('lang', lang); - window.location.href = url.toString(); -} - -// Enhanced logo interaction -function logoClick() { - // Add a fun interaction when logo is clicked - const logo = document.querySelector('.logo'); - logo.style.animationPlayState = 'running'; - - // Get a new "no" response when logo is clicked - setTimeout(() => { - location.reload(); - }, 500); -} - -// Sparkle effects on hover -function createSparkles(element) { - const rect = element.getBoundingClientRect(); - const sparkles = 5; - - for (let i = 0; i < sparkles; i++) { - const sparkle = document.createElement('div'); - sparkle.innerHTML = '✨'; - sparkle.style.position = 'fixed'; - sparkle.style.left = (rect.left + Math.random() * rect.width) + 'px'; - sparkle.style.top = (rect.top + Math.random() * rect.height) + 'px'; - sparkle.style.fontSize = (Math.random() * 20 + 10) + 'px'; - sparkle.style.pointerEvents = 'none'; - sparkle.style.zIndex = '1000'; - sparkle.style.animation = 'sparkleFloat 2s ease-out forwards'; - - document.body.appendChild(sparkle); - - setTimeout(() => { - sparkle.remove(); - }, 2000); - } -} - -// Initialize event listeners when DOM is loaded -document.addEventListener('DOMContentLoaded', function() { - // Add sparkle effects on logo hover - const logo = document.querySelector('.logo'); - if (logo) { - logo.addEventListener('mouseenter', function() { - createSparkles(this); - }); - } - - // Add keyboard shortcuts - document.addEventListener('keydown', function(event) { - // Press 'R' to refresh and get new response - if (event.key.toLowerCase() === 'r' && !event.ctrlKey && !event.metaKey) { - location.reload(); - } - - // Press 'L' to focus language selector - if (event.key.toLowerCase() === 'l' && !event.ctrlKey && !event.metaKey) { - const languageSelector = document.querySelector('.language-selector'); - if (languageSelector) { - languageSelector.focus(); - } - } - }); - - // Add subtle animations to elements on scroll (if needed in future) - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - entry.target.style.opacity = '1'; - entry.target.style.transform = 'translateY(0)'; - } - }); - }); - - // Observe elements for animation (currently not needed but prepared for future use) - const animatedElements = document.querySelectorAll('.container > *'); - animatedElements.forEach(el => { - observer.observe(el); - }); -}); - -// Add some fun easter eggs -let clickCount = 0; -const logo = document.querySelector('.logo'); - -if (logo) { - logo.addEventListener('click', function() { - clickCount++; - - // Easter egg: Multiple rapid clicks - if (clickCount >= 5) { - this.style.animation = 'logoShake 0.5s ease-in-out, rotate 2s linear infinite'; - setTimeout(() => { - this.style.animation = 'logoFloat 4s ease-in-out infinite'; - }, 2000); - clickCount = 0; - } - }); -} - -// Reset click count after a delay -setInterval(() => { - clickCount = 0; -}, 3000); diff --git a/views/index.erb b/views/index.erb deleted file mode 100644 index 7ddb0f0..0000000 --- a/views/index.erb +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - No as a Service - - - -
-

No as a Service

-

The definitive multilingual API for negative responses

- -
- Currently in: <%= language_name %> - <% if language == 'no' %>🇳🇴<% end %> - <% if language == 'sv' %>🇸🇪<% end %> - <% if language == 'da' %>🇩🇰<% end %> - <% if language == 'is' %>🇮🇸<% end %> - <% if language == 'fi' %>🇫🇮<% end %> - <% if language == 'fo' %>🇫🇴<% end %> -
- -
- -
- -
- <%= no_response %> -
- -
- - - -
- -
-

API Usage

-

JSON API: GET /api/no or GET /api/no/:lang

-

Languages: GET /languages

-

URL Parameter: ?lang=<%= language %>

-

Accept-Language header supported

-

Health Check: GET /health

-
-
- - - -