diff --git a/_astro/colors.590c63ff.css b/_astro/colors.590c63ff.css new file mode 100644 index 0000000..b7b3b9e --- /dev/null +++ b/_astro/colors.590c63ff.css @@ -0,0 +1 @@ +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-family:inherit;color:var(--tw-prose-kbd);box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%);font-size:.875em;border-radius:.3125rem;padding:.1875em .375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-kbd: #111827;--tw-prose-kbd-shadows: 17 24 39;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-kbd: #fff;--tw-prose-invert-kbd-shadows: 255 255 255;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-left:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-right:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.static{position:static}.relative{position:relative}.sticky{position:sticky}.top-8{top:2rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-8{margin-left:2rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.h-screen{height:100vh}.w-16{width:4rem}.w-64{width:16rem}.w-full{width:100%}.max-w-prose{max-width:65ch}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-outside{list-style-position:outside}.list-disc{list-style-type:disc}.list-square{list-style-type:square}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border-t{border-top-width:1px}.border-solid{border-style:solid}.border-violet-600{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity))}.bg-\[\#0A2744\]{--tw-bg-opacity: 1;background-color:rgb(10 39 68 / var(--tw-bg-opacity))}.bg-\[\#1D4061\]{--tw-bg-opacity: 1;background-color:rgb(29 64 97 / var(--tw-bg-opacity))}.bg-\[\#292568\]{--tw-bg-opacity: 1;background-color:rgb(41 37 104 / var(--tw-bg-opacity))}.bg-\[\#5A7997\]{--tw-bg-opacity: 1;background-color:rgb(90 121 151 / var(--tw-bg-opacity))}.bg-\[\#777777\]{--tw-bg-opacity: 1;background-color:rgb(119 119 119 / var(--tw-bg-opacity))}.bg-\[\#89A1B8\]{--tw-bg-opacity: 1;background-color:rgb(137 161 184 / var(--tw-bg-opacity))}.bg-\[\#dddddd\]{--tw-bg-opacity: 1;background-color:rgb(221 221 221 / var(--tw-bg-opacity))}.bg-\[\#eeeeee\]{--tw-bg-opacity: 1;background-color:rgb(238 238 238 / var(--tw-bg-opacity))}.bg-dr-blue-1{--tw-bg-opacity: 1;background-color:rgb(10 39 68 / var(--tw-bg-opacity))}.bg-dr-blue-2{--tw-bg-opacity: 1;background-color:rgb(29 64 97 / var(--tw-bg-opacity))}.bg-dr-blue-3{--tw-bg-opacity: 1;background-color:rgb(90 121 151 / var(--tw-bg-opacity))}.bg-dr-blue-4{--tw-bg-opacity: 1;background-color:rgb(137 161 184 / var(--tw-bg-opacity))}.bg-dr-gray-5{--tw-bg-opacity: 1;background-color:rgb(119 119 119 / var(--tw-bg-opacity))}.bg-dr-gray-8{--tw-bg-opacity: 1;background-color:rgb(221 221 221 / var(--tw-bg-opacity))}.bg-dr-gray-9{--tw-bg-opacity: 1;background-color:rgb(238 238 238 / var(--tw-bg-opacity))}.bg-dr-purp-drk{--tw-bg-opacity: 1;background-color:rgb(41 37 104 / var(--tw-bg-opacity))}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity))}.bg-sky-50{--tw-bg-opacity: 1;background-color:rgb(240 249 255 / var(--tw-bg-opacity))}.bg-sky-900{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity))}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-40{padding-top:10rem;padding-bottom:10rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.pb-8{padding-bottom:2rem}.pt-16{padding-top:4rem}.pt-2{padding-top:.5rem}.pt-32{padding-top:8rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-7xl{font-size:4.5rem;line-height:1}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-blue-50{--tw-text-opacity: 1;color:rgb(239 246 255 / var(--tw-text-opacity))}.text-blue-900{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity))}.text-neutral-900{--tw-text-opacity: 1;color:rgb(23 23 23 / var(--tw-text-opacity))}.text-orange-400{--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity))}.text-sky-50{--tw-text-opacity: 1;color:rgb(240 249 255 / var(--tw-text-opacity))}.text-sky-900{--tw-text-opacity: 1;color:rgb(12 74 110 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.hover\:bg-sky-900:hover{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.prose-p\:inline :is(:where(p):not(:where([class~=not-prose],[class~=not-prose] *))){display:inline}.prose-a\:text-blue-900 :is(:where(a):not(:where([class~=not-prose],[class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity))}.prose-a\:underline :is(:where(a):not(:where([class~=not-prose],[class~=not-prose] *))){text-decoration-line:underline}.prose-a\:hover\:text-blue-600:hover :is(:where(a):not(:where([class~=not-prose],[class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:sticky{position:sticky}.sm\:top-0{top:0}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-8{gap:2rem}.sm\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}} diff --git a/assets/6b960ed9.59ee9b53.css b/assets/6b960ed9.59ee9b53.css deleted file mode 100644 index 0c7d72b..0000000 --- a/assets/6b960ed9.59ee9b53.css +++ /dev/null @@ -1 +0,0 @@ -*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~="not-prose"] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~="not-prose"] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(thead th strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(ol):not(:where([class~="not-prose"] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)){list-style-type:upper-alpha}.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)){list-style-type:lower-alpha}.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)){list-style-type:upper-alpha}.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)){list-style-type:lower-alpha}.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)){list-style-type:upper-roman}.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)){list-style-type:lower-roman}.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)){list-style-type:upper-roman}.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~="not-prose"] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~="not-prose"] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~="not-prose"] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"\201c""\201d""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *)):after{content:close-quote}.prose :where(h1):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~="not-prose"] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~="not-prose"] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~="not-prose"] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~="not-prose"] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure > *):not(:where([class~="not-prose"] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~="not-prose"] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~="not-prose"] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~="not-prose"] *)):before{content:"`"}.prose :where(code):not(:where([class~="not-prose"] *)):after{content:"`"}.prose :where(a code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(h1 code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(h2 code):not(:where([class~="not-prose"] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~="not-prose"] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(blockquote code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(thead th code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(pre):not(:where([class~="not-prose"] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~="not-prose"] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~="not-prose"] *)):before{content:none}.prose :where(pre code):not(:where([class~="not-prose"] *)):after{content:none}.prose :where(table):not(:where([class~="not-prose"] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~="not-prose"] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~="not-prose"] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~="not-prose"] *)){vertical-align:top}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(video):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~="not-prose"] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol > li):not(:where([class~="not-prose"] *)){padding-left:.375em}.prose :where(ul > li):not(:where([class~="not-prose"] *)){padding-left:.375em}.prose :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h2 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h3 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h4 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~="not-prose"] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"] *)){padding-right:0}.prose :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-sm :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.1428571em}.prose-sm :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.1428571em}.prose-sm :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-sm :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-base :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose-base :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose-base :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose-base :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose-base :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose-base :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-base :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-lg :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-lg :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-lg :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-lg :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-xl :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8em;margin-bottom:.8em}.prose-xl :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.2em}.prose-xl :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.2em}.prose-xl :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.2em}.prose-xl :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.2em}.prose-xl :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-xl :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-2xl :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8333333em;margin-bottom:.8333333em}.prose-2xl :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-2xl :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-2xl :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-2xl :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.static{position:static}.relative{position:relative}.sticky{position:sticky}.top-8{top:2rem}.my-4{margin-top:1rem;margin-bottom:1rem}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mt-8{margin-top:2rem}.mt-2{margin-top:.5rem}.mb-4{margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mt-4{margin-top:1rem}.ml-8{margin-left:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.grid{display:grid}.contents{display:contents}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.h-screen{height:100vh}.w-16{width:4rem}.w-64{width:16rem}.w-full{width:100%}.max-w-prose{max-width:65ch}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-outside{list-style-position:outside}.list-square{list-style-type:square}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.overflow-y-auto{overflow-y:auto}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.border-t{border-top-width:1px}.border-solid{border-style:solid}.border-violet-600{--tw-border-opacity: 1;border-color:rgb(124 58 237 / var(--tw-border-opacity))}.bg-sky-50{--tw-bg-opacity: 1;background-color:rgb(240 249 255 / var(--tw-bg-opacity))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity))}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity))}.bg-\[\#292568\]{--tw-bg-opacity: 1;background-color:rgb(41 37 104 / var(--tw-bg-opacity))}.bg-\[\#0A2744\]{--tw-bg-opacity: 1;background-color:rgb(10 39 68 / var(--tw-bg-opacity))}.bg-\[\#1D4061\]{--tw-bg-opacity: 1;background-color:rgb(29 64 97 / var(--tw-bg-opacity))}.bg-\[\#5A7997\]{--tw-bg-opacity: 1;background-color:rgb(90 121 151 / var(--tw-bg-opacity))}.bg-\[\#89A1B8\]{--tw-bg-opacity: 1;background-color:rgb(137 161 184 / var(--tw-bg-opacity))}.bg-\[\#eeeeee\]{--tw-bg-opacity: 1;background-color:rgb(238 238 238 / var(--tw-bg-opacity))}.bg-\[\#dddddd\]{--tw-bg-opacity: 1;background-color:rgb(221 221 221 / var(--tw-bg-opacity))}.bg-\[\#777777\]{--tw-bg-opacity: 1;background-color:rgb(119 119 119 / var(--tw-bg-opacity))}.bg-sky-900{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity))}.p-8{padding:2rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-4{padding-left:1rem;padding-right:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.py-40{padding-top:10rem;padding-bottom:10rem}.pb-2{padding-bottom:.5rem}.pt-16{padding-top:4rem}.pb-8{padding-bottom:2rem}.pt-2{padding-top:.5rem}.pt-32{padding-top:8rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-7xl{font-size:4.5rem;line-height:1}.font-bold{font-weight:700}.text-blue-900{--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-orange-400{--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity))}.text-neutral-900{--tw-text-opacity: 1;color:rgb(23 23 23 / var(--tw-text-opacity))}.text-sky-900{--tw-text-opacity: 1;color:rgb(12 74 110 / var(--tw-text-opacity))}.text-blue-50{--tw-text-opacity: 1;color:rgb(239 246 255 / var(--tw-text-opacity))}.text-sky-50{--tw-text-opacity: 1;color:rgb(240 249 255 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-sky-200{--tw-text-opacity: 1;color:rgb(186 230 253 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.hover\:bg-sky-900:hover{--tw-bg-opacity: 1;background-color:rgb(12 74 110 / var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.prose-h1\:font-sans :is(:where(h1):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-h2\:font-sans :is(:where(h2):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-h3\:font-sans :is(:where(h3):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-h4\:font-sans :is(:where(h4):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-h5\:font-sans :is(:where(h5):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-h6\:font-sans :is(:where(h6):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.prose-p\:inline :is(:where(p):not(:where([class~="not-prose"] *))){display:inline}.prose-a\:text-red-600 :is(:where(a):not(:where([class~="not-prose"] *))){--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.prose-a\:text-blue-900 :is(:where(a):not(:where([class~="not-prose"] *))){--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity))}.prose-a\:underline :is(:where(a):not(:where([class~="not-prose"] *))){text-decoration-line:underline}.prose-a\:no-underline :is(:where(a):not(:where([class~="not-prose"] *))){text-decoration-line:none}.hover\:prose-a\:text-red-400 :is(:where(a):not(:where([class~="not-prose"] *))):hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.prose-a\:hover\:text-blue-600:hover :is(:where(a):not(:where([class~="not-prose"] *))){--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:prose-a\:underline :is(:where(a):not(:where([class~="not-prose"] *))):hover{text-decoration-line:underline}.hover\:prose-a\:decoration-2 :is(:where(a):not(:where([class~="not-prose"] *))):hover{text-decoration-thickness:2px}.prose-strong\:font-sans :is(:where(strong):not(:where([class~="not-prose"] *))){font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}@media (min-width: 640px){.sm\:sticky{position:sticky}.sm\:top-0{top:0}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-8{gap:2rem}.sm\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}} diff --git a/blog/2012-08-20-sublime-text-keymapping/index.html b/blog/2012-08-20-sublime-text-keymapping/index.html deleted file mode 100644 index 6e3f484..0000000 --- a/blog/2012-08-20-sublime-text-keymapping/index.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - Sublime Text 2 Keymapping :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Sublime Text 2 Keymapping

