Class: Fresco::Welcome
Instance Attribute Summary
Attributes inherited from Action
Instance Method Summary collapse
- #call(req = Request.new("", "", "")) ⇒ Object
-
#welcome_body ⇒ Object
Manual string concat (matches build_headers / Json.encode_string in runtime.rb) keeps each intermediate narrowly typed String for Spinel — heredocs and ‘<<~`-style literals don’t appear elsewhere in the Spinel-compiled surface, so we stick to the established ‘out += …` pattern.
Methods inherited from Action
#after_action, #before_action, #halt!, #handle, #initialize, #layout, #redirect_to, #render
Constructor Details
This class inherits a constructor from Fresco::Action
Instance Method Details
#call(req = Request.new("", "", "")) ⇒ Object
21 22 23 |
# File 'lib/fresco/runtime/welcome.rb', line 21 def call(req = Request.new("", "", "")) Response.html(200, welcome_body) end |
#welcome_body ⇒ Object
Manual string concat (matches build_headers / Json.encode_string in runtime.rb) keeps each intermediate narrowly typed String for Spinel — heredocs and ‘<<~`-style literals don’t appear elsewhere in the Spinel-compiled surface, so we stick to the established ‘out += …` pattern.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/fresco/runtime/welcome.rb', line 30 def welcome_body out = "<!doctype html>" out += "<html>" out += "<head>" out += "<meta charset=\"utf-8\">" out += "<title>Welcome to Fresco</title>" out += "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" out += "<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>" out += "<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap\" rel=\"stylesheet\">" out += "<script>" out += "(function () {" out += " var t = localStorage.getItem(\"theme\");" out += " if (!t) {" out += " t = (window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches) ? \"dark\" : \"light\";" out += " localStorage.setItem(\"theme\", t);" out += " }" out += " document.documentElement.setAttribute(\"data-theme\", t);" out += "})();" out += "</script>" out += "<style>" out += ":root { --bg: #f5f5f4; --fg: #1c1917; --muted: #78716c; --border: #e7e5e4; --code-bg: #e7e5e4; --accent: #0066cc; --status: #78716c; }" out += ":root[data-theme=\"dark\"] { --bg: #1c1917; --fg: #e7e5e4; --muted: #a8a29e; --border: #292524; --code-bg: #292524; --accent: #6ab0ff; --status: #a8a29e; }" out += "body { font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; max-width: 40rem; margin: 4rem auto; padding: 0 1.5rem; background: var(--bg); color: var(--fg); transition: background 0.15s, color 0.15s; }" out += "h1 { font-weight: 700; font-size: 2.2rem; margin: 0 0 0.5rem; line-height: 1.2; }" out += ".status { color: var(--status); font-size: 0.9rem; letter-spacing: 0.05em; text-transform: uppercase; }" out += ".lede { color: var(--muted); margin: 0 0 1.75rem; }" out += "p { line-height: 1.5; }" out += "code { background: var(--code-bg); padding: 0.1rem 0.3rem; border-radius: 3px; }" out += "a { color: var(--accent); }" out += "a:hover { text-decoration: underline; }" out += "hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }" out += "footer { color: var(--muted); font-size: 0.85rem; }" out += "#theme-toggle { position: fixed; top: 1rem; right: 1rem; background: transparent; border: 1px solid var(--border); color: var(--fg); border-radius: 6px; padding: 0.4rem; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }" out += "#theme-toggle:hover { background: var(--code-bg); }" out += "#theme-toggle svg { width: 18px; height: 18px; display: none; }" out += ":root[data-theme=\"light\"] #theme-toggle .icon-moon { display: inline-block; }" out += ":root[data-theme=\"dark\"] #theme-toggle .icon-sun { display: inline-block; }" # Trans pride coloring on the "Fresco" wordmark — 6 letters laid # out as a mirror of the 5-stripe flag (blue / pink / white / # pink / blue). The two middle letters take --fg as a stand-in # for "white" so they stay readable on both stone backgrounds # (renders as dark stone in light mode, near-white in dark mode). # Light mode uses deeper blue/pink variants so they don't wash # out against #f5f5f4; dark mode uses the canonical pastels which # pop on #1c1917. out += ".pride { font-weight: 700; letter-spacing: 0.04em; margin-left: 0.1em; }" out += ".pride span { display: inline-block; }" out += ".pride .p1 { color: #2dbfe0; }" out += ".pride .p2 { color: #ed7b95; }" out += ".pride .p3 { color: var(--fg); }" out += ".pride .p4 { color: var(--fg); }" out += ".pride .p5 { color: #ed7b95; }" out += ".pride .p6 { color: #2dbfe0; }" out += ":root[data-theme=\"dark\"] .pride .p1 { color: #5bcefa; }" out += ":root[data-theme=\"dark\"] .pride .p2 { color: #f5a9b8; }" out += ":root[data-theme=\"dark\"] .pride .p5 { color: #f5a9b8; }" out += ":root[data-theme=\"dark\"] .pride .p6 { color: #5bcefa; }" # Two CTA cards (Docs / GitHub). Whole card is the clickable <a>; # the icon, title, and "→" sit stacked inside. Flex layout, equal # width, collapses to a single column on narrow viewports so the # cards don't squeeze on mobile. out += ".cards { display: flex; gap: 0.875rem; margin: 0 0 1.75rem; }" out += ".cta { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 1.25rem; border: 2px solid var(--border); border-radius: 8px; background: transparent; color: var(--fg); text-decoration: none; transition: transform 0.15s, border-color 0.15s; }" # Trans pride hover border: two-layer background trick. The inner # solid layer is clipped to padding-box and covers the card body; # the gradient layer fills border-box and only shows through where # the (now-transparent) border sits. The gradient encodes the # 5-stripe flag (blue / pink / white / pink / blue) doubled # end-to-end so panning 200% bg-size from 0 to -100% loops # seamlessly — the second half is identical to the first. out += ".cta:hover { border-color: transparent; transform: translateY(-1px); background-image: linear-gradient(var(--code-bg), var(--code-bg)), linear-gradient(90deg, #5bcefa, #f5a9b8, #ffffff, #f5a9b8, #5bcefa, #f5a9b8, #ffffff, #f5a9b8, #5bcefa); background-size: auto, 200% 100%; background-position: 0 0, 0 0; background-origin: border-box; background-clip: padding-box, border-box; animation: prideShift 4s linear infinite; }" out += "@keyframes prideShift { to { background-position: 0 0, -100% 0; } }" out += ".cta svg { width: 28px; height: 28px; stroke: var(--accent); }" out += ".cta-title { font-weight: 700; font-size: 1.05rem; }" out += ".cta-sub { color: var(--muted); font-size: 0.88rem; }" out += ".cta:hover .cta-sub { color: var(--accent); }" out += "@media (max-width: 30rem) { .cards { flex-direction: column; } }" # Respect the OS reduce-motion preference — keep the rainbow # border, just stop panning it for folks who opted out. out += "@media (prefers-reduced-motion: reduce) { .cta:hover { animation: none; } }" out += "</style>" out += "</head>" out += "<body>" out += "<button id=\"theme-toggle\" aria-label=\"Toggle color theme\" title=\"Toggle color theme\">" out += "<svg class=\"icon-sun\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/></svg>" out += "<svg class=\"icon-moon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>" out += "</button>" out += "<div class=\"status\">200 · Welcome</div>" out += "<h1>You're on <span class=\"pride\"><span class=\"p1\">F</span><span class=\"p2\">r</span><span class=\"p3\">e</span><span class=\"p4\">s</span><span class=\"p5\">c</span><span class=\"p6\">o</span></span>.</h1>" out += "<p class=\"lede\">Spinel-compiled Ruby. Single binary. No runtime.</p>" # Lucide icons (MIT) inlined as SVG. book-open → Docs; # github → repo. Update href="#" with real URLs when ready. out += "<div class=\"cards\">" out += "<a class=\"cta\" href=\"https://github.com/afomera/fresco\">" out += "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 7v14\"/><path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>" out += "<div class=\"cta-title\">Documentation</div>" out += "<div class=\"cta-sub\">Get started →</div>" out += "</a>" out += "<a class=\"cta\" href=\"https://github.com/afomera/fresco\">" out += "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"/><path d=\"M9 18c-4.51 2-5-2-7-2\"/></svg>" out += "<div class=\"cta-title\">GitHub</div>" out += "<div class=\"cta-sub\">View source →</div>" out += "</a>" out += "</div>" out += "<p>Next step: add <code>root to: SomeAction</code> to <code>config/routes.rb</code>, then rebuild and reload.</p>" out += "<hr>" out += "<footer>Powered by Spinel + Fresco. Served from the fresco gem's <code>welcome</code> action.</footer>" out += "<script>" out += "(function () {" out += " document.getElementById(\"theme-toggle\").addEventListener(\"click\", function () {" out += " var cur = document.documentElement.getAttribute(\"data-theme\");" out += " var nxt = cur === \"dark\" ? \"light\" : \"dark\";" out += " document.documentElement.setAttribute(\"data-theme\", nxt);" out += " localStorage.setItem(\"theme\", nxt);" out += " });" out += "})();" out += "</script>" out += "</body>" out += "</html>" out end |