-

Made some keymappings in Sublime Text 2.

-

Open the keymap files by going to “Sublime Text 2” > “Preferences” > “Key Bindings - User”

-

This is what my key bindings file looks like:

- -

I opened up the Sublime Text 2 packages directories in Sublime Text 2, and search for the commands I wanted to add key bindings to. The names aren’t always obvious.

-

It turns out that the first two of these commands are from the context menu (reachable by right clicking). The context menu is described in ~/Library/Application Support/Sublime Text 2/Packages/Default/Context.sublime-menu. I copy/pasted the commands from Context.sublime-menu to the keybindings file and added key binds.

-

The reindent command is found in the command palette (bound to super+p by default). The commands in there are found in ~/Library/Application Support/Sublime Text 2/Packages/Default/Default.sublime-commands

-
-
-
- - - \ No newline at end of file diff --git a/blog/2012-09-04-dot-gitconfig/index.html b/blog/2012-09-04-dot-gitconfig/index.html deleted file mode 100644 index 923fc3c..0000000 --- a/blog/2012-09-04-dot-gitconfig/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - Log commnad for .gitconfig :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Log commnad for .gitconfig

-

Here is a sweet pretty log command that I found somewhere in my ~/.gitconfig.

- -
-
-
- - - \ No newline at end of file diff --git a/blog/2012-09-04-git-branch-in-command-line/index.html b/blog/2012-09-04-git-branch-in-command-line/index.html deleted file mode 100644 index 0a114cf..0000000 --- a/blog/2012-09-04-git-branch-in-command-line/index.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - Git branch in command-line :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Git branch in command-line

-

Here’s a script to show the git branch in the command prompt. I color the git branch to quickly show thestate of the working directory. RED means there are unstaged changes. YELLOW means the working directory and index match. And GREEN means working directory is clean (nothing to commit).

-

This is known to work with git version 1.7.12. However, git version 1.7.6.4 doesn’t properly support the -b flag with the --porcelain flags.

- -

RED, YELLOW, GREEN, and NO_COLOUR are color settings.

-

The function parse_git_branch parses the branch from the git status -b --porcelain command. I used head to take the first lineusing sed to run the regular expressions.

-

parse_git_status echo’s RED or GREEN depending if the directory is clean or not. git status --porcelain is a minimalist git status that should be used for scripting. In the first line, I pass stderr to stdin so that I can grep it for “not a git repository.”

-

The PS1 line sets the prompt. The $(function_name) runs the function every time the prompt is accessed, which is what you want for a prompt. If it were merely $(function_name) or function_name, the function would be run only once.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2012-10-04-affix/index.html b/blog/2012-10-04-affix/index.html deleted file mode 100644 index 72de8cc..0000000 --- a/blog/2012-10-04-affix/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - Affixing navbars using bootstrap. :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Affixing navbars using bootstrap.

-

Start at http://twitter.github.com/bootstrap/javascript.html#affix

-

Add a .myelement.affix {top: 50px;} style to set where the nav should stop at.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2013-02-18-find-options-in-xcode-documentation/index.html b/blog/2013-02-18-find-options-in-xcode-documentation/index.html deleted file mode 100644 index be26d32..0000000 --- a/blog/2013-02-18-find-options-in-xcode-documentation/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - Find Options in Xcode Documentation :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Find Options in Xcode Documentation

-

Xcode searches all available documentation sets by default. This is annoying when developing for the latest version of iOS, and a result comes back from every previous documentation set.

-

Clicking on the little magnifying glass in the search box shows a Find Options button. Clicking that shows how to remove extraneous documentation sets from the search results.

-

Here is a picture from the Xcode documentation showing how:

-

Xcode Documentation Screenshot

-
-
-
- - - \ No newline at end of file diff --git a/blog/2013-07-22-introducing-DRAutolayout/index.html b/blog/2013-07-22-introducing-DRAutolayout/index.html deleted file mode 100644 index b8b75fa..0000000 --- a/blog/2013-07-22-introducing-DRAutolayout/index.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - Introducing DRAutolayout :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Introducing DRAutolayout

-

Autolayout in iOS enables responsive design. It’s more enjoyable than computing frames by hand; however, it’s a bit wordy when writing views in code. And a lot of the autolayout api look the similar. I’ve been using some helper code to encapsulate some of the calls. I published them in a Cocoa pod. Here is a tour of the features of this Cocoa pod.

-

There are two categories that I made. The first is a category on NSLayoutConstraint for making raw NSLayoutConstraints. The second is a category on UIView for adding subviews and layout to a view.

-

NSLayoutConstraint (DRAutolayout)

-

The design philosophy is to provide all possible NSLayoutConstraint constraints, but use method names instead of constants to select the different ones.

-

For example, instead of writing out [view addConstraint:[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]] in native parlance in order to constrain view1’s center to view2’s center, I write [view addConstraint:[NSLayoutConstraint centerX:view1 toCenterX:view2]], which is easier for me to read.

-

Further, I made helper methods to constrain view tops to view tops and view rights to view rights. [NSLayoutConstraint top:view1 toTop:view2 multiplier:1.0 constant:0] and [NSLayoutConstraint right:view1 toRight:view2 multiplier:1.0 constant:0], respectively. To make subviews cling to the top, sides, and button of the superview. Of course, there is left:toLeft:multiplier:constant: also.

-

I also found it useful to constrain lefts to rights when placing views next to each other with some spacing between. [NSLayoutConstraint left:self.topBar toRight:self.menuButton multiplier:1.0 constant:10].

-

You can leave off the multiplier and constant arguments. So [NSLayoutConstraint right:view1 toRight:view2 multiplier:1.0 constant:0] and [NSLayoutConstraint right:view1 toRight:view2] are equivalent.

-

UIView (DRAutoLayout)

-

The second category is on UIView and is useful for making a simple flow layout down the middle of the screen, placing related objects to the right and left. The demo app uses this category. I recommend placing all subviews to a UIScrollView.

-

Remember to both placeAtTop: and placeAtBottom: in relation to the UIScrollView! This tells autolayout where the top and bottom of the content is for the UIScrollView. If you don’t do this, and you can set the contentSize of the UIScrollView manually, the UIScrollView might not scroll as expected.

-

Then after calling addSubview with all your subviews, you can arrange them by calling [scrollView place:view2 below:view1 distance:20] and [scrollView place:view2 below:view1 distance:20]. Then [scrollView horizontallyCenterSubviews:@[view1, view2]] to align them all in the center.

-

To place something to the left or right of something in the center, use the place:leftOf:distance method: [scrollView place:leftView leftOf:centerView distance:40]. The same works to place something to the right: [scrollView place:rightView rightOf:centerView distance:40].

-

Conclusion

-

I hope you find these autolayout helper methods useful. I really like autolayout. And I like these helper methods because they increase the clarity of the code while reducing the amount of code to read.

-

Checkout out the project at https://github.com/danramteke/DRAutolayout or add pod 'DRAutolayout' to your Podfile.

-

No Longer Maintained

-

This package is no longer maintained.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2013-09-03-converting-to-rubymotion/index.html b/blog/2013-09-03-converting-to-rubymotion/index.html deleted file mode 100644 index 4de3515..0000000 --- a/blog/2013-09-03-converting-to-rubymotion/index.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - Converting from Objective-C to RubyMotion :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Converting from Objective-C to RubyMotion

-

At Cyrus, we recently fielded a proposal to convert an existing app on the App Store from Objective-C to RubyMotion. I’ll give some background to the project and client, describe what we did, and give some recommendations.

-

First, a little background on the proposal. The client already had a pure Objective-C app in the App Store. They already had a design firm and a consulting firm that they had a a good working relationship with. They were Rails developers, curious about how RubyMotion could benefit them.

-

Also, they were pushing new features constantly. The other firm writing new features while we were translating the app to RubyMotion seemed like a recipe for continual merge conflicts. We would have been happy to implement new features for them. Stopping development to translate the app to RubyMotion didn’t seem like a good use of their time.

-

Here’s what we did. We planned to vendor the Objective-C project, convert the application delegate to Ruby, and then pull over one controller at a time until we got everything in Ruby that would be likely to change. We planned to leave in Objective-C all the dropped-in third party libraries.

-

We started by vendoring the entire Objective-C project. And already hit some bumps in the road. Because the other dev shop used the .pch file (pre-compiled headers file) to require fewer #import statements in their files, we couldn’t simply drop the entire project in a vendor sub-folder in our RubyMotion project. Further, the cocoapods wouldn’t load for the vendored project. Because of all these missing dependencies, we had to take a different route.

-

Instead, we built the existing project as a static framework for iOS, remembering to properly export headers. This approach didn’t require us tp fiddle with the .pch file, muck with pre-compiler directives, or do any other shenanigans with dependencies. This gave us a .a file that we put in the vendor directory of our RubyMotion project. (Although we found blog posts describing how to do so, we didn’t bother making a .a file for both the simulator and the iPhone - we were spiking this out to try to get it to work.) Here is the line from our Rakefile

-
app.vendor_project('vendor/libclientname-static', :static, :force_load => false,
-    :products => ['libclientname-static.a'],
-    :headers_dir => "include/libclientname-static")
-

After translating the application delegate, vendoring the appropriate included frameworks, and setting up the cocoapods, we had a stable foundation to build on. We were able to reference controllers in the storyboard, and instantiate views from the vendored .a file.

-

Through this prep work, we discovered that converting a project from Objective-C to RubyMotion is doable. It’s even doable to translate one class at a time, iteratively, until the project feels right. The cost-to-benefit of stopping to translate an already existing app is very dependent on the environment the app is being developed in. When new features are important, not translating makes sense. If a team of Ruby developers is taking over an app, a translation makes more sense.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2013-12-04-atos/index.html b/blog/2013-12-04-atos/index.html deleted file mode 100644 index a199479..0000000 --- a/blog/2013-12-04-atos/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - Symbolicating RubyMotion Crash Logs :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Symbolicating RubyMotion Crash Logs

-

Update! The information in this blog post is mostly irrelevent now that you can type rake crashlog:device to symbolicate crash logs from your device as of RubyMotion 2.18. Thanks!

-

Symbolicating a crash log is the process of finding a line number in source from a crash log. Crash logs often look like this. This specific process is interesting in particular to RubyMotion because Apple’s Xcode doesn’t symbolicate to Ruby code.

-

Now let’s see how it’s done.

-

After running rake device, you have binary files in the build directory, including a .dSYM file. Go ahead and cd into that directory, I’ll be typing all commands from that directory. Here is the directory listing for my build/iPhoneOS-7.0-Development directory: Imgur

-

A good example to use is this crash log I received from our customer: Crash Trace Binary Images Another good resource is this handy guide to getting crash logs from Apple devices: Pocket

-

Next, we’re going to use a command called atos. You’ll need the load address, the architecture, and the list of addresses from the crash log. I’ll show where to get these from the crash log.

-

Here is the command I typed based on the crash log example: xcrun atos -l 0xed000 -arch armv7 0x00a22a30 0x00a22b56 0x0098caec 0x00a20866 0x00a0a930 0x00a09a66 0x00a0a420 0x0080b524 0x0081241e 0x008124ae -o Redacted\ \(Dev\).dSYM/Contents/Resources/DWARF/Redacted\ \(Dev\)

-

The value for the -l flag is taken from the Binary Images section. That first number there. The value for the -arch flag is arm7 since the binaries were built for an iOS device.

-

The rest of the hex numbers are taken from the first screen shot of the crash log. I copy/pasted them from the crash log for now (would like to hear of a better way). And I got this output:

-
got symbolicator for Redacted (Dev).dSYM/Contents/Resources/DWARF/Redacted (Dev), base address 4000
-__vm_raise() (in Redacted (Dev)) + 276
-rb_vm_raise (in Redacted (Dev)) + 82
-rb_frozen_class_p (in Redacted (Dev)) + 72
-rb_vm_method_missing (in Redacted (Dev)) + 358
-rb_vm_dispatch (in Redacted (Dev)) + 3060
-rb_vm_trigger_method_missing (in Redacted (Dev)) + 522
-rb_vm_dispatch (in Redacted (Dev)) + 1764
-vm_fast_plus (in Redacted (Dev)) + 356
-__unnamed_28 (in Redacted (Dev)) + 274
-rb_scope__commandDidStart:__ (in Redacted (Dev)) (login_view_controller.rb:56)
-

As you can see, the crash was in the login controller. Looking at that file, it’s the Facebook log in code.

-

Although having to copy/paste addresses into the command line isn’t ideal, the main goal is retrieving a line number in ruby code (although off by one) from an otherwise opaque crash log intended for Xcode. And this allows further debugging.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2019-10-15-appstore-script/index.html b/blog/2019-10-15-appstore-script/index.html deleted file mode 100644 index fcfa39a..0000000 --- a/blog/2019-10-15-appstore-script/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - Automating Your Upload to Testflight in 4 Easy Steps :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Automating Your Upload to Testflight in 4 Easy Steps

-

Motivation

-

I was working on a project for a client where I was wrapping their website into a WKWebView in order to provide native BlueTooth access. -I of course wanted to automate uploading to the App Store, and typically I would just use Fastlane, but this client -doesn’t have iOS devs, and they most likely have no interest in keeping Fastlane’s gem up to date, and very likely have no interest in keeping -Ruby up to date since they weren’t using Ruby for anything else.

-

And personally, my projects don’t have Ruby backends either now that I’ve moved to Server-side Swift. So I don’t want to deal with Ruby if possible, either.

-

So I looked into how to automatically upload to TestFlight without Fastlane, and it was much easier than I expected. It was embarrassingly easier than I expected. It’s 4 commands.

-

It’s only four commands

-

The four steps are:

-
    -
  1. Update build number
  2. -
  3. Archive project
  4. -
  5. Export the archive
  6. -
  7. Upload the exported .ipa
  8. -
-

There’s a little bit of housekeeping around that: making a temporary directory to store the build artifacts. I chose .build here, but of course you can choose whatever you like.

-

The script: appstore.sh

-
#!/bin/bash
-
-if [ -z $APP_LOADER_USERNAME ]; then
-  echo "need app loader username stored in \$APP_LOADER_USERNAME"
-  exit 1
-fi
-
-if [ -z $APP_LOADER_PASSWORD ]; then
-  echo "need app loader password stored in \$APP_LOADER_PASSWORD\nGenerate an app-specific password at appleid.apple.com"
-  exit 1
-fi
-
-function version_bump {
-  xcrun agvtool next-version -all
-}
-
-function archive_project {
-  xcodebuild -project ExampleApp.xcodeproj -scheme ExampleApp \
-    -sdk iphoneos archive -archivePath ./.build/ExampleApp.xcarchive  \
-    -allowProvisioningUpdates -allowProvisioningDeviceRegistration
-}
-
-function export_archive {
-  xcodebuild -exportArchive -archivePath ./.build/ExampleApp.xcarchive \
-    -exportOptionsPlist appstore-export-options.plist \
-    -exportPath ./.build \
-    -allowProvisioningUpdates -allowProvisioningDeviceRegistration
-}
-
-function upload {
-  echo "=== uploading ExampleApp to App Store ===" && \
-  xcrun altool --upload-app -t ios \
-    -f ./.build/ExampleApp.ipa \
-    -u $APP_LOADER_USERNAME -p $APP_LOADER_PASSWORD && \
-  echo "Uploaded ExampleApp build $(xcrun agvtool what-version -terse) of version $(xcrun agvtool what-marketing-version -terse1)"
-}
-
-mkdir -p ./.build && \
-version_bump && \
-archive_project && \
-export_archive && \
-upload
-

You’ll also need a file with the export options. In the script above, it’s called appstore-export-options.plist. And you can stick it wherever. -In this example, it’s in the root directory of the project. -The file looks like this:

-
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>method</key>
-    <string>app-store</string>
-    <key>teamID</key>
-    <string>XXXXXXXXXX</string>
-    <key>generateAppStoreInformation</key>
-    <true/>
-    <key>uploadBitcode</key>
-    <true/>
-    <key>uploadSymbols</key>
-    <true/>
-</dict>
-</plist>
-

Personalize the script

-
    -
  1. Change ExampleApp.xcodeproj to whatever you’re using. If you’re using a workspace, then pass along -workspace ExampleWorkspace.xcworkspace to the archive command.
  2. -
  3. Change the ExampleApp scheme name to whatever you’re using.
  4. -
  5. Change the team ID in the appstore-export-options.plist
  6. -
  7. The username and password for uploading to the App Store are store in environment variables: $APP_LOADER_USERNAME and $APP_LOADER_PASSWORD. -Be sure to set them in your environment before running the script, however you store them. They are used by the app loader tool to upload the .ipa to Apple. -The script checks that username and password environment variables are set -at the beginning of the script so that the whole build succeeds just to find that the username and password are missing.
  8. -
-

Additional notes

-

agvtool

-

If you want to use agvtool to update your build numbers, then you’ll need to enable it in your project settings. -Here is a lengthy guide to agvtool and how to -enable it on your project. I recommend using agvtool since it comes with Xcode, there’s no additional dependencies.

-

codesign

-

The first time you run this script, you may get a password prompt saying something like “codesign would like to access some keys in your keychain.” -I always hit “Always allow” so that it doesn’t bug me any more.

-

And that’s it

-

Now you have a short script that builds and uploads your app to the App Store. You don’t have to worry about Ruby or Ruby Gems anymore. -And since bash is already on every Mac, and Xcode is already needed for iOS development, there’s no additional dependencies to download for development, or during CI/CD build.

-

Please Note

-

I learned a lot of this stuff from this article -back when I was first researching how to do this for my client. That article hasn’t been updated in a while, and so I can’t just send people directly to that article. -That’s why I wrote this one.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2020-11-20-spm-and-docker/index.html b/blog/2020-11-20-spm-and-docker/index.html deleted file mode 100644 index dfad633..0000000 --- a/blog/2020-11-20-spm-and-docker/index.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - - - Docker Layers for Swift Package Manager :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Docker Layers for Swift Package Manager

-

This post will show how to use Docker layers for Swift Package Manager projects. The sample project is on GitHub.

-

Motivation

-

Consider this Dockerfile for a Swift project:

-
FROM swift:5.3.1-focal
-WORKDIR /build
-ADD ./Package.* ./
-ADD ./Sources ./Sources
-RUN swift build -c release
-ENV PORT 80
-EXPOSE $PORT
-CMD /app/Run serve --env production --hostname 0.0.0.0 -p $PORT
-

We would build it using docker build -t hello-world . and we would see the following output:

-
 => [internal] load .dockerignore                                                                                             0.0s
- => => transferring context: 103B                                                                                             0.0s
- => [internal] load build definition from Dockerfile                                                                          0.0s
- => => transferring dockerfile: 503B                                                                                          0.0s
- => [internal] load metadata for docker.io/library/swift:5.3.1-focal                                                          0.6s
- => [1/5] FROM docker.io/library/swift:5.3.1-focal@sha256:3fff4e7b806d04e1f8ef4d8f15eabcd8c9d8898f5333cc59aa3fc2f527ce96a9   36.3s
- => => resolve docker.io/library/swift:5.3.1-focal@sha256:3fff4e7b806d04e1f8ef4d8f15eabcd8c9d8898f5333cc59aa3fc2f527ce96a9    0.0s
- => => sha256:3fff4e7b806d04e1f8ef4d8f15eabcd8c9d8898f5333cc59aa3fc2f527ce96a9 320B / 320B                                    0.0s
- => => sha256:a0f91b147671afee61dec3eb0f6cedfd819732931a8e5dc706a79a65509f3b58 1.37kB / 1.37kB                                0.0s
- => => sha256:e07bbf2adda7980b0d48d09a34922356fc699409bf384c5532540a3e6739cb2c 7.13kB / 7.13kB                                0.0s
- => => sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf 28.56MB / 28.56MB                              2.8s
- => => sha256:ba13d3bc422b493440f97a8f148d245e1999cb616cb05876edc3ef29e79852f2 847B / 847B                                    0.2s
- => => sha256:a254829d9e55168306fd80a49e02eb015551facee9c444d9dce3b26d19238b82 162B / 162B                                    0.2s
- => => sha256:d800a558a8d3578248d40fccef72e826fe3b709469aa49c2f2975ab7ad7ddc98 93.61MB / 93.61MB                              7.2s
- => => sha256:ee5f2394c96e071384625f1080ece8feca75f4b7b0118570e82032ee2cc4b131 422.07MB / 422.07MB                           17.7s
- => => extracting sha256:6a5697faee43339ef8e33e3839060252392ad99325a48f7c9d7e93c22db4d4cf                                     1.9s
- => => extracting sha256:ba13d3bc422b493440f97a8f148d245e1999cb616cb05876edc3ef29e79852f2                                     0.0s
- => => extracting sha256:a254829d9e55168306fd80a49e02eb015551facee9c444d9dce3b26d19238b82                                     0.0s
- => => extracting sha256:d800a558a8d3578248d40fccef72e826fe3b709469aa49c2f2975ab7ad7ddc98                                     7.7s
- => => extracting sha256:ee5f2394c96e071384625f1080ece8feca75f4b7b0118570e82032ee2cc4b131                                    17.6s
- => [internal] load build context                                                                                             0.0s
- => => transferring context: 5.55kB                                                                                           0.0s
- => [2/5] WORKDIR /build                                                                                                      0.7s
- => [3/5] ADD ./Package.* ./                                                                                                  0.1s
- => [4/5] ADD ./Sources ./Sources                                                                                             0.0s
- => [5/5] RUN swift build -c release                                                                                        291.9s
- => exporting to image                                                                                                        3.5s
- => => exporting layers                                                                                                       3.4s
- => => writing image sha256:fda601b5aebba754ac82c1a5dc95f9ad0be6b7695340d3355a9bab26d6c70237                                  0.1s
- => => naming to docker.io/library/hello-world                                                                                0.0s
-

We can see the project builds for over 300 seconds or 5 minutes.

-

When building this project, there’s only one line that builds any Swift code: RUN swift build -c release. Therefore only one Docker layer cache exists. -So anytime you change your code, in your own Sources directory, your third party dependencies rebuild your entire project without any cache.

-

Ideally, the third party dependencies would download and cache separately from your own code. For example the web framework Vapor has many dependencies, -and compiling just the framework takes a long time.

-

Solution

-

If we create a empty files in a directory structure that matches our Sources folder, we can build the project’s dependencies. And docker will cache the built dependencies into a separate layer in our Dockerfile. This will speed up subsequent builds.

-

And now our dockerfile could look like this:

-
FROM swift:5.3.1-focal
-WORKDIR /build
-ADD ./Package.* ./                                                   # A
-RUN swift package resolve                                            # B
-RUN mkdir -p Sources/App && touch Sources/App/empty.swift \
-    && mkdir -p Sources/Run && touch Sources/Run/main.swift          # C
-RUN swift build -c release                                           # D
-ADD ./Sources ./Sources                                              # E
-RUN swift build -c release                                           # F
-ENV PORT 80
-EXPOSE $PORT
-CMD /app/Run serve --env production --hostname 0.0.0.0 -p $PORT
-

On line # A, we add the Package.swift and Package.resolved files as normal. On line # B, we resolve all the dependencies.

-

If stop editing here, and we swift build now, we’ll get an error about missing sources, since we haven’t copied our Sources in yet. -To solve this, we write line # C to create the needed directories and create empty files in them. Note that empty file the Run directory needs to be named main.swift since it’s an executable target.

-

On line # D, we build all our dependencies.

-

On lines # E and # F, we add our sources and build then as normal.

-

Now when we run the docker build, the output of docker build -t hello-world . hasn’t changed much, because nothing has been cached yet. However, the next time we build (after we make some small code changes - for example, change from “hello world” to “hello earth”), -the output of docker build -t hello-world . looks like this:

-
[+] Building 4.8s (13/13) FINISHED
- => [internal] load build definition from Dockerfile                                                                            0.0s
- => => transferring dockerfile: 37B                                                                                             0.0s
- => [internal] load .dockerignore                                                                                               0.0s
- => => transferring context: 34B                                                                                                0.0s
- => [internal] load metadata for docker.io/library/swift:5.3.1-focal                                                            0.5s
- => [1/8] FROM docker.io/library/swift:5.3.1-focal@sha256:3fff4e7b806d04e1f8ef4d8f15eabcd8c9d8898f5333cc59aa3fc2f527ce96a9      0.0s
- => [internal] load build context                                                                                               0.0s
- => => transferring context: 461B                                                                                               0.0s
- => CACHED [2/8] WORKDIR /build                                                                                                 0.0s
- => CACHED [3/8] ADD ./Package.* ./                                                                                             0.0s
- => CACHED [4/8] RUN swift package resolve                                                                                      0.0s
- => CACHED [5/8] RUN mkdir -p Sources/App && touch Sources/App/empty.swift     && mkdir -p Sources/Deps && touch Sources/Deps/  0.0s
- => CACHED [6/8] RUN swift build -c release --target Deps                                                                       0.0s
- => [7/8] ADD ./Sources ./Sources                                                                                               0.1s
- => [8/8] RUN swift build -c release                                                                                            3.8s
- => exporting to image                                                                                                          0.3s
- => => exporting layers                                                                                                         0.3s
- => => writing image sha256:5960e06c78c5c2e247a42bff00c390ffcf75093e546228e8aa4cbd46ce37e5e1                                    0.0s
- => => naming to docker.io/library/hello-world                                                                                  0.0s
-

We can see that it builds much quicker! Only a few seconds compared to the over 300 seconds before caching.

-

Also, we can see the various layers marked as CACHED. Only our own project’s code needs to be compiled, since the third party dependencies already are compiled and cached in the docker layer.

-

I hope this was helpful. Checkout the sample project on Github here https://github.com/danramteke/spm-in-docker

-
-
-
- - - \ No newline at end of file diff --git a/blog/2021-04-07-xcodegen-for-multiplatform/index.html b/blog/2021-04-07-xcodegen-for-multiplatform/index.html deleted file mode 100644 index 483f599..0000000 --- a/blog/2021-04-07-xcodegen-for-multiplatform/index.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - Xcodegen for Multiplatform Xcode projects :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Xcodegen for Multiplatform Xcode projects

-

Here’s how I use Xcodegen for a new project that targets MacOS and iOS. It’s here for your reference or inspiration.

-

Project

-

Here is my project.yml:

-
name: Example
-options:
-  deploymentTarget:
-    iOS: "14.0"
-    macOS: "11.0"
-schemes:
-  Example-iOS:
-    build:
-      targets:
-        Example-iOS: all
-  Example-macOS:
-    build:
-      targets:
-        Example-macOS: all
-targets:
-  Example-macOS:
-    type: application
-    platform: macOS
-    sources:
-      - path: macOS
-      - path: Shared
-    settings:
-      DEVELOPMENT_TEAM: 1234567890
-      CODE_SIGN_STYLE: Automatic
-      ORGANIZATIONNAME: "Example Organization"
-      PRODUCT_BUNDLE_IDENTIFIER: com.Example.app
-      VERSIONING_SYSTEM: apple-generic
-      SWIFT_VERSION: 5.3
-      CURRENT_PROJECT_VERSION: ${APP_BUILD_NUMBER}
-    entitlements:
-      path: macOS/macOS.entitlements
-      properties:
-        com.apple.security.app-sandbox: true
-        com.apple.security.files.user-selected.read-only: true
-    info:
-      path: macOS/Info.plist
-      properties:
-        CFBundleVersion: ${APP_BUILD_NUMBER}
-        CFBundleShortVersionString: ${APP_BUILD_VERSION}
-        CFBundleDevelopmentRegion: $(DEVELOPMENT_LANGUAGE)
-        CFBundleName: $(PRODUCT_NAME)
-        CFBundleDisplayName: Example
-        CFBundleIconFile: ""
-        CFBundleIdentifier: $(PRODUCT_BUNDLE_IDENTIFIER)
-        CFBundleInfoDictionaryVersion: "6.0"
-        CFBundlePackageType: $(PRODUCT_BUNDLE_PACKAGE_TYPE)
-        LSMinimumSystemVersion: $(MACOSX_DEPLOYMENT_TARGET)
-  Example-iOS:
-    type: application
-    platform: iOS
-    sources:
-      - path: iOS
-      - path: Shared
-    settings:
-      DEVELOPMENT_TEAM: 1234567890
-      CODE_SIGN_STYLE: Automatic
-      ORGANIZATIONNAME: "Example Organization"
-      PRODUCT_BUNDLE_IDENTIFIER: com.Example.app
-      TARGETED_DEVICE_FAMILY: 1,2
-      VERSIONING_SYSTEM: apple-generic
-      SWIFT_VERSION: 5.3
-      CURRENT_PROJECT_VERSION: ${APP_BUILD_NUMBER}
-    entitlements:
-      path: iOS/iOS.entitlements
-    info:
-      path: iOS/Info.plist
-      properties:
-        CFBundleVersion: ${APP_BUILD_NUMBER}
-        CFBundleShortVersionString: ${APP_BUILD_VERSION}
-        CFBundleDevelopmentRegion: $(DEVELOPMENT_LANGUAGE)
-        CFBundleDisplayName: Example
-        ITSAppUsesNonExemptEncryption: false
-        LSRequiresIPhoneOS: true
-        UIApplicationSceneManifest:
-          UIApplicationSupportsMultipleScenes: true
-        UIApplicationSupportsIndirectInputEvents: true
-        UILaunchScreen: {}
-        NSAppTransportSecurity:
-          NSAllowsLocalNetworking: true
-        UIRequiredDeviceCapabilities: [armv7]
-        UISupportedInterfaceOrientations:
-          - UIInterfaceOrientationPortrait
-        UISupportedInterfaceOrientations~ipad:
-          - UIInterfaceOrientationPortrait
-          - UIInterfaceOrientationPortraitUpsideDown
-          - UIInterfaceOrientationLandscapeLeft
-          - UIInterfaceOrientationLandscapeRight
-

This defines two targets, one for macOS and one for iOS, just like Xcode’s multiplatform template.

-

Bootstrap

-

I don’t call xcodegen directly. The project.yml references two variables for the build number and the marketing version, and those are stored in two files named build-number which contains the current build number (for example: 1) and build-version which contains the current marketing version (for example: 1.0.0).

-

To make sure those values are available to xcodegen, I create a bootstrap.sh script that looks like this:

-
rm -fr *.xcodeproj
-
-export APP_BUILD_NUMBER=$(cat build-number)
-export APP_BUILD_VERSION=$(cat build-version)
-
-xcodegen generate -s project.yml
-

So when I need to regenerate the xcode project, I run ./bootstrap.sh from the project’s root directory.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2022-09-06-tagging-for-ios-devops/index.html b/blog/2022-09-06-tagging-for-ios-devops/index.html deleted file mode 100644 index cf65953..0000000 --- a/blog/2022-09-06-tagging-for-ios-devops/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - Tagging for iOS DevOps :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Tagging for iOS DevOps

-

When you’re ready to submit a build to TestFlight, I recommend tagging that commit in the style of 1.2.3-7, where 1.2.3 is the marketing version shown on the App Store and 7 is the build number. This strategy allows us to easily reference what code went into which build.

-

If we take this approach, we can set up our CI/CD system to build our code and push to the App Store.

-

On BitRise, we can use the following code to interpret the tag as as a marketing version and build number. This code uses Bash regular expressions.

-
#!/usr/bin/env bash
-set -e set -o pipefail set -x
-
-echo "tag is: $BITRISE_GIT_TAG"
-rx='([0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)'
-if [[ $BITRISE_GIT_TAG =~ $rx ]]; then
- echo "0: ${BASH_REMATCH[0]}"
- echo "1: ${BASH_REMATCH[1]}"
- echo "2: ${BASH_REMATCH[2]}"
-else
- echo "No match!"
- exit 1
-fi
-envman add --key GIT_TAG_MARKETING_VERSION --value "${BASH_REMATCH[1]}"
-envman add --key GIT_TAG_BUILD_NUMBER --value "${BASH_REMATCH[2]}"
-

If this script is run as a “build step”, we use BitRise’s envman to add the parsed information to our environment, so that our other build steps have easy access to this information.

-
-
-
- - - \ No newline at end of file diff --git a/blog/2022-09-17-contour-daemonset/index.html b/blog/2022-09-17-contour-daemonset/index.html deleted file mode 100644 index 4efced1..0000000 --- a/blog/2022-09-17-contour-daemonset/index.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - Kubernetes without Load Balancers with Contour :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-

Kubernetes without Load Balancers with Contour

-

For small projects, when the cost of a load balancer can cost as much as a smaller node, a load balancer can feel like overkill. I would prefer to have another compute node instead! But I still want the other benefits of Kubernetes. This is the work around I’ve done on for DigitalOcean (referral link)’s Kubernetes aka DOKS.

-

Single node

-

Start with the raw Contour yaml available from their GitHub: v1.22.1/…/contour.yaml then search for a Service with name: envoy,

-

For the Service, change the type from LoadBalancer to NodePort.

-

If you only have one node, you’re done! You can point your DigitalOcean (referral link) ReservedIP to this node.

-

More than one node

-

If you have more than one Node, I use a nodeSelector and a label to specify an “edge” node.

-

Find a DaemonSet with name: envoy, add a nodeSelector key to the spec.template.spec dictionary. It will be a sibling of the containers key. It could look like this:

-
spec:
-  # …
-  template:
-    # …
-    spec:
-      nodeSelector:
-        node-role: edge
-      containers:
-      # …
-

Then, looking at your cluster, find the Node you want to use as the edge node. And run something like this kubectl label node $edgeNodeName node-role=edge --overwrite

-

You can now point your DigitalOcean (referral link) ReservedIP to this node.

-

Apply

-

Now you can apply your modified contour.yaml! Now you have a nice Ingress controller with nice features. You can spend your budget on compute instead of a load balancer.

-

Notes

-

This is a more brittle setup for sure. If DigitalOcean (referral link) ever recreates your cluster for you, they will usually create a Load Balancer for you. Just remember to delete it.

-

Also, this setup can’t really do in-place upgrades. So instead, when you need to upgrade your Kubernetes version, setup a second cluster with your apps, swap your ReservedIP to that cluster, then delete your old one.

-

DigitalOcean Referral Badge

-
-
-
- - - \ No newline at end of file diff --git a/colors/index.html b/colors/index.html index a43d83f..84b79be 100644 --- a/colors/index.html +++ b/colors/index.html @@ -1,51 +1,9 @@ - - - - - - - - - - - - - - Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
- - - \ No newline at end of file +Daniel Ramteke iOS, DevOps, and web development in New York City
Daniel Ramteke
+Resume + +Testimonials + +Press + +Contact +
\ No newline at end of file diff --git a/contact/index.html b/contact/index.html index af784dc..41c8417 100644 --- a/contact/index.html +++ b/contact/index.html @@ -1,63 +1,9 @@ - - - - - - - - - - - - - - Contact :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-Contact me -

Contact me daniel at danramteke dot com

-
-
- - - \ No newline at end of file +Contact :: Daniel Ramteke iOS, DevOps, and web development in New York City
Daniel Ramteke
+Resume + +Testimonials + +Press + +Contact +
Contact me

Contact me daniel at danramteke dot com

\ No newline at end of file diff --git a/devops/index.html b/devops/index.html index d5ceda1..75e6d6a 100644 --- a/devops/index.html +++ b/devops/index.html @@ -1,100 +1,19 @@ - - - - - - - - - - - - - - Custom DevOps :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-Custom DevOps -

Wrangling iOS build scripts and optimizing server infrastructure scripts: I enjoy both of these. If it can be automated with a script, it probably should. Scripting as much as possible increases confidence in important processes such as publishing to the App Store or disaster recovery. - Unfortunately, not everyone has the time or familiarity to develop bullet-proof scripts. Contracting a DevOps consultant such as myself can propel your project forward.

-

Server infrastructure toolbox

-
- -
-
-

Docker

-

- - Docker is a format for packing apps into standardized containers. Standardized and lightweight containers enables new workflows for codesharing, quality testing, and dev enviroments. Docker shines in production enviroments when combined with an orchestration layer such as Kubernetes. -

- -

Visit website

-
-
- -
-
-

Terraform

- -

- Terraform is a declarative tool for managing infrastructure. And it's a gamechanger. It has eclipsed Ansible in my workflows. It also enables reuse with its modules feature. +Custom DevOps :: Daniel Ramteke iOS, DevOps, and web development in New York City

Custom DevOps

Wrangling iOS build scripts and optimizing server infrastructure scripts: I enjoy both of these. If it can be automated with a script, it probably should. Scripting as much as possible increases confidence in important processes such as publishing to the App Store or disaster recovery. + Unfortunately, not everyone has the time or familiarity to develop bullet-proof scripts. Contracting a DevOps consultant such as myself can propel your project forward.

Server infrastructure toolbox

Docker

+Docker is a format for packing apps into standardized containers. Standardized and lightweight containers enables new workflows for codesharing, quality testing, and dev enviroments. Docker shines in production enviroments when combined with an orchestration layer such as Kubernetes. +

Visit website

Terraform

+Terraform is a declarative tool for managing infrastructure. And it's a gamechanger. It has eclipsed Ansible in my workflows. It also enables reuse with its modules feature. I even have a merged pull request to DigitalOcean's Terraform Provider. -

- -

Visit website

-
-
- -
-
-

Kubernetes

- -

- After declaring infra with Terraform and packing apps with Docker, Kubernetes is the declarative glue between the two. +

Visit website

Kubernetes

+After declaring infra with Terraform and packing apps with Docker, Kubernetes is the declarative glue between the two. I've been running Kubernetes in production for Underway since 2018. My preferred ingress controller is Contour. -

-

Visit website

- -
-
- -
-
-
-
- - - \ No newline at end of file +

Visit website

\ No newline at end of file diff --git a/index.html b/index.html index 6d08730..aae98cd 100644 --- a/index.html +++ b/index.html @@ -1,95 +1,14 @@ - - - - - - - - - - - - - - Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
- iOS freelancer in New York City - -
-

Current projects

- - - -
-
- - - \ No newline at end of file +}, --> \ No newline at end of file diff --git a/ios/index.html b/ios/index.html index 4fc231c..272daf4 100644 --- a/ios/index.html +++ b/ios/index.html @@ -1,152 +1,33 @@ - - - - - - - - - - - - - - Custom iOS and iPhone app development :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-Custom iOS and iPhone app development -

+Custom iOS and iPhone app development :: Daniel Ramteke iOS, DevOps, and web development in New York City

Custom iOS and iPhone app development

I have been developing for iOS ever since Apple unveiled the App Store with iOS 2.0. I transitioned to Swift as soon as it was announced, looking back to Objective-C as needed. -

Previous iOS clients include Stash, Newsela, uSTADIUM, and CoinMarketCap.

-

iOS build scripts

-
- -
-
-

Screenshots

-

- - Keeping screenshots up-to-date for the App Store can require a lot of repetitive effort. Whenever Underway has a UI change, I use Xcode's UI testing feature along with Fastlane to generate fresh screenshots. Scripting can save a lot of time, and still show the latest screenshots on the App Store. -

- -

View Underway's screenshots on the App Store.

- -
-
- -
-
-

XcodeGen

- -

I'm very familiar with XcodeGen. Generating +

Previous iOS clients include Stash, Newsela, uSTADIUM, and CoinMarketCap.

iOS build scripts

Screenshots

+Keeping screenshots up-to-date for the App Store can require a lot of repetitive effort. Whenever Underway has a UI change, I use Xcode's UI testing feature along with Fastlane to generate fresh screenshots. Scripting can save a lot of time, and still show the latest screenshots on the App Store. +

View Underway's screenshots on the App Store.

XcodeGen

I'm very familiar with XcodeGen. Generating the project file reduces merge conflicts, and eliminates build configuration drift. The maintainer has kept it up to date with all the changes to Xcode over the years. XcodeGen combined with CocoaPods or Carthage is a powerful code management combo. -

- - - -

Visit on GitHub

-
-
- -
-
-

Swish

- -

- Ruby with Fastlane doesn't need to the last word when it comes to iOS deploy scripts. I've been developing Swish alongside Underway to script repetive tasks. -

-

Visit on GitHub

- -
-
- -
-
-

Tools for a better app

-
- -
-
-

PaintCode

-

PaintCode generates vectorized images at +

Visit on GitHub

Swish

+Ruby with Fastlane doesn't need to the last word when it comes to iOS deploy scripts. I've been developing Swish alongside Underway to script repetive tasks. +

Visit on GitHub

Tools for a better app

PaintCode

PaintCode generates vectorized images at runtime, passing in colors and lineweights as arguments. Saves time going back and forth with designers on the right image, and allows for image reuse. In addition, this speeds up the launch time of the app due to how iOS handles asset catalogs. -

- -

Visit website

-
-
- -
-
-

GRDB.swift

- -

+

Visit website

GRDB.swift

GRDB has outmatched every other SQLite wrapper I've tried. It's interface is clear and easy to use. GRDB enables raw SQL as well as mapping results to structs or classes. And it's even updated for Combine. It also has a smaller footprint compared to Realm. -

- -

View on GitHub

-
-
- -
-
-

XcodeGen

- -

- I'm very familiar with XcodeGen. Generating +

View on GitHub

XcodeGen

+I'm very familiar with XcodeGen. Generating the project file reduces merge conflicts, and eliminates build configuration drift. The maintainer has kept it up to date with all the changes to Xcode over the years. XcodeGen combined with CocoaPods or Carthage is a powerful code management combo. -

- -

View on GitHub

- -
-
- -
-
-
-
- - - \ No newline at end of file +

View on GitHub

\ No newline at end of file diff --git a/press/index.html b/press/index.html index e5e62fe..d592e41 100644 --- a/press/index.html +++ b/press/index.html @@ -1,63 +1,11 @@ - - - - - - - - - - - - - - Press coverage :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
- -
- -

Press coverage

Publications highlighting my work

For Full Queue Developer

    -
  • iOS Dev Weekly #616

    This post-WWDC '23 issue featured three of my Swift Macros.IdentifiedEnumCases to generate IDs for enumeration cases, StaticMemberIterable to confidently iterate through static members of a type, and UniquelyTypedID to let the compiler confirm you're passing the right kind of ID

  • iOS Dev Weekly #563

    "I’m linking to this app from Daniel Ramteke for several reasons. First, it’s an excellent (and free) way to play with the various haptic parameters to get precisely the desired effect. Secondly, Daniel streamed the entire development of this app on his Twitch channel and uploaded all 13 session recordings to YouTube."

  • -

For Underway

    -
  • Viewing NYC

    "Enter Underway NYC, a new iOS App that removes all the extra features and bloat of the transit apps and simply +Press coverage :: Daniel Ramteke iOS, DevOps, and web development in New York City

    Press coverage

    Publications highlighting my work

    For Full Queue Developer

    • iOS Dev Weekly #616

      This post-WWDC '23 issue featured three of my Swift Macros.IdentifiedEnumCases to generate IDs for enumeration cases, StaticMemberIterable to confidently iterate through static members of a type, and UniquelyTypedID to let the compiler confirm you're passing the right kind of ID

    • iOS Dev Weekly #563

      "I’m linking to this app from Daniel Ramteke for several reasons. First, it’s an excellent (and free) way to play with the various haptic parameters to get precisely the desired effect. Secondly, Daniel streamed the entire development of this app on his Twitch channel and uploaded all 13 session recordings to YouTube."

    For Underway

    • Viewing NYC

      "Enter Underway NYC, a new iOS App that removes all the extra features and bloat of the transit apps and simply shows - you how many minutes it is until the next train at your station."

    • Brokelyn

      "In addition to the MTA's online Subway Times, which offers the current arrival times by line and station and is accessible from the internet even beyond the subway's hallowed platforms, Underway NYC now offers the same information but in app form and plotted to a subway map."

    • MakeSpace

      "Let's face it: Google Maps can only get you so far. … Made specifically with busy New Yorkers in mind, it's an all-in-one commute compendium."

    • Brick Underground

      "Should you use it? If you just want to know when the next train is arriving, but don't need step-by-step navigation."

    • -
    -
    -
    -
    - - - \ No newline at end of file + you how many minutes it is until the next train at your station."

  • Brokelyn

    "In addition to the MTA's online Subway Times, which offers the current arrival times by line and station and is accessible from the internet even beyond the subway's hallowed platforms, Underway NYC now offers the same information but in app form and plotted to a subway map."

  • MakeSpace

    "Let's face it: Google Maps can only get you so far. … Made specifically with busy New Yorkers in mind, it's an all-in-one commute compendium."

  • Brick Underground

    "Should you use it? If you just want to know when the next train is arriving, but don't need step-by-step navigation."

\ No newline at end of file diff --git a/resume/index.html b/resume/index.html index 615bcca..4c0c144 100644 --- a/resume/index.html +++ b/resume/index.html @@ -1,421 +1,194 @@ - - - - - - - - - - - - - - Résumé :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
- -
- - - -
- -

About

I am a seasoned Swift developer, specializing in iOS, Swift-on-the-Server, and DevOps.

I am looking for a Developer Relations role, advocating for developers, helping developers on social media. For my current social media personality, please see FullQueueDeveloper.com

-
- - - - - - -
- -

Skills

    -
  • Swift, SwiftUI, UIKit, Xcode, VIPER
  • Vapor, GRDB, XcodeGen
  • Swift Argument Parser
  • Unit testing, UI testing
  • Terraform, Kubernetes, Docker
  • Redis, Postgres, MySQL
  • TypeScript, JavaScript, AstroJS, React, NodeJS
  • Kotlin, JetPack Compose, Java
  • GitHub Actions, GitLab CI/CD, BitRise CI
  • Figma, PaintCode, ImageMagick, ffmpeg
  • Final Cut Pro, Pixelmator Pro, Inkscape, Logic Pro
  • -
-
- - -
- -

Past experience

There are three main eras here. XP/Agile consulting with Cyrus Innovation from 2007-2014, iOS freelancing from 2014 to 2020, and the pandemic era from 2020 on when I started my online personality, Full Queue Developer.

    -
  • Artium
    Android engineer, March 2023 - October 2023
      -
    • Résumé :: Daniel Ramteke iOS, DevOps, and web development in New York City

      About

      I am a seasoned Swift developer, specializing in iOS, Swift-on-the-Server, and DevOps.

      I am looking for a Developer Relations role, advocating for developers, helping developers on social media. For my current social media personality, please see FullQueueDeveloper.com

      Skills

      • Swift, SwiftUI, UIKit, Xcode, VIPER
      • Vapor, GRDB, XcodeGen
      • Swift Argument Parser
      • Unit testing, UI testing
      • Terraform, Kubernetes, Docker
      • Redis, Postgres, MySQL
      • TypeScript, JavaScript, AstroJS, React, NodeJS
      • Kotlin, JetPack Compose, Java
      • GitHub Actions, GitLab CI/CD, BitRise CI
      • Figma, PaintCode, ImageMagick, ffmpeg
      • Final Cut Pro, Pixelmator Pro, Inkscape, Logic Pro

      Past experience

      There are three main eras here. XP/Agile consulting with Cyrus Innovation from 2007-2014, iOS freelancing from 2014 to 2020, and the pandemic era from 2020 on when I started my online personality, Full Queue Developer.

      • Artium
        Android engineer, March 2023 - October 2023
        • Built the Android app using JetPack Compose and Koin

          -
        • Managed CI/CD scripts for tagging and releasing to Firebase

          -
        • Built a CLI for testing using Bash.

          - -
        • -
      • WeThink
        Freelancer with TopTal, July 2022 - October 2022
          -
        • WeThink
          Freelancer with TopTal, July 2022 - October 2022
          • Inherited partially complete iOS, Android and node web app

            -
          • Hired specialists across the globe and managed the project to launch

            - -
          • -
        • TopTal client
          Freelancer with TopTal,
            -
          • TopTal client
            Freelancer with TopTal,
            • Designed iPhone app to instantly book technicians to fix home maintenance issues

              -
            • Streamlined onboarding flow with camera

              -
            • Prototyped in SwiftUI and Sketch

              - -
            • -
          • Daring Bit Assembly
            Consultant with Sincere Labs NYC, February 2022 - March 2022
              -
            • Daring Bit Assembly
              Consultant with Sincere Labs NYC, February 2022 - March 2022
              • Aided in their client project by porting open source framework away from Realm to GRDB.

                -
              • Objective-C interacting with new Swift code.

                -
              • Worked quickly to meet their client’s deadline, updating frequently

                - -
              • -
            • Stash
              Full-stack engineer, January 2020 - November 2021
                -
              • Worked quickly to meet their client's deadline, updating frequently

                +
            • Stash
              Full-stack engineer, January 2020 - November 2021
              • Tech-lead for a referral rewards program across backend, web, Android, and iOS

                -
              • Mentored colleagues learning to develop on iOS.

                -
              • Experimental social financial site at crew.stash.com using React & Ruby on Rails

                - + prose-a:hover:text-blue-600">

                Experimental social financial site at crew.stash.com using React & Ruby on Rails

              • Converted monolithic services to microservices, using Ruby on Rails, Kafka, HTML/CSS and Docker

                -
              • iOS developer dependency management using Mint, Cocoapods, & Xcodegen

                - + prose-a:hover:text-blue-600">

                iOS developer dependency management using Mint, Cocoapods, & Xcodegen

              • Native iOS screens and architecture for onboarding overhaul

                -
              • Interviewed candidates for senior roles

                - -
              • -
            • GrowFlow
              Consultant with TopTal, August 2019 - September 2019
                -
              • GrowFlow
                Consultant with TopTal, August 2019 - September 2019
                • Embedded GrowFlow’s existing webapp into a WKWebView with native login screen

                  - + prose-a:hover:text-blue-600">

                  Embedded GrowFlow's existing webapp into a WKWebView with native login screen

                • Coordinated on-device capabilities such as BlueTooth and local storage to enable their React webapp to connect and reconnect to receipt and label printers.

                  -
                • Integrated native Zebra and Star Micronics SDKs to print over BlueTooth, USB, and WiFi from iPad.

                  -
                • Converted PDFs to native CoreGraphics bitmaps to print receipts and package labels

                  - -
                • -
              • Stash
                Freelancer with Dan Ramteke Consulting, September 2018 - April 2019
                  -
                • Stash
                  Freelancer with Dan Ramteke Consulting, September 2018 - April 2019
                  • Took ownership of investigating a launch bug that involved AppsFlyer and concurrent threads which resulted in potential 10 second launch times. The fix I architected and implemented resulted in a ~10% increase in registrations.

                    -
                  • Braze, Mixpanel, Apptimize, AppsFlyer, and Branch integrations.

                    -
                  • Migrated from Branch to AppsFlyer

                    - -
                  • -
                • CoinMarketCap
                  Freelancer with Dan Ramteke Consulting, August 2017 - August 2018
                    -
                  • CoinMarketCap
                    Freelancer with Dan Ramteke Consulting, August 2017 - August 2018
                    • Built first iOS app according to the provided designs.

                      -
                    • Implemented currency conversion and currency formatting.

                      - -
                    • -
                  • Stash
                    Freelancer with Dan Ramteke Consulting, July 2017 - October 2017
                      -
                    • Stash
                      Freelancer with Dan Ramteke Consulting, July 2017 - October 2017
                      • Massive iOS app rewrite from Objective-C with MVC architecture to Swift with VIPER architecture with improved testability.

                        -
                      • Coordinated across with development and business teams to codify the rules around purchase logic, implementing them in Fitnesse tests.

                        - -
                      • -
                    • Pascal Dangin
                      Freelancer with Dan Ramteke Consulting, August 2016 - May 2017
                        -
                      • Pascal Dangin
                        Freelancer with Dan Ramteke Consulting, August 2016 - May 2017
                        • Added usuability features to existing Objective-C camera app

                          -
                        • Migration off Parse to new vendor due to Parse end-of-life

                          - -
                        • -
                      • Sifftr
                        Freelancer with Dan Ramteke Consulting, February 2016 - December 2017
                          -
                        • Sifftr
                          Freelancer with Dan Ramteke Consulting, February 2016 - December 2017
                          • Led team of two designers, two developers to build iOS app using Swift

                            -
                          • Integrated the Twitter SDK.

                            - -
                          • -
                        • Showdown!
                          Freelancer with Dan Ramteke Consulting, May 2014 - February 2017
                            -
                          • Showdown!
                            Freelancer with Dan Ramteke Consulting, May 2014 - February 2017
                            • Led team of one developer and one designer

                              -
                            • Used Swift and Rails to help design and deliver a contest-driven photo sharing app.

                              -
                            • Maintained the original RubyMotion version of the app

                              -
                            • Migrated the app to Swift from RubyMotion

                              - -
                            • -
                          • uSTADIUM
                            Freelancer with Dan Ramteke Consulting, August 2015 - December 2015
                              -
                            • uSTADIUM
                              Freelancer with Dan Ramteke Consulting, August 2015 - December 2015
                              • Used Parse and Objective-C to help deliver UI improvements and a new feature: daily fantasy.

                                -
                              • Users of the app loved the changes!

                                - -
                              • -
                            • Newsela
                              Freelancer with Dan Ramteke Consulting, February 2015 - June 2015
                                -
                              • Newsela
                                Freelancer with Dan Ramteke Consulting, February 2015 - June 2015
                                • Used Swift and UIKit to create the first version of their iOS app

                                  -
                                • Worked with their in-house design and backend teams, as well as their newly-hired iOS engineer.

                                  - -
                                • -
                              • Blinkbuggy
                                Freelance iOS Developer, February 2014 - June 2014
                                  -
                                • Blinkbuggy
                                  Freelance iOS Developer, February 2014 - June 2014
                                  • Used Objective-C and Rails to further improve their app

                                    - -
                                  • -
                                • Cyrus Innovation LLC.
                                  Senior Agile Developer, July 2007 - February 2014
                                    -
                                  • Cyrus Innovation LLC.
                                    Senior Agile Developer, July 2007 - February 2014
                                    • Built an internal mailing list server in Python, Cyrus Lists, allowing co-workers to self-manage mailing list subscriptions, before Slack was popular.

                                      -
                                    • Launched a Planning Poker iPhone app on the App Store.

                                      -
                                    • Launched I’m Late iPhone app using RubyMotion.

                                      - + prose-a:hover:text-blue-600">

                                      Launched I'm Late iPhone app using RubyMotion.

                                    • Engaged in hiring process, interviewing, and reviewing code tests

                                      -
                                    • Full client project list to come

                                      - -
                                    • -
                                  • Peter Russo Design
                                    Consultant with Cyrus Innovation, Summer 2013 - Fall 2013
                                      -
                                    • Peter Russo Design
                                      Consultant with Cyrus Innovation, Summer 2013 - Fall 2013
                                      • Used CoreBluetooth, MapKit, and RubyMotion to deliver an iPhone app to control mechanical components for a prototype automobile project.

                                        - -
                                      • -
                                    • BazaarVoice
                                      Consultant with Cyrus Innovation, Fall 2012 - Spring 2013
                                        -
                                      • BazaarVoice
                                        Consultant with Cyrus Innovation, Fall 2012 - Spring 2013
                                        • Used AngularJS, Dropwizard, Elastic Search, and AWS automation to deliver internal tools

                                          - -
                                        • -
                                      • For the Makers
                                        Consultant with Cyrus Innovation, Summer 2012
                                          -
                                        • For the Makers
                                          Consultant with Cyrus Innovation, Summer 2012
                                          • Contributed to a small ecommerce website using Ruby on Rails

                                            - -
                                          • -
                                        • Make Love Not Porn
                                          Consultant with Cyrus Innovation, Summer 2012
                                            -
                                          • Make Love Not Porn
                                            Consultant with Cyrus Innovation, Summer 2012
                                            • Used Ruby and Rails to help with video encoding and admin pages

                                              - -
                                            • -
                                          • Simon and Schuster
                                            Consultant with Cyrus Innovation, Fall 2011 - Spring 2012
                                              -
                                            • Simon and Schuster
                                              Consultant with Cyrus Innovation, Fall 2011 - Spring 2012
                                              • Delivered features as an integrated member of their Ruby on Rails development team. Features include new search results page, typeahead search, and PowerReviews integration.

                                                - -
                                              • -
                                            • TheLadders
                                              Consultant with Cyrus Innovation, Spring, Summer 2011
                                                -
                                              • TheLadders
                                                Consultant with Cyrus Innovation, Spring, Summer 2011
                                                • Added features and fixed bugs for their marketing-focused CMS using Java

                                                  - -
                                                • -
                                              • Federal Reserve Bank of NY
                                                Consultant with Cyrus Innovation, Winter 2011
                                                  -
                                                • Federal Reserve Bank of NY
                                                  Consultant with Cyrus Innovation, Winter 2011
                                                  • Developed a document sharing platform in JRuby on Rails, in addition to other maintenance and bug fixes

                                                    - -
                                                  • -
                                                • Boston Capital (Lexingon Solutions)
                                                  Consultant with Cyrus Innovation, Spring 2009 - 2010
                                                    -
                                                  • Boston Capital (Lexingon Solutions)
                                                    Consultant with Cyrus Innovation, Spring 2009 - 2010
                                                    • Built their core asset management system using Webwork, Hibernate, Tomcat, and Pico.

                                                      -
                                                    • Migrated new client databases into Fusion.

                                                      -
                                                    • Setup a VMware TeamCity continuous integration instance.

                                                      - -
                                                    • -
                                                  • ICorrect.com
                                                    Consultant with Cyrus Innovation, Summer 2008
                                                      -
                                                    • ICorrect.com
                                                      Consultant with Cyrus Innovation, Summer 2008
                                                      • Used Ruby, Rails, Rspec, Selenium, and MySql to build a custom CMS.

                                                        - -
                                                      • -
                                                    • Kaplan Test Prep
                                                      Consultant with Cyrus Innovation, Summer 2007 - Winter 2009
                                                        -
                                                      • Kaplan Test Prep
                                                        Consultant with Cyrus Innovation, Summer 2007 - Winter 2009
                                                        • Aided in Agile conversion as an embedded team.

                                                          -
                                                        • Built out their revenue recognition system and their drop shipping system using SpringMVC, Hibernate, Selenium, Dojo, and Fit.

                                                          -
                                                        • Maintained their legacy CSR apps.

                                                          - -
                                                        • -
                                                      • -
                                                      -
      - - - - - - -
      - -

      Conferences

      -
      - - -
      - -

      Education

        -
      • Rutgers University, New Brunswick, NJ (Autumn 2000 - Spring 2007) -
        M.S. & B.S., Computer Science. Minor in Music
          -
        • Musical notation app with MIDI playback. Concepted the app. Designed the UX. Gathered and guided team of four from concept to delivery. Built using Java Swing. -
        • -
      • -
      -
      - -
      -
      -
      - - - \ No newline at end of file +

Conferences

Education

  • Rutgers University, New Brunswick, NJ (Autumn 2000 - Spring 2007) +
    M.S. & B.S., Computer Science. Minor in Music
    • Musical notation app with MIDI playback. Concepted the app. Designed the UX. Gathered and guided team of four from concept to delivery. Built using Java Swing. +
\ No newline at end of file diff --git a/testimonials/index.html b/testimonials/index.html index 0944302..a9e60c0 100644 --- a/testimonials/index.html +++ b/testimonials/index.html @@ -1,70 +1,14 @@ - - - - - - - - - - - - - - Testimonials :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-

Testimonials

-
- -

Testimonials for consulting

    -
  • Grant Surlyn, sunflowerseastar

    - Daniel exhibits the standards of excellence in his interpersonal communication style, his day-to-day work, and his contributions to the wider programming community. The trifecta. -

  • Dan Cogan-Drew, CPO at Newsela

    Daniel got along very well with our team and did meaningful work towards helping us to complete our app.

  • Cliff Hazelton, CTO at Stash

    Dan is a dependable employee and a hard worker. During his time at Stash, he has delivered quality code as a member of the iOS team. He has worked seamlessly on a number of high-value epics and features while delivering in a timely fashion. He was involved in a number of projects, including a complete re-write of our iOS mobile app using the VIPER pattern. More recently, Dan has worked as an iOS engineer on our Growth team. He works closely with the Stash Marketing department and his experience as a senior engineer has allowed the team to move quickly while producing quality software with high test coverage.

  • Shane Donaway, GrowFlow

    - 5 stars, would contract again. I needed something. You gave it to me for a good price. I am happy. Successful project. -

  • -
-
-
- -

Reviews for Underway

    -
  • ian.j.mck, December 27, 2021 -

    Underway is marvelous. It is the navigation app that I use most consistently on the train, and for good reason. It has exactly the information I need and nothing more, and it executes it perfectly. I know how to read a map. I don’t need colorful directions and an inventive new route that combines ferries, buses, and a dollar van. I want to know when I need to leave my apartment and if it’s worth it to wait for an express train. Underway gets me the information I want and it does it without drama. It also has lots of very thoughtful touches. It works very well offline, and I like that I can send a train tracking link to people I’m meeting so that they can track me live. It’s like flight tracking but for the subway. For all its utility and minimalism, Underway also betrays the meticulousness of its creators. Changes to the subway map are added right away — the walking connection between Times Square and Bryant Park seemed to appear within hours after it opened. Everything works how I need it to, when I need it to. The only thing that might need some more nuanced adjustment is the service change interface, which shows absolutely everything affecting a train at once and can be a touch overwhelming. Other apps do service changes, though. Nothing does what Underway does as well as Underway does it. I haven’t even written an app review in years. I just feel I have a duty to point out the rare thing in our world that is not a disgusting disappointment. This is among them. Bravo!

  • -
-
-
-
- - - \ No newline at end of file +Testimonials :: Daniel Ramteke iOS, DevOps, and web development in New York City
Daniel Ramteke
+Resume + +Testimonials + +Press + +Contact +

Testimonials

Testimonials for consulting

  • Grant Surlyn, sunflowerseastar

    +Daniel exhibits the standards of excellence in his interpersonal communication style, his day-to-day work, and his contributions to the wider programming community. The trifecta. +

  • Dan Cogan-Drew, CPO at Newsela

    Daniel got along very well with our team and did meaningful work towards helping us to complete our app.

  • Cliff Hazelton, CTO at Stash

    Dan is a dependable employee and a hard worker. During his time at Stash, he has delivered quality code as a member of the iOS team. He has worked seamlessly on a number of high-value epics and features while delivering in a timely fashion. He was involved in a number of projects, including a complete re-write of our iOS mobile app using the VIPER pattern. More recently, Dan has worked as an iOS engineer on our Growth team. He works closely with the Stash Marketing department and his experience as a senior engineer has allowed the team to move quickly while producing quality software with high test coverage.

  • Shane Donaway, GrowFlow

    +5 stars, would contract again. I needed something. You gave it to me for a good price. I am happy. Successful project. +

Reviews for Underway

  • ian.j.mck, December 27, 2021 +

    Underway is marvelous. It is the navigation app that I use most consistently on the train, and for good reason. It has exactly the information I need and nothing more, and it executes it perfectly. I know how to read a map. I don’t need colorful directions and an inventive new route that combines ferries, buses, and a dollar van. I want to know when I need to leave my apartment and if it’s worth it to wait for an express train. Underway gets me the information I want and it does it without drama. It also has lots of very thoughtful touches. It works very well offline, and I like that I can send a train tracking link to people I’m meeting so that they can track me live. It’s like flight tracking but for the subway. For all its utility and minimalism, Underway also betrays the meticulousness of its creators. Changes to the subway map are added right away — the walking connection between Times Square and Bryant Park seemed to appear within hours after it opened. Everything works how I need it to, when I need it to. The only thing that might need some more nuanced adjustment is the service change interface, which shows absolutely everything affecting a train at once and can be a touch overwhelming. Other apps do service changes, though. Nothing does what Underway does as well as Underway does it. I haven’t even written an app review in years. I just feel I have a duty to point out the rare thing in our world that is not a disgusting disappointment. This is among them. Bravo!

\ No newline at end of file diff --git a/web/index.html b/web/index.html index f016760..2537502 100644 --- a/web/index.html +++ b/web/index.html @@ -1,105 +1,20 @@ - - - - - - - - - - - - - - Custom web development :: Daniel Ramteke iOS, DevOps, and web development in New York City - - - - -
-
Daniel Ramteke
- - -
- - - Resume - - - - Testimonials - - - - Press - - - - Contact - -
- -
- -
-
-
-Custom web development -
- +Custom web development :: Daniel Ramteke iOS, DevOps, and web development in New York City
Custom web development
A website and a webserver often play an important role in a complete app. I've got you covered here. VueJS is my preferred frontend framework, and I'm flexible if your team is already using React, Alpine, or Svelte. - - -
-

Web toolbox

-
- -
-
-

Tailwind CSS

-

- - Tailwind is a fresh take on CSS tooling. It's utilty-first and modular out-of-the box. It's also customizable for managing themes across web properties. This website and www.underway.nyc both use Tailwind CSS. -

- -

Visit website

-
-
- -
-
-

Astro

- -

- Astro is a relatively new static site generator. It pre-renders static React and Vue components for a fast web experience. It uses JSX style components to manage layouts and re-usable components. +

Web toolbox

Tailwind CSS

+Tailwind is a fresh take on CSS tooling. It's utilty-first and modular out-of-the box. It's also customizable for managing themes across web properties. This website and www.underway.nyc both use Tailwind CSS. +

Visit website

Astro

+Astro is a relatively new static site generator. It pre-renders static React and Vue components for a fast web experience. It uses JSX style components to manage layouts and re-usable components. This website and www.underway.nyc are both built using Astro. - -

- -

Visit website

-
-
- -
-
-

Vapor

- -

- Vapor is a webapp framework written on Swift, and built on top on non-blocking IO for lightning speed. - Underway's server relies on Vapor, not only as a webserver, but also for background workers to fetch & transform data from the MTA. -

-

Visit website

- -
-
- -
-
-
-
- - - \ No newline at end of file +

Visit website

Vapor

+Vapor is a webapp framework written on Swift, and built on top on non-blocking IO for lightning speed. +Underway's server relies on Vapor, not only as a webserver, but also for background workers to fetch & transform data from the MTA. +

Visit website

\ No newline at end of